From c6a6e8b8cde3f7be7a7c509f7c20bad273063556 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Thu, 23 Jan 2025 16:24:00 -0700 Subject: [PATCH 1/4] feat: optional system hooks feature; function that gets triggered on unregister of a delegation --- packages/cli/src/deploy/getWorldContracts.ts | 16 +- packages/world/mud.config.ts | 15 + .../world/src/ICustomUnregisterDelegation.sol | 8 + packages/world/src/IOptionalSystemHook.sol | 43 + packages/world/src/IWorldErrors.sol | 14 + packages/world/src/SystemCall.sol | 35 + packages/world/src/codegen/index.sol | 1 + .../src/codegen/interfaces/IBaseWorld.sol | 4 + .../IExtendedRegistrationSystem.sol | 11 + .../IExtendedWorldRegistrationSystem.sol | 27 + .../codegen/tables/OptionalSystemHooks.sol | 853 ++++++++++++++++++ .../codegen/tables/UserDelegationControl.sol | 89 ++ .../init/ExtendedRegistrationSystem.sol | 16 + .../world/src/modules/init/InitModule.sol | 18 +- packages/world/src/modules/init/constants.sol | 8 + .../src/modules/init/functionSignatures.sol | 11 + .../ExtendedWorldRegistrationSystem.sol | 137 +++ .../WorldRegistrationSystem.sol | 11 + packages/world/test/createInitModule.sol | 4 +- 19 files changed, 1316 insertions(+), 5 deletions(-) create mode 100644 packages/world/src/ICustomUnregisterDelegation.sol create mode 100644 packages/world/src/IOptionalSystemHook.sol create mode 100644 packages/world/src/codegen/interfaces/IExtendedRegistrationSystem.sol create mode 100644 packages/world/src/codegen/interfaces/IExtendedWorldRegistrationSystem.sol create mode 100644 packages/world/src/codegen/tables/OptionalSystemHooks.sol create mode 100644 packages/world/src/modules/init/ExtendedRegistrationSystem.sol create mode 100644 packages/world/src/modules/init/implementations/ExtendedWorldRegistrationSystem.sol diff --git a/packages/cli/src/deploy/getWorldContracts.ts b/packages/cli/src/deploy/getWorldContracts.ts index f0de3388be..ae3e706165 100644 --- a/packages/cli/src/deploy/getWorldContracts.ts +++ b/packages/cli/src/deploy/getWorldContracts.ts @@ -2,6 +2,7 @@ import accessManagementSystemBuild from "@latticexyz/world/out/AccessManagementS import balanceTransferSystemBuild from "@latticexyz/world/out/BalanceTransferSystem.sol/BalanceTransferSystem.json" assert { type: "json" }; import batchCallSystemBuild from "@latticexyz/world/out/BatchCallSystem.sol/BatchCallSystem.json" assert { type: "json" }; import registrationSystemBuild from "@latticexyz/world/out/RegistrationSystem.sol/RegistrationSystem.json" assert { type: "json" }; +import extendedRegistrationSystemBuild from "@latticexyz/world/out/ExtendedRegistrationSystem.sol/ExtendedRegistrationSystem.json" assert { type: "json" }; import initModuleBuild from "@latticexyz/world/out/InitModule.sol/InitModule.json" assert { type: "json" }; import initModuleAbi from "@latticexyz/world/out/InitModule.sol/InitModule.abi.json" assert { type: "json" }; import { Hex, encodeDeployData, size } from "viem"; @@ -33,11 +34,18 @@ export function getWorldContracts(deployerAddress: Hex) { bytecode: registrationBytecode, }); + const extendedRegistrationDeployedBytecodeSize = size(extendedRegistrationSystemBuild.deployedBytecode.object as Hex); + const extendedRegistrationBytecode = extendedRegistrationSystemBuild.bytecode.object as Hex; + const extendedRegistration = getContractAddress({ + deployerAddress, + bytecode: extendedRegistrationBytecode, + }); + const initModuleDeployedBytecodeSize = size(initModuleBuild.deployedBytecode.object as Hex); const initModuleBytecode = encodeDeployData({ bytecode: initModuleBuild.bytecode.object as Hex, abi: initModuleAbi, - args: [accessManagementSystem, balanceTransferSystem, batchCallSystem, registration], + args: [accessManagementSystem, balanceTransferSystem, batchCallSystem, registration, extendedRegistration], }); const initModule = getContractAddress({ deployerAddress, bytecode: initModuleBytecode }); @@ -66,6 +74,12 @@ export function getWorldContracts(deployerAddress: Hex) { debugLabel: "core registration system", address: registration, }, + ExtendedRegistrationSystem: { + bytecode: extendedRegistrationBytecode, + deployedBytecodeSize: extendedRegistrationDeployedBytecodeSize, + debugLabel: "extended core registration system", + address: extendedRegistration, + }, InitModule: { bytecode: initModuleBytecode, deployedBytecodeSize: initModuleDeployedBytecodeSize, diff --git a/packages/world/mud.config.ts b/packages/world/mud.config.ts index f5224bc38e..2cd77f39f2 100644 --- a/packages/world/mud.config.ts +++ b/packages/world/mud.config.ts @@ -45,6 +45,9 @@ export const tablesConfig = defineWorld({ delegationControlId: "ResourceId", }, key: ["delegator", "delegatee"], + codegen: { + storeArgument: true, + }, }, NamespaceDelegationControl: { schema: { @@ -85,6 +88,18 @@ export const tablesConfig = defineWorld({ }, key: ["systemId"], }, + OptionalSystemHooks: { + schema: { + player: "address", + systemId: "ResourceId", + callDataHash: "bytes32", + hooks: "bytes21[]", + }, + key: ["player", "systemId", "callDataHash"], + codegen: { + storeArgument: true, + }, + }, FunctionSelectors: { schema: { worldFunctionSelector: "bytes4", diff --git a/packages/world/src/ICustomUnregisterDelegation.sol b/packages/world/src/ICustomUnregisterDelegation.sol new file mode 100644 index 0000000000..1d4da9c67e --- /dev/null +++ b/packages/world/src/ICustomUnregisterDelegation.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { IERC165 } from "./IERC165.sol"; + +interface ICustomUnregisterDelegation is IERC165 { + function canUnregister(address delegator) external returns (bool); +} diff --git a/packages/world/src/IOptionalSystemHook.sol b/packages/world/src/IOptionalSystemHook.sol new file mode 100644 index 0000000000..391d2004e6 --- /dev/null +++ b/packages/world/src/IOptionalSystemHook.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { ISystemHook } from "./ISystemHook.sol"; + +/** + * @title IOptionalSystemHook + * @dev Interface defining optional hooks for external functionality. + * Provides pre and post hooks that can be triggered before and after a system call respectively. + * This interface adheres to the ERC-165 standard for determining interface support. + */ +interface IOptionalSystemHook is ISystemHook { + /** + * @notice Executes when a system hook is registered by the user. + * @dev Provides the ability to add custom logic or checks when a system hook is registered. + * @param msgSender The original sender of the system call. + * @param systemId The ID of the system + * @param enabledHooksBitmap Bitmap indicating which hooks are enabled + * @param callDataHash The hash of the call data for the system hook + */ + function onRegisterHook( + address msgSender, + ResourceId systemId, + uint8 enabledHooksBitmap, + bytes32 callDataHash + ) external; + + /** + * @notice Executes when a system hook is unregistered by the user. + * @dev Provides the ability to add custom logic or checks when a system hook is unregistered. + * @param msgSender The original sender of the system call. + * @param systemId The ID of the system + * @param enabledHooksBitmap Bitmap indicating which hooks are enabled + * @param callDataHash The hash of the call data for the system hook + */ + function onUnregisterHook( + address msgSender, + ResourceId systemId, + uint8 enabledHooksBitmap, + bytes32 callDataHash + ) external; +} diff --git a/packages/world/src/IWorldErrors.sol b/packages/world/src/IWorldErrors.sol index e702f86695..5627154562 100644 --- a/packages/world/src/IWorldErrors.sol +++ b/packages/world/src/IWorldErrors.sol @@ -80,6 +80,12 @@ interface IWorldErrors { */ error World_UnlimitedDelegationNotAllowed(); + /** + * @notice Raised when unregister delegation is called but a custom unregister delegation blocks it + * e.g. if the delegation agreement wanted to enforce some conditions before allowing the delegation to be removed + */ + error World_CustomUnregisterDelegationNotAllowed(); + /** * @notice Raised when there's an insufficient balance for a particular operation. * @param balance The current balance. @@ -107,4 +113,12 @@ interface IWorldErrors { * @param functionSelector The function selector of the disallowed callback. */ error World_CallbackNotAllowed(bytes4 functionSelector); + + /** + * @notice Raised when trying to register an optional system hook that is already registered. + * @param systemId The ID of the system. + * @param hookAddress The address of the hook. + * @param callDataHash The hash of the call data. + */ + error World_OptionalHookAlreadyRegistered(ResourceId systemId, address hookAddress, bytes32 callDataHash); } diff --git a/packages/world/src/SystemCall.sol b/packages/world/src/SystemCall.sol index 02bbc9575b..b4f193d596 100644 --- a/packages/world/src/SystemCall.sol +++ b/packages/world/src/SystemCall.sol @@ -12,9 +12,11 @@ import { BEFORE_CALL_SYSTEM, AFTER_CALL_SYSTEM } from "./systemHookTypes.sol"; import { IWorldErrors } from "./IWorldErrors.sol"; import { ISystemHook } from "./ISystemHook.sol"; +import { IOptionalSystemHook } from "./IOptionalSystemHook.sol"; import { Systems } from "./codegen/tables/Systems.sol"; import { SystemHooks } from "./codegen/tables/SystemHooks.sol"; +import { OptionalSystemHooks } from "./codegen/tables/OptionalSystemHooks.sol"; import { Balances } from "./codegen/tables/Balances.sol"; /** @@ -145,6 +147,10 @@ library SystemCall { // Get system hooks bytes21[] memory hooks = SystemHooks._get(systemId); + // Get optional hooks specified by the caller + bytes21[] memory optionalSystemHooks = OptionalSystemHooks._get(caller, systemId, bytes32(0)); + bytes21[] memory optionalSystemHooksWithCallData = OptionalSystemHooks._get(caller, systemId, keccak256(callData)); + // Call onBeforeCallSystem hooks (before calling the system) for (uint256 i; i < hooks.length; i++) { Hook hook = Hook.wrap(hooks[i]); @@ -153,6 +159,21 @@ library SystemCall { } } + // Call optional onBeforeCallSystem hooks (before calling the system) + for (uint256 i; i < optionalSystemHooks.length; i++) { + Hook hook = Hook.wrap(optionalSystemHooks[i]); + if (hook.isEnabled(BEFORE_CALL_SYSTEM)) { + IOptionalSystemHook(hook.getAddress()).onBeforeCallSystem(caller, systemId, callData); + } + } + + for (uint256 i; i < optionalSystemHooksWithCallData.length; i++) { + Hook hook = Hook.wrap(optionalSystemHooksWithCallData[i]); + if (hook.isEnabled(BEFORE_CALL_SYSTEM)) { + IOptionalSystemHook(hook.getAddress()).onBeforeCallSystem(caller, systemId, callData); + } + } + // Call the system and forward any return data (success, data) = call({ caller: caller, value: value, systemId: systemId, callData: callData }); @@ -163,6 +184,20 @@ library SystemCall { ISystemHook(hook.getAddress()).onAfterCallSystem(caller, systemId, callData); } } + + // Call optional onAfterCallSystem hooks (after calling the system) + for (uint256 i; i < optionalSystemHooks.length; i++) { + Hook hook = Hook.wrap(optionalSystemHooks[i]); + if (hook.isEnabled(AFTER_CALL_SYSTEM)) { + IOptionalSystemHook(hook.getAddress()).onAfterCallSystem(caller, systemId, callData); + } + } + for (uint256 i; i < optionalSystemHooksWithCallData.length; i++) { + Hook hook = Hook.wrap(optionalSystemHooksWithCallData[i]); + if (hook.isEnabled(AFTER_CALL_SYSTEM)) { + IOptionalSystemHook(hook.getAddress()).onAfterCallSystem(caller, systemId, callData); + } + } } /** diff --git a/packages/world/src/codegen/index.sol b/packages/world/src/codegen/index.sol index b00d58a490..1b9586c5b7 100644 --- a/packages/world/src/codegen/index.sol +++ b/packages/world/src/codegen/index.sol @@ -12,6 +12,7 @@ import { Balances } from "./tables/Balances.sol"; import { Systems } from "./tables/Systems.sol"; import { SystemRegistry } from "./tables/SystemRegistry.sol"; import { SystemHooks } from "./tables/SystemHooks.sol"; +import { OptionalSystemHooks } from "./tables/OptionalSystemHooks.sol"; import { FunctionSelectors } from "./tables/FunctionSelectors.sol"; import { FunctionSignatures } from "./tables/FunctionSignatures.sol"; import { InitModuleAddress } from "./tables/InitModuleAddress.sol"; diff --git a/packages/world/src/codegen/interfaces/IBaseWorld.sol b/packages/world/src/codegen/interfaces/IBaseWorld.sol index 144be64d0f..b86ddfb89d 100644 --- a/packages/world/src/codegen/interfaces/IBaseWorld.sol +++ b/packages/world/src/codegen/interfaces/IBaseWorld.sol @@ -5,10 +5,12 @@ pragma solidity >=0.8.24; import { IStore } from "@latticexyz/store/src/IStore.sol"; import { IWorldKernel } from "../../IWorldKernel.sol"; +import { IExtendedRegistrationSystem } from "./IExtendedRegistrationSystem.sol"; import { IRegistrationSystem } from "./IRegistrationSystem.sol"; import { IAccessManagementSystem } from "./IAccessManagementSystem.sol"; import { IBalanceTransferSystem } from "./IBalanceTransferSystem.sol"; import { IBatchCallSystem } from "./IBatchCallSystem.sol"; +import { IExtendedWorldRegistrationSystem } from "./IExtendedWorldRegistrationSystem.sol"; import { IModuleInstallationSystem } from "./IModuleInstallationSystem.sol"; import { IWorldRegistrationSystem } from "./IWorldRegistrationSystem.sol"; @@ -22,10 +24,12 @@ import { IWorldRegistrationSystem } from "./IWorldRegistrationSystem.sol"; interface IBaseWorld is IStore, IWorldKernel, + IExtendedRegistrationSystem, IRegistrationSystem, IAccessManagementSystem, IBalanceTransferSystem, IBatchCallSystem, + IExtendedWorldRegistrationSystem, IModuleInstallationSystem, IWorldRegistrationSystem {} diff --git a/packages/world/src/codegen/interfaces/IExtendedRegistrationSystem.sol b/packages/world/src/codegen/interfaces/IExtendedRegistrationSystem.sol new file mode 100644 index 0000000000..69ddf03873 --- /dev/null +++ b/packages/world/src/codegen/interfaces/IExtendedRegistrationSystem.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +/** + * @title IExtendedRegistrationSystem + * @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 IExtendedRegistrationSystem {} diff --git a/packages/world/src/codegen/interfaces/IExtendedWorldRegistrationSystem.sol b/packages/world/src/codegen/interfaces/IExtendedWorldRegistrationSystem.sol new file mode 100644 index 0000000000..1c56ad2fd7 --- /dev/null +++ b/packages/world/src/codegen/interfaces/IExtendedWorldRegistrationSystem.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +/* Autogenerated file. Do not edit manually. */ + +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { IOptionalSystemHook } from "../../IOptionalSystemHook.sol"; + +/** + * @title IExtendedWorldRegistrationSystem + * @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 IExtendedWorldRegistrationSystem { + function registerOptionalSystemHook( + ResourceId systemId, + IOptionalSystemHook hookAddress, + uint8 enabledHooksBitmap, + bytes32 callDataHash + ) external; + + function unregisterOptionalSystemHook( + ResourceId systemId, + IOptionalSystemHook hookAddress, + bytes32 callDataHash + ) external; +} diff --git a/packages/world/src/codegen/tables/OptionalSystemHooks.sol b/packages/world/src/codegen/tables/OptionalSystemHooks.sol new file mode 100644 index 0000000000..9ae3230e1d --- /dev/null +++ b/packages/world/src/codegen/tables/OptionalSystemHooks.sol @@ -0,0 +1,853 @@ +// 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"; + +library OptionalSystemHooks { + // Hex below is the result of `WorldResourceIdLib.encode({ namespace: "world", name: "OptionalSystemHo", typeId: RESOURCE_TABLE });` + ResourceId constant _tableId = ResourceId.wrap(0x7462776f726c640000000000000000004f7074696f6e616c53797374656d486f); + + FieldLayout constant _fieldLayout = + FieldLayout.wrap(0x0000000100000000000000000000000000000000000000000000000000000000); + + // Hex-encoded key schema of (address, bytes32, bytes32) + Schema constant _keySchema = Schema.wrap(0x00540300615f5f00000000000000000000000000000000000000000000000000); + // Hex-encoded value schema of (bytes21[]) + Schema constant _valueSchema = Schema.wrap(0x00000001b6000000000000000000000000000000000000000000000000000000); + + /** + * @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] = "player"; + keyNames[1] = "systemId"; + keyNames[2] = "callDataHash"; + } + + /** + * @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[](1); + fieldNames[0] = "hooks"; + } + + /** + * @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 Register the table with its config (using the specified store). + */ + function register(IStore _store) internal { + _store.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + + /** + * @notice Get hooks. + */ + function getHooks( + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Get hooks. + */ + function _getHooks( + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Get hooks (using the specified store). + */ + function getHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = _store.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Get hooks. + */ + function get( + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = StoreSwitch.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Get hooks. + */ + function _get( + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = StoreCore.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Get hooks (using the specified store). + */ + function get( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (bytes21[] memory hooks) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + bytes memory _blob = _store.getDynamicField(_tableId, _keyTuple, 0); + return (SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_bytes21()); + } + + /** + * @notice Set hooks. + */ + function setHooks(address player, ResourceId systemId, bytes32 callDataHash, bytes21[] memory hooks) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Set hooks. + */ + function _setHooks(address player, ResourceId systemId, bytes32 callDataHash, bytes21[] memory hooks) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Set hooks (using the specified store). + */ + function setHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + bytes21[] memory hooks + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Set hooks. + */ + function set(address player, ResourceId systemId, bytes32 callDataHash, bytes21[] memory hooks) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Set hooks. + */ + function _set(address player, ResourceId systemId, bytes32 callDataHash, bytes21[] memory hooks) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Set hooks (using the specified store). + */ + function set( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + bytes21[] memory hooks + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.setDynamicField(_tableId, _keyTuple, 0, EncodeArray.encode((hooks))); + } + + /** + * @notice Get the length of hooks. + */ + function lengthHooks(address player, ResourceId systemId, bytes32 callDataHash) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get the length of hooks. + */ + function _lengthHooks(address player, ResourceId systemId, bytes32 callDataHash) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get the length of hooks (using the specified store). + */ + function lengthHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = _store.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get the length of hooks. + */ + function length(address player, ResourceId systemId, bytes32 callDataHash) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = StoreSwitch.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get the length of hooks. + */ + function _length(address player, ResourceId systemId, bytes32 callDataHash) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = StoreCore.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get the length of hooks (using the specified store). + */ + function length( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal view returns (uint256) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + uint256 _byteLength = _store.getDynamicFieldLength(_tableId, _keyTuple, 0); + unchecked { + return _byteLength / 21; + } + } + + /** + * @notice Get an item of hooks. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemHooks( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Get an item of hooks. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItemHooks( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Get an item of hooks (using the specified store). + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItemHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = _store.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Get an item of hooks. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItem( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = StoreSwitch.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Get an item of hooks. + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function _getItem( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = StoreCore.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Get an item of hooks (using the specified store). + * @dev Reverts with Store_IndexOutOfBounds if `_index` is out of bounds for the array. + */ + function getItem( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index + ) internal view returns (bytes21) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _blob = _store.getDynamicFieldSlice(_tableId, _keyTuple, 0, _index * 21, (_index + 1) * 21); + return (bytes21(_blob)); + } + } + + /** + * @notice Push an element to hooks. + */ + function pushHooks(address player, ResourceId systemId, bytes32 callDataHash, bytes21 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to hooks. + */ + function _pushHooks(address player, ResourceId systemId, bytes32 callDataHash, bytes21 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to hooks (using the specified store). + */ + function pushHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to hooks. + */ + function push(address player, ResourceId systemId, bytes32 callDataHash, bytes21 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to hooks. + */ + function _push(address player, ResourceId systemId, bytes32 callDataHash, bytes21 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Push an element to hooks (using the specified store). + */ + function push(IStore _store, address player, ResourceId systemId, bytes32 callDataHash, bytes21 _element) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.pushToDynamicField(_tableId, _keyTuple, 0, abi.encodePacked((_element))); + } + + /** + * @notice Pop an element from hooks. + */ + function popHooks(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Pop an element from hooks. + */ + function _popHooks(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Pop an element from hooks (using the specified store). + */ + function popHooks(IStore _store, address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Pop an element from hooks. + */ + function pop(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Pop an element from hooks. + */ + function _pop(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Pop an element from hooks (using the specified store). + */ + function pop(IStore _store, address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.popFromDynamicField(_tableId, _keyTuple, 0, 21); + } + + /** + * @notice Update an element of hooks at `_index`. + */ + function updateHooks( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of hooks at `_index`. + */ + function _updateHooks( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of hooks (using the specified store) at `_index`. + */ + function updateHooks( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + _store.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of hooks at `_index`. + */ + function update( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreSwitch.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of hooks at `_index`. + */ + function _update( + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + StoreCore.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Update an element of hooks (using the specified store) at `_index`. + */ + function update( + IStore _store, + address player, + ResourceId systemId, + bytes32 callDataHash, + uint256 _index, + bytes21 _element + ) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + unchecked { + bytes memory _encoded = abi.encodePacked((_element)); + _store.spliceDynamicData(_tableId, _keyTuple, 0, uint40(_index * 21), uint40(_encoded.length), _encoded); + } + } + + /** + * @notice Delete all data for given keys. + */ + function deleteRecord(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreSwitch.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Delete all data for given keys. + */ + function _deleteRecord(address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); + } + + /** + * @notice Delete all data for given keys (using the specified store). + */ + function deleteRecord(IStore _store, address player, ResourceId systemId, bytes32 callDataHash) internal { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + _store.deleteRecord(_tableId, _keyTuple); + } + + /** + * @notice Tightly pack dynamic data lengths using this table's schema. + * @return _encodedLengths The lengths of the dynamic fields (packed into a single bytes32 value). + */ + function encodeLengths(bytes21[] memory hooks) internal pure returns (EncodedLengths _encodedLengths) { + // Lengths are effectively checked during copy by 2**40 bytes exceeding gas limits + unchecked { + _encodedLengths = EncodedLengthsLib.pack(hooks.length * 21); + } + } + + /** + * @notice Tightly pack dynamic (variable length) data using this table's schema. + * @return The dynamic data, encoded into a sequence of bytes. + */ + function encodeDynamic(bytes21[] memory hooks) internal pure returns (bytes memory) { + return abi.encodePacked(EncodeArray.encode((hooks))); + } + + /** + * @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(bytes21[] memory hooks) internal pure returns (bytes memory, EncodedLengths, bytes memory) { + bytes memory _staticData; + EncodedLengths _encodedLengths = encodeLengths(hooks); + bytes memory _dynamicData = encodeDynamic(hooks); + + return (_staticData, _encodedLengths, _dynamicData); + } + + /** + * @notice Encode keys as a bytes32 array using this table's field layout. + */ + function encodeKeyTuple( + address player, + ResourceId systemId, + bytes32 callDataHash + ) internal pure returns (bytes32[] memory) { + bytes32[] memory _keyTuple = new bytes32[](3); + _keyTuple[0] = bytes32(uint256(uint160(player))); + _keyTuple[1] = ResourceId.unwrap(systemId); + _keyTuple[2] = callDataHash; + + return _keyTuple; + } +} diff --git a/packages/world/src/codegen/tables/UserDelegationControl.sol b/packages/world/src/codegen/tables/UserDelegationControl.sol index adb06d1d62..8eb8e5dfe4 100644 --- a/packages/world/src/codegen/tables/UserDelegationControl.sol +++ b/packages/world/src/codegen/tables/UserDelegationControl.sol @@ -64,6 +64,13 @@ library UserDelegationControl { StoreCore.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); } + /** + * @notice Register the table with its config (using the specified store). + */ + function register(IStore _store) internal { + _store.registerTable(_tableId, _fieldLayout, _keySchema, _valueSchema, getKeyNames(), getFieldNames()); + } + /** * @notice Get delegationControlId. */ @@ -94,6 +101,22 @@ library UserDelegationControl { return ResourceId.wrap(bytes32(_blob)); } + /** + * @notice Get delegationControlId (using the specified store). + */ + function getDelegationControlId( + IStore _store, + address delegator, + address delegatee + ) internal view returns (ResourceId delegationControlId) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[1] = bytes32(uint256(uint160(delegatee))); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return ResourceId.wrap(bytes32(_blob)); + } + /** * @notice Get delegationControlId. */ @@ -118,6 +141,22 @@ library UserDelegationControl { return ResourceId.wrap(bytes32(_blob)); } + /** + * @notice Get delegationControlId (using the specified store). + */ + function get( + IStore _store, + address delegator, + address delegatee + ) internal view returns (ResourceId delegationControlId) { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[1] = bytes32(uint256(uint160(delegatee))); + + bytes32 _blob = _store.getStaticField(_tableId, _keyTuple, 0, _fieldLayout); + return ResourceId.wrap(bytes32(_blob)); + } + /** * @notice Set delegationControlId. */ @@ -152,6 +191,28 @@ library UserDelegationControl { ); } + /** + * @notice Set delegationControlId (using the specified store). + */ + function setDelegationControlId( + IStore _store, + address delegator, + address delegatee, + ResourceId delegationControlId + ) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[1] = bytes32(uint256(uint160(delegatee))); + + _store.setStaticField( + _tableId, + _keyTuple, + 0, + abi.encodePacked(ResourceId.unwrap(delegationControlId)), + _fieldLayout + ); + } + /** * @notice Set delegationControlId. */ @@ -186,6 +247,23 @@ library UserDelegationControl { ); } + /** + * @notice Set delegationControlId (using the specified store). + */ + function set(IStore _store, address delegator, address delegatee, ResourceId delegationControlId) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[1] = bytes32(uint256(uint160(delegatee))); + + _store.setStaticField( + _tableId, + _keyTuple, + 0, + abi.encodePacked(ResourceId.unwrap(delegationControlId)), + _fieldLayout + ); + } + /** * @notice Delete all data for given keys. */ @@ -208,6 +286,17 @@ library UserDelegationControl { StoreCore.deleteRecord(_tableId, _keyTuple, _fieldLayout); } + /** + * @notice Delete all data for given keys (using the specified store). + */ + function deleteRecord(IStore _store, address delegator, address delegatee) internal { + bytes32[] memory _keyTuple = new bytes32[](2); + _keyTuple[0] = bytes32(uint256(uint160(delegator))); + _keyTuple[1] = bytes32(uint256(uint160(delegatee))); + + _store.deleteRecord(_tableId, _keyTuple); + } + /** * @notice Tightly pack static (fixed length) data using this table's schema. * @return The static data, encoded into a sequence of bytes. diff --git a/packages/world/src/modules/init/ExtendedRegistrationSystem.sol b/packages/world/src/modules/init/ExtendedRegistrationSystem.sol new file mode 100644 index 0000000000..2f45e2f158 --- /dev/null +++ b/packages/world/src/modules/init/ExtendedRegistrationSystem.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { IWorldErrors } from "../../IWorldErrors.sol"; + +import { ExtendedWorldRegistrationSystem } from "./implementations/ExtendedWorldRegistrationSystem.sol"; + +/** + * @title Extended Registration System for World + * @author MUD (https://mud.dev) by Lattice (https://lattice.xyz) + * @notice This system aggregates World registration and installation functionalities externalized from the World contract, aiming to keep the World contract's bytecode lean. + * @dev Aggregates multiple system implementations for the World. + */ +contract ExtendedRegistrationSystem is ExtendedWorldRegistrationSystem { + // Currently, no additional functionality is added in this aggregate contract. +} diff --git a/packages/world/src/modules/init/InitModule.sol b/packages/world/src/modules/init/InitModule.sol index 417b2f91b3..cc93e51e5c 100644 --- a/packages/world/src/modules/init/InitModule.sol +++ b/packages/world/src/modules/init/InitModule.sol @@ -21,13 +21,15 @@ import { BalanceTransferSystem } from "./implementations/BalanceTransferSystem.s import { BatchCallSystem } from "./implementations/BatchCallSystem.sol"; import { RegistrationSystem } from "./RegistrationSystem.sol"; -import { ACCESS_MANAGEMENT_SYSTEM_ID, BALANCE_TRANSFER_SYSTEM_ID, BATCH_CALL_SYSTEM_ID, REGISTRATION_SYSTEM_ID } from "./constants.sol"; -import { getFunctionSignaturesAccessManagement, getFunctionSignaturesBalanceTransfer, getFunctionSignaturesBatchCall, getFunctionSignaturesRegistration } from "./functionSignatures.sol"; +import { ExtendedRegistrationSystem } from "./ExtendedRegistrationSystem.sol"; +import { ACCESS_MANAGEMENT_SYSTEM_ID, BALANCE_TRANSFER_SYSTEM_ID, BATCH_CALL_SYSTEM_ID, REGISTRATION_SYSTEM_ID, EXTENDED_REGISTRATION_SYSTEM_ID } from "./constants.sol"; +import { getFunctionSignaturesAccessManagement, getFunctionSignaturesBalanceTransfer, getFunctionSignaturesBatchCall, getFunctionSignaturesRegistration, getFunctionSignaturesExtendedRegistration } from "./functionSignatures.sol"; import { Systems } from "../../codegen/tables/Systems.sol"; import { FunctionSelectors } from "../../codegen/tables/FunctionSelectors.sol"; import { FunctionSignatures } from "../../codegen/tables/FunctionSignatures.sol"; import { SystemHooks } from "../../codegen/tables/SystemHooks.sol"; +import { OptionalSystemHooks } from "../../codegen/tables/OptionalSystemHooks.sol"; import { SystemRegistry } from "../../codegen/tables/SystemRegistry.sol"; import { InitModuleAddress } from "../../codegen/tables/InitModuleAddress.sol"; import { Balances } from "../../codegen/tables/Balances.sol"; @@ -45,17 +47,20 @@ contract InitModule is Module { address internal immutable balanceTransferSystem; address internal immutable batchCallSystem; address internal immutable registrationSystem; + address internal immutable extendedRegistrationSystem; constructor( AccessManagementSystem _accessManagementSystem, BalanceTransferSystem _balanceTransferSystem, BatchCallSystem _batchCallSystem, - RegistrationSystem _registrationSystem + RegistrationSystem _registrationSystem, + ExtendedRegistrationSystem _extendedRegistrationSystem ) { accessManagementSystem = address(_accessManagementSystem); balanceTransferSystem = address(_balanceTransferSystem); batchCallSystem = address(_batchCallSystem); registrationSystem = address(_registrationSystem); + extendedRegistrationSystem = address(_extendedRegistrationSystem); } /** @@ -84,6 +89,7 @@ contract InitModule is Module { FunctionSelectors.register(); FunctionSignatures.register(); SystemHooks.register(); + OptionalSystemHooks.register(); SystemRegistry.register(); InitModuleAddress.register(); @@ -108,6 +114,7 @@ contract InitModule is Module { _registerSystem(balanceTransferSystem, BALANCE_TRANSFER_SYSTEM_ID); _registerSystem(batchCallSystem, BATCH_CALL_SYSTEM_ID); _registerSystem(registrationSystem, REGISTRATION_SYSTEM_ID); + _registerSystem(extendedRegistrationSystem, EXTENDED_REGISTRATION_SYSTEM_ID); } /** @@ -147,6 +154,11 @@ contract InitModule is Module { for (uint256 i = 0; i < functionSignaturesRegistration.length; i++) { _registerRootFunctionSelector(REGISTRATION_SYSTEM_ID, functionSignaturesRegistration[i]); } + + string[2] memory functionSignaturesExtendedRegistration = getFunctionSignaturesExtendedRegistration(); + for (uint256 i = 0; i < functionSignaturesExtendedRegistration.length; i++) { + _registerRootFunctionSelector(EXTENDED_REGISTRATION_SYSTEM_ID, functionSignaturesExtendedRegistration[i]); + } } /** diff --git a/packages/world/src/modules/init/constants.sol b/packages/world/src/modules/init/constants.sol index e17ab3635a..39c2c37f35 100644 --- a/packages/world/src/modules/init/constants.sol +++ b/packages/world/src/modules/init/constants.sol @@ -37,3 +37,11 @@ ResourceId constant BATCH_CALL_SYSTEM_ID = ResourceId.wrap( ResourceId constant REGISTRATION_SYSTEM_ID = ResourceId.wrap( bytes32(abi.encodePacked(RESOURCE_SYSTEM, ROOT_NAMESPACE, bytes16("Registration"))) ); + +/** + * @dev Resource ID for extended core registration system. + * @dev This ID is derived from the RESOURCE_SYSTEM type, the ROOT_NAMESPACE, and the system name. + */ +ResourceId constant EXTENDED_REGISTRATION_SYSTEM_ID = ResourceId.wrap( + bytes32(abi.encodePacked(RESOURCE_SYSTEM, ROOT_NAMESPACE, bytes16("ExtendedRegistra"))) +); diff --git a/packages/world/src/modules/init/functionSignatures.sol b/packages/world/src/modules/init/functionSignatures.sol index e9a7fe13bd..122a7c99a8 100644 --- a/packages/world/src/modules/init/functionSignatures.sol +++ b/packages/world/src/modules/init/functionSignatures.sol @@ -60,3 +60,14 @@ function getFunctionSignaturesRegistration() pure returns (string[14] memory) { "unregisterNamespaceDelegation(bytes32)" ]; } + +/** + * @dev Function signatures for extended registration system + */ +function getFunctionSignaturesExtendedRegistration() pure returns (string[2] memory) { + return [ + // --- ExtendedWorldRegistrationSystem --- + "registerOptionalSystemHook(bytes32,address,uint8,bytes32)", + "unregisterOptionalSystemHook(bytes32,address,bytes32)" + ]; +} diff --git a/packages/world/src/modules/init/implementations/ExtendedWorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/ExtendedWorldRegistrationSystem.sol new file mode 100644 index 0000000000..55dc897922 --- /dev/null +++ b/packages/world/src/modules/init/implementations/ExtendedWorldRegistrationSystem.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +import { Hook, HookLib } from "@latticexyz/store/src/Hook.sol"; +import { ResourceId } from "@latticexyz/store/src/ResourceId.sol"; +import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; + +import { System } from "../../../System.sol"; +import { WorldContextConsumer, IWorldContextConsumer } from "../../../WorldContext.sol"; +import { WorldResourceIdLib, WorldResourceIdInstance } from "../../../WorldResourceId.sol"; +import { SystemCall } from "../../../SystemCall.sol"; +import { ROOT_NAMESPACE_ID, ROOT_NAME } from "../../../constants.sol"; +import { RESOURCE_NAMESPACE, RESOURCE_SYSTEM } from "../../../worldResourceTypes.sol"; +import { AccessControl } from "../../../AccessControl.sol"; +import { Delegation } from "../../../Delegation.sol"; +import { requireInterface } from "../../../requireInterface.sol"; +import { NamespaceOwner } from "../../../codegen/tables/NamespaceOwner.sol"; +import { ResourceAccess } from "../../../codegen/tables/ResourceAccess.sol"; +import { UserDelegationControl } from "../../../codegen/tables/UserDelegationControl.sol"; +import { NamespaceDelegationControl } from "../../../codegen/tables/NamespaceDelegationControl.sol"; +import { IOptionalSystemHook } from "../../../IOptionalSystemHook.sol"; +import { IWorldErrors } from "../../../IWorldErrors.sol"; +import { IDelegationControl } from "../../../IDelegationControl.sol"; +import { ICustomUnregisterDelegation } from "../../../ICustomUnregisterDelegation.sol"; +import { ERC165Checker } from "../../../ERC165Checker.sol"; + +import { SystemHooks } from "../../../codegen/tables/SystemHooks.sol"; +import { OptionalSystemHooks } from "../../../codegen/tables/OptionalSystemHooks.sol"; +import { SystemRegistry } from "../../../codegen/tables/SystemRegistry.sol"; +import { Systems } from "../../../codegen/tables/Systems.sol"; +import { FunctionSelectors } from "../../../codegen/tables/FunctionSelectors.sol"; +import { FunctionSignatures } from "../../../codegen/tables/FunctionSignatures.sol"; +import { requireNamespace } from "../../../requireNamespace.sol"; +import { requireValidNamespace } from "../../../requireValidNamespace.sol"; + +import { LimitedCallContext } from "../LimitedCallContext.sol"; + +/** + * @title ExtendedWorldRegistrationSystem + * @dev This contract provides extended functions related to registering resources other than tables in the World. + */ +contract ExtendedWorldRegistrationSystem is System, IWorldErrors, LimitedCallContext { + using WorldResourceIdInstance for ResourceId; + + /** + * @notice Registers a new optional system hook for the user + * @dev Adds a new hook for the system at the provided user, system, and call data hash (optional) + * @param systemId The ID of the system + * @param hookAddress The address of the hook being registered + * @param enabledHooksBitmap Bitmap indicating which hooks are enabled + * @param callDataHash The hash of the call data for the system hook + */ + function registerOptionalSystemHook( + ResourceId systemId, + IOptionalSystemHook hookAddress, + uint8 enabledHooksBitmap, + bytes32 callDataHash + ) public onlyDelegatecall { + // Require the provided system ID to have type RESOURCE_SYSTEM + if (systemId.getType() != RESOURCE_SYSTEM) { + revert World_InvalidResourceType(RESOURCE_SYSTEM, systemId, systemId.toString()); + } + + // Require the provided address to implement the IOptionalSystemHook interface + requireInterface(address(hookAddress), type(IOptionalSystemHook).interfaceId); + + // Require the system to exist + AccessControl.requireExistence(systemId); + + // Require the system's namespace to exist + AccessControl.requireExistence(systemId.getNamespaceId()); + + bytes21[] memory currentHooks = OptionalSystemHooks._get(_msgSender(), systemId, callDataHash); + for (uint256 i = 0; i < currentHooks.length; i++) { + if (Hook.wrap(currentHooks[i]).getAddress() == address(hookAddress)) { + revert World_OptionalHookAlreadyRegistered(systemId, address(hookAddress), callDataHash); + } + } + + IOptionalSystemHook(address(hookAddress)).onRegisterHook(_msgSender(), systemId, enabledHooksBitmap, callDataHash); + + // Register the hook + OptionalSystemHooks.push( + _msgSender(), + systemId, + callDataHash, + Hook.unwrap(HookLib.encode(address(hookAddress), enabledHooksBitmap)) + ); + } + + /** + * @notice Unregisters an optional system hook + * @dev Removes a hook for the system at the provided user, system, and call data hash (optional) + * @param systemId The ID of the system + * @param hookAddress The address of the hook being unregistered + * @param callDataHash The hash of the call data for the system hook + */ + function unregisterOptionalSystemHook( + ResourceId systemId, + IOptionalSystemHook hookAddress, + bytes32 callDataHash + ) public virtual onlyDelegatecall { + // Remove the hook from the list of hooks for this system in the optional system hooks table + bytes21[] memory currentHooks = OptionalSystemHooks._get(_msgSender(), systemId, callDataHash); + + // Initialize the new hooks array with the same length because we don't know if the hook is registered yet + bytes21[] memory newHooks = new bytes21[](currentHooks.length); + + // Filter the array of current hooks + uint256 newHooksIndex; + unchecked { + for (uint256 currentHooksIndex; currentHooksIndex < currentHooks.length; currentHooksIndex++) { + Hook hook = Hook.wrap(currentHooks[currentHooksIndex]); + if (hook.getAddress() != address(hookAddress)) { + newHooks[newHooksIndex] = currentHooks[currentHooksIndex]; + newHooksIndex++; + } else { + address(hookAddress).call( + abi.encodeCall( + IOptionalSystemHook.onUnregisterHook, + (_msgSender(), systemId, hook.getBitmap(), callDataHash) + ) + ); + } + } + } + + // Set the new hooks table length in place + // (Note: this does not update the free memory pointer) + assembly { + mstore(newHooks, newHooksIndex) + } + + // Set the new hooks table + OptionalSystemHooks._set(_msgSender(), systemId, callDataHash, newHooks); + } +} diff --git a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol index ab89a6964d..034600e33d 100644 --- a/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol +++ b/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol @@ -21,6 +21,8 @@ import { NamespaceDelegationControl } from "../../../codegen/tables/NamespaceDel import { ISystemHook } from "../../../ISystemHook.sol"; import { IWorldErrors } from "../../../IWorldErrors.sol"; import { IDelegationControl } from "../../../IDelegationControl.sol"; +import { ICustomUnregisterDelegation } from "../../../ICustomUnregisterDelegation.sol"; +import { ERC165Checker } from "../../../ERC165Checker.sol"; import { SystemHooks } from "../../../codegen/tables/SystemHooks.sol"; import { SystemRegistry } from "../../../codegen/tables/SystemRegistry.sol"; @@ -274,6 +276,15 @@ abstract contract WorldRegistrationSystem is System, IWorldErrors, LimitedCallCo * @param delegatee The address of the delegatee */ function unregisterDelegation(address delegatee) public onlyDelegatecall { + if (ERC165Checker.supportsInterface(delegatee, type(ICustomUnregisterDelegation).interfaceId)) { + (bool canUnregisterSuccess, bytes memory canUnregisterReturnData) = delegatee.call( + abi.encodeCall(ICustomUnregisterDelegation.canUnregister, (_msgSender())) + ); + if (!canUnregisterSuccess) revert World_CustomUnregisterDelegationNotAllowed(); + canUnregisterSuccess = abi.decode(canUnregisterReturnData, (bool)); + if (!canUnregisterSuccess) revert World_CustomUnregisterDelegationNotAllowed(); + } + // Delete the delegation control contract address UserDelegationControl.deleteRecord({ delegator: _msgSender(), delegatee: delegatee }); } diff --git a/packages/world/test/createInitModule.sol b/packages/world/test/createInitModule.sol index 8354584c5a..055d243324 100644 --- a/packages/world/test/createInitModule.sol +++ b/packages/world/test/createInitModule.sol @@ -7,6 +7,7 @@ import { BatchCallSystem } from "../src/modules/init/implementations/BatchCallSy import { InitModule } from "../src/modules/init/InitModule.sol"; import { RegistrationSystem } from "../src/modules/init/RegistrationSystem.sol"; +import { ExtendedRegistrationSystem } from "../src/modules/init/ExtendedRegistrationSystem.sol"; function createInitModule() returns (InitModule) { return @@ -14,6 +15,7 @@ function createInitModule() returns (InitModule) { new AccessManagementSystem(), new BalanceTransferSystem(), new BatchCallSystem(), - new RegistrationSystem() + new RegistrationSystem(), + new ExtendedRegistrationSystem() ); } From f4df4a6ed1907e025ae78a4729b102d8697f515b Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Wed, 29 Jan 2025 16:14:27 -0700 Subject: [PATCH 2/4] feat: compile to cjs --- packages/abi-ts/package.json | 5 +- packages/block-logs-stream/package.json | 5 +- packages/cli/package.json | 5 +- packages/cli/scripts/generate-test-tables.ts | 170 +++++++------- packages/cli/tsup.config.ts | 1 + packages/common/package.json | 50 +++- packages/common/tsup.config.ts | 1 + packages/config/package.json | 15 +- packages/config/tsup.config.ts | 1 + packages/dev-tools/package.json | 5 +- packages/entrykit/package.json | 10 +- packages/entrykit/src/bin/deploy.ts | 167 ++++++------- packages/entrykit/src/store.ts | 4 +- packages/entrykit/tsup.config.ts | 1 + packages/explorer/package.json | 5 +- packages/faucet/package.json | 5 +- packages/faucet/src/bin/faucet-server.ts | 74 +++--- packages/gas-report/package.json | 5 +- packages/protocol-parser/package.json | 10 +- packages/react/package.json | 5 +- packages/recs/package.json | 10 +- packages/schema-type/package.json | 15 +- packages/solhint-config-mud/package.json | 5 +- packages/solhint-plugin-mud/package.json | 5 +- packages/stash/package.json | 15 +- packages/stash/tsup.config.ts | 1 + packages/store-indexer/package.json | 5 +- .../src/bin/postgres-decoded-indexer.ts | 152 ++++++------ .../store-indexer/src/bin/postgres-indexer.ts | 222 +++++++++--------- .../store-indexer/src/bin/sqlite-indexer.ts | 222 +++++++++--------- packages/store-sync/package.json | 55 ++++- packages/store-sync/src/common.ts | 12 +- packages/store-sync/tsup.config.ts | 1 + packages/store/package.json | 20 +- packages/store/tsup.config.ts | 1 + packages/utils/package.json | 5 +- packages/vite-plugin-mud/package.json | 3 +- .../package.json | 10 +- packages/world-module-erc20/package.json | 10 +- packages/world-module-metadata/package.json | 5 +- packages/world-modules/package.json | 5 +- packages/world/package.json | 20 +- packages/world/tsup.config.ts | 1 + tsup.config.base.ts | 2 +- 44 files changed, 781 insertions(+), 565 deletions(-) diff --git a/packages/abi-ts/package.json b/packages/abi-ts/package.json index 90961ef150..3879353f54 100644 --- a/packages/abi-ts/package.json +++ b/packages/abi-ts/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - "./internal": "./dist/exports/internal.js" + "./internal": { + "import": "./dist/exports/internal.js", + "require": "./dist/exports/internal.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/block-logs-stream/package.json b/packages/block-logs-stream/package.json index fc7ac7a47d..a8c84f881d 100644 --- a/packages/block-logs-stream/package.json +++ b/packages/block-logs-stream/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ea213c87f..3c1e796605 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/cli/scripts/generate-test-tables.ts b/packages/cli/scripts/generate-test-tables.ts index 15d1df9cfd..b7e6284f2b 100644 --- a/packages/cli/scripts/generate-test-tables.ts +++ b/packages/cli/scripts/generate-test-tables.ts @@ -3,96 +3,98 @@ import { defineStore } from "@latticexyz/store"; import { fileURLToPath } from "node:url"; import path from "node:path"; -const configPath = fileURLToPath(import.meta.url); +(async (): Promise => { + const configPath = fileURLToPath(import.meta.url); -// This config is used only for tests. -// Aside from avoiding `mud.config.ts` in cli package (could cause issues), -// this also tests that mudConfig and tablegen can work as standalone functions -const config = defineStore({ - sourceDirectory: "../contracts/src", - enums: { - Enum1: ["E1", "E2", "E3"], - Enum2: ["E1"], - }, - userTypes: { - TestTypeAddress: { filePath: "../contracts/src/types.sol", type: "address" }, - TestTypeInt64: { filePath: "../contracts/src/types.sol", type: "int64" }, - "TestTypeLibrary.TestTypeBool": { filePath: "../contracts/src/types.sol", type: "bool" }, - "TestTypeLibrary.TestTypeUint128": { filePath: "../contracts/src/types.sol", type: "uint128" }, - ResourceId: { filePath: "@latticexyz/store/src/ResourceId.sol", type: "bytes32" }, - }, - tables: { - Statics: { - schema: { - k1: "uint256", - k2: "int32", - k3: "bytes16", - k4: "address", - k5: "bool", - k6: "Enum2", - v1: "uint256", - v2: "int32", - v3: "bytes16", - v4: "address", - v5: "bool", - v6: "Enum1", - }, - key: ["k1", "k2", "k3", "k4", "k5", "k6"], + // This config is used only for tests. + // Aside from avoiding `mud.config.ts` in cli package (could cause issues), + // this also tests that mudConfig and tablegen can work as standalone functions + const config = defineStore({ + sourceDirectory: "../contracts/src", + enums: { + Enum1: ["E1", "E2", "E3"], + Enum2: ["E1"], }, - Dynamics1: { - schema: { - key: "bytes32", - staticB32: "bytes32[1]", - staticI32: "int32[2]", - staticU128: "uint128[3]", - staticAddrs: "address[4]", - staticBools: "bool[5]", - }, - key: ["key"], + userTypes: { + TestTypeAddress: { filePath: "../contracts/src/types.sol", type: "address" }, + TestTypeInt64: { filePath: "../contracts/src/types.sol", type: "int64" }, + "TestTypeLibrary.TestTypeBool": { filePath: "../contracts/src/types.sol", type: "bool" }, + "TestTypeLibrary.TestTypeUint128": { filePath: "../contracts/src/types.sol", type: "uint128" }, + ResourceId: { filePath: "@latticexyz/store/src/ResourceId.sol", type: "bytes32" }, }, - Dynamics2: { - schema: { - key: "bytes32", - u64: "uint64[]", - str: "string", - b: "bytes", + tables: { + Statics: { + schema: { + k1: "uint256", + k2: "int32", + k3: "bytes16", + k4: "address", + k5: "bool", + k6: "Enum2", + v1: "uint256", + v2: "int32", + v3: "bytes16", + v4: "address", + v5: "bool", + v6: "Enum1", + }, + key: ["k1", "k2", "k3", "k4", "k5", "k6"], }, - key: ["key"], - }, - Singleton: { - schema: { - v1: "int256", - v2: "uint32[2]", - v3: "uint32[2]", - v4: "uint32[1]", + Dynamics1: { + schema: { + key: "bytes32", + staticB32: "bytes32[1]", + staticI32: "int32[2]", + staticU128: "uint128[3]", + staticAddrs: "address[4]", + staticBools: "bool[5]", + }, + key: ["key"], }, - key: [], - codegen: { dataStruct: false }, - }, - Offchain: { - type: "offchainTable", - schema: { - key: "bytes32", - value: "uint256", + Dynamics2: { + schema: { + key: "bytes32", + u64: "uint64[]", + str: "string", + b: "bytes", + }, + key: ["key"], }, - key: ["key"], - }, - UserTyped: { - schema: { - k1: "TestTypeAddress", - k2: "TestTypeInt64", - k3: "TestTypeLibrary.TestTypeBool", - k4: "TestTypeLibrary.TestTypeUint128", - k5: "ResourceId", - v1: "TestTypeAddress", - v2: "TestTypeInt64", - v3: "TestTypeLibrary.TestTypeBool", - v4: "TestTypeLibrary.TestTypeUint128", - v5: "ResourceId", + Singleton: { + schema: { + v1: "int256", + v2: "uint32[2]", + v3: "uint32[2]", + v4: "uint32[1]", + }, + key: [], + codegen: { dataStruct: false }, + }, + Offchain: { + type: "offchainTable", + schema: { + key: "bytes32", + value: "uint256", + }, + key: ["key"], + }, + UserTyped: { + schema: { + k1: "TestTypeAddress", + k2: "TestTypeInt64", + k3: "TestTypeLibrary.TestTypeBool", + k4: "TestTypeLibrary.TestTypeUint128", + k5: "ResourceId", + v1: "TestTypeAddress", + v2: "TestTypeInt64", + v3: "TestTypeLibrary.TestTypeBool", + v4: "TestTypeLibrary.TestTypeUint128", + v5: "ResourceId", + }, + key: ["k1", "k2", "k3", "k4", "k5"], }, - key: ["k1", "k2", "k3", "k4", "k5"], }, - }, -}); + }); -await tablegen({ rootDir: path.dirname(configPath), config }); + await tablegen({ rootDir: path.dirname(configPath), config }); +})(); diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index c307577d6c..86dffab1e5 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -24,4 +24,5 @@ export default defineConfig((opts) => ({ env: { MUD_PACKAGES: JSON.stringify(mudPackages), }, + noExternal: ["@ark/util", "p-queue", "p-retry"], })); diff --git a/packages/common/package.json b/packages/common/package.json index 363fc85302..66cddf6adf 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -10,16 +10,46 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./actions": "./dist/actions.js", - "./chains": "./dist/chains.js", - "./codegen": "./dist/codegen.js", - "./errors": "./dist/errors.js", - "./foundry": "./dist/foundry.js", - "./type-utils": "./dist/type-utils.js", - "./utils": "./dist/utils.js", - "./kms": "./dist/kms.js", - "./internal": "./dist/internal.js", + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./actions": { + "import": "./dist/actions.js", + "require": "./dist/actions.cjs" + }, + "./chains": { + "import": "./dist/chains.js", + "require": "./dist/chains.cjs" + }, + "./codegen": { + "import": "./dist/codegen.js", + "require": "./dist/codegen.cjs" + }, + "./errors": { + "import": "./dist/errors.js", + "require": "./dist/errors.cjs" + }, + "./foundry": { + "import": "./dist/foundry.js", + "require": "./dist/foundry.cjs" + }, + "./type-utils": { + "import": "./dist/type-utils.js", + "require": "./dist/type-utils.cjs" + }, + "./utils": { + "import": "./dist/utils.js", + "require": "./dist/utils.cjs" + }, + "./kms": { + "import": "./dist/kms.js", + "require": "./dist/kms.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, "./tsconfig.base.json": "./tsconfig.base.json" }, "typesVersions": { diff --git a/packages/common/tsup.config.ts b/packages/common/tsup.config.ts index 5df16f9c38..9667985b61 100644 --- a/packages/common/tsup.config.ts +++ b/packages/common/tsup.config.ts @@ -15,4 +15,5 @@ export default defineConfig((opts) => ({ kms: "src/exports/kms.ts", internal: "src/exports/internal.ts", }, + noExternal: ["p-queue", "p-retry"], })); diff --git a/packages/config/package.json b/packages/config/package.json index e3e5b72dbf..6b861af2a0 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -10,9 +10,18 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js", - "./node": "./dist/deprecated/node.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./node": { + "import": "./dist/deprecated/node.js", + "require": "./dist/deprecated/node.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/config/tsup.config.ts b/packages/config/tsup.config.ts index 3a5748aef6..cfd67b96a9 100644 --- a/packages/config/tsup.config.ts +++ b/packages/config/tsup.config.ts @@ -8,4 +8,5 @@ export default defineConfig((opts) => ({ internal: "src/exports/internal.ts", "deprecated/node": "src/deprecated/node/index.ts", }, + noExternal: ["@ark/util"], })); diff --git a/packages/dev-tools/package.json b/packages/dev-tools/package.json index b701c6b8e6..5f94f655c9 100644 --- a/packages/dev-tools/package.json +++ b/packages/dev-tools/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/entrykit/package.json b/packages/entrykit/package.json index c3f7885363..d32527da1b 100644 --- a/packages/entrykit/package.json +++ b/packages/entrykit/package.json @@ -10,8 +10,14 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/tsup/exports/index.js", - "./internal": "./dist/tsup/exports/internal.js" + ".": { + "import": "./dist/tsup/exports/index.js", + "require": "./dist/tsup/exports/index.cjs" + }, + "./internal": { + "import": "./dist/tsup/exports/internal.js", + "require": "./dist/tsup/exports/internal.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/entrykit/src/bin/deploy.ts b/packages/entrykit/src/bin/deploy.ts index d9b503f701..a31f1328da 100644 --- a/packages/entrykit/src/bin/deploy.ts +++ b/packages/entrykit/src/bin/deploy.ts @@ -26,110 +26,111 @@ import { writeContract } from "@latticexyz/common"; import { entryPoint07Address } from "viem/account-abstraction"; // TODO: parse env with arktype (to avoid zod dep) and throw when absent - -const privateKey = process.env.PRIVATE_KEY; -if (!isHex(privateKey)) { - // TODO: detect anvil and automatically put this env var where it needs to go? - throw new Error( - `Missing \`PRIVATE_KEY\` environment variable. If you're using Anvil, run +(async (): Promise => { + const privateKey = process.env.PRIVATE_KEY; + if (!isHex(privateKey)) { + // TODO: detect anvil and automatically put this env var where it needs to go? + throw new Error( + `Missing \`PRIVATE_KEY\` environment variable. If you're using Anvil, run echo "PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" > .env to use a prefunded Anvil account.`, - ); -} -const account = privateKeyToAccount(privateKey); -const rpcUrl = await getRpcUrl(); - -const client = createClient({ account, transport: http(rpcUrl) }); - -const chainId = await getChainId(client); - -console.log("Deploying to chain", chainId, "from", account.address, "via", rpcUrl); + ); + } + const account = privateKeyToAccount(privateKey); + const rpcUrl = await getRpcUrl(); -// TODO: deployer address flag/env var? -const deployerAddress = await ensureDeployer(client); + const client = createClient({ account, transport: http(rpcUrl) }); -// https://github.com/eth-infinitism/account-abstraction/blob/b3bae63bd9bc0ed394dfca8668008213127adb62/hardhat.config.ts#L11 -const entryPointSalt = "0x90d8084deab30c2a37c45e8d47f49f2f7965183cb6990a98943ef94940681de3"; -const entryPointAddress = getContractAddress({ - deployerAddress, - bytecode: entryPointArtifact.bytecode as Hex, - salt: entryPointSalt, -}); -if (entryPointAddress !== entryPoint07Address) { - throw new Error( - `Unexpected EntryPoint v0.7 address\n\n Expected: ${entryPoint07Address}\nActual: ${entryPointAddress}`, - ); -} + const chainId = await getChainId(client); -// Deploy entrypoint first, because following deploys need to be able to call it. -await ensureContractsDeployed({ - client, - deployerAddress, - contracts: [ - { - bytecode: entryPointArtifact.bytecode as Hex, - salt: entryPointSalt, - deployedBytecodeSize: size(entryPointArtifact.deployedBytecode as Hex), - debugLabel: "EntryPoint v0.7", - }, - ], -}); + console.log("Deploying to chain", chainId, "from", account.address, "via", rpcUrl); -await ensureContractsDeployed({ - client, - deployerAddress, - contracts: [ - { - bytecode: concatHex([ - simpleAccountFactoryArtifact.bytecode as Hex, - encodeAbiParameters(parseAbiParameters("address"), [entryPointAddress]), - ]), - deployedBytecodeSize: size(simpleAccountFactoryArtifact.deployedBytecode as Hex), - debugLabel: "SimpleAccountFactory", - }, - ], -}); + // TODO: deployer address flag/env var? + const deployerAddress = await ensureDeployer(client); -if (chainId === 31337) { - const localPaymasterBytecode = concatHex([ - localPaymasterArtifact.bytecode.object as Hex, - encodeAbiParameters(parseAbiParameters("address"), [entryPointAddress]), - ]); - const localPaymasterAddress = getContractAddress({ deployerAddress, bytecode: localPaymasterBytecode }); + // https://github.com/eth-infinitism/account-abstraction/blob/b3bae63bd9bc0ed394dfca8668008213127adb62/hardhat.config.ts#L11 + const entryPointSalt = "0x90d8084deab30c2a37c45e8d47f49f2f7965183cb6990a98943ef94940681de3"; + const entryPointAddress = getContractAddress({ + deployerAddress, + bytecode: entryPointArtifact.bytecode as Hex, + salt: entryPointSalt, + }); + if (entryPointAddress !== entryPoint07Address) { + throw new Error( + `Unexpected EntryPoint v0.7 address\n\n Expected: ${entryPoint07Address}\nActual: ${entryPointAddress}`, + ); + } + // Deploy entrypoint first, because following deploys need to be able to call it. await ensureContractsDeployed({ client, deployerAddress, contracts: [ { - bytecode: localPaymasterBytecode, - deployedBytecodeSize: size(localPaymasterArtifact.deployedBytecode.object as Hex), - debugLabel: "GenerousPaymaster", + bytecode: entryPointArtifact.bytecode as Hex, + salt: entryPointSalt, + deployedBytecodeSize: size(entryPointArtifact.deployedBytecode as Hex), + debugLabel: "EntryPoint v0.7", }, ], }); - const tx = await writeContract(client, { - chain: null, - address: entryPointAddress, - abi: [ + await ensureContractsDeployed({ + client, + deployerAddress, + contracts: [ { - inputs: [{ name: "account", type: "address" }], - name: "depositTo", - outputs: [], - stateMutability: "payable", - type: "function", + bytecode: concatHex([ + simpleAccountFactoryArtifact.bytecode as Hex, + encodeAbiParameters(parseAbiParameters("address"), [entryPointAddress]), + ]), + deployedBytecodeSize: size(simpleAccountFactoryArtifact.deployedBytecode as Hex), + debugLabel: "SimpleAccountFactory", }, ], - functionName: "depositTo", - args: [localPaymasterAddress], - value: parseEther("100"), }); - await waitForTransactions({ client, hashes: [tx] }); - console.log("\nFunded local paymaster at:", localPaymasterAddress, "\n"); -} -console.log("\nEntryKit contracts are ready!\n"); -process.exit(0); + if (chainId === 31337) { + const localPaymasterBytecode = concatHex([ + localPaymasterArtifact.bytecode.object as Hex, + encodeAbiParameters(parseAbiParameters("address"), [entryPointAddress]), + ]); + const localPaymasterAddress = getContractAddress({ deployerAddress, bytecode: localPaymasterBytecode }); + + await ensureContractsDeployed({ + client, + deployerAddress, + contracts: [ + { + bytecode: localPaymasterBytecode, + deployedBytecodeSize: size(localPaymasterArtifact.deployedBytecode.object as Hex), + debugLabel: "GenerousPaymaster", + }, + ], + }); + + const tx = await writeContract(client, { + chain: null, + address: entryPointAddress, + abi: [ + { + inputs: [{ name: "account", type: "address" }], + name: "depositTo", + outputs: [], + stateMutability: "payable", + type: "function", + }, + ], + functionName: "depositTo", + args: [localPaymasterAddress], + value: parseEther("100"), + }); + await waitForTransactions({ client, hashes: [tx] }); + console.log("\nFunded local paymaster at:", localPaymasterAddress, "\n"); + } + + console.log("\nEntryKit contracts are ready!\n"); + process.exit(0); +})(); diff --git a/packages/entrykit/src/store.ts b/packages/entrykit/src/store.ts index 99852130f5..1d24fa921e 100644 --- a/packages/entrykit/src/store.ts +++ b/packages/entrykit/src/store.ts @@ -26,4 +26,6 @@ function listener(event: StorageEvent) { store.persist.rehydrate(); } } -window.addEventListener("storage", listener); +if (typeof window !== "undefined") { + window.addEventListener("storage", listener); +} diff --git a/packages/entrykit/tsup.config.ts b/packages/entrykit/tsup.config.ts index 436412f738..53a3e64273 100644 --- a/packages/entrykit/tsup.config.ts +++ b/packages/entrykit/tsup.config.ts @@ -6,6 +6,7 @@ export default defineConfig((opts) => ({ ...baseConfig(opts), outDir: "dist/tsup", entry: ["src/exports/index.ts", "src/exports/internal.ts", "src/bin/deploy.ts"], + noExternal: ["@ark/util"], // Because we're injecting CSS via shadow DOM, we'll disable style injection and load CSS as a base64 string. // TODO: figure out how to do this conditionally for only specific imports? injectStyle: false, diff --git a/packages/explorer/package.json b/packages/explorer/package.json index 22a815fc5a..7763fa0390 100644 --- a/packages/explorer/package.json +++ b/packages/explorer/package.json @@ -4,7 +4,10 @@ "description": "World Explorer is a tool for visually exploring and manipulating the state of worlds", "type": "module", "exports": { - "./observer": "./dist/exports/observer.js" + "./observer": { + "import": "./dist/exports/observer.js", + "require": "./dist/exports/observer.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/faucet/package.json b/packages/faucet/package.json index f54cfa1b43..badf59605a 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/faucet/src/bin/faucet-server.ts b/packages/faucet/src/bin/faucet-server.ts index 657a06c213..0b4b175166 100644 --- a/packages/faucet/src/bin/faucet-server.ts +++ b/packages/faucet/src/bin/faucet-server.ts @@ -7,39 +7,41 @@ import { privateKeyToAccount } from "viem/accounts"; import { AppRouter, createAppRouter } from "../createAppRouter"; import { parseEnv } from "./parseEnv"; -const env = parseEnv(); - -const client = createClient({ - transport: http(env.RPC_HTTP_URL), -}); - -const faucetAccount = privateKeyToAccount(env.FAUCET_PRIVATE_KEY); - -// @see https://fastify.dev/docs/latest/ -const server = fastify({ - maxParamLength: 5000, - logger: true, -}); - -await server.register(import("@fastify/compress")); -await server.register(import("@fastify/cors")); - -// k8s healthchecks -server.get("/healthz", (req, res) => res.code(200).send()); -server.get("/readyz", (req, res) => res.code(200).send()); - -// @see https://trpc.io/docs/server/adapters/fastify -server.register(fastifyTRPCPlugin, { - prefix: "/trpc", - trpcOptions: { - router: createAppRouter(), - createContext: async () => ({ - client, - faucetAccount, - dripAmount: env.DRIP_AMOUNT_ETHER, - }), - }, -}); - -await server.listen({ host: env.HOST, port: env.PORT }); -console.log(`faucet server listening on http://${env.HOST}:${env.PORT}`); +(async (): Promise => { + const env = parseEnv(); + + const client = createClient({ + transport: http(env.RPC_HTTP_URL), + }); + + const faucetAccount = privateKeyToAccount(env.FAUCET_PRIVATE_KEY); + + // @see https://fastify.dev/docs/latest/ + const server = fastify({ + maxParamLength: 5000, + logger: true, + }); + + await server.register(import("@fastify/compress")); + await server.register(import("@fastify/cors")); + + // k8s healthchecks + server.get("/healthz", (req, res) => res.code(200).send()); + server.get("/readyz", (req, res) => res.code(200).send()); + + // @see https://trpc.io/docs/server/adapters/fastify + server.register(fastifyTRPCPlugin, { + prefix: "/trpc", + trpcOptions: { + router: createAppRouter(), + createContext: async () => ({ + client, + faucetAccount, + dripAmount: env.DRIP_AMOUNT_ETHER, + }), + }, + }); + + await server.listen({ host: env.HOST, port: env.PORT }); + console.log(`faucet server listening on http://${env.HOST}:${env.PORT}`); +})(); diff --git a/packages/gas-report/package.json b/packages/gas-report/package.json index da4c41e06b..46e79f0e60 100644 --- a/packages/gas-report/package.json +++ b/packages/gas-report/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - "./internal": "./dist/exports/internal.js" + "./internal": { + "import": "./dist/exports/internal.js", + "require": "./dist/exports/internal.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/protocol-parser/package.json b/packages/protocol-parser/package.json index 434ab7d0a5..01ca0fe262 100644 --- a/packages/protocol-parser/package.json +++ b/packages/protocol-parser/package.json @@ -10,8 +10,14 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/react/package.json b/packages/react/package.json index 830057db7a..c9e8b0aae2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/recs/package.json b/packages/recs/package.json index bd17120a30..fa770e8979 100644 --- a/packages/recs/package.json +++ b/packages/recs/package.json @@ -9,8 +9,14 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./deprecated": "./dist/deprecated/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./deprecated": { + "import": "./dist/deprecated/index.js", + "require": "./dist/deprecated/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/schema-type/package.json b/packages/schema-type/package.json index de38ff5086..50fdab23c0 100644 --- a/packages/schema-type/package.json +++ b/packages/schema-type/package.json @@ -10,9 +10,18 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js", - "./deprecated": "./dist/deprecated.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./deprecated": { + "import": "./dist/deprecated.js", + "require": "./dist/deprecated.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/solhint-config-mud/package.json b/packages/solhint-config-mud/package.json index 6e4251bc47..189ade7282 100644 --- a/packages/solhint-config-mud/package.json +++ b/packages/solhint-config-mud/package.json @@ -8,7 +8,10 @@ }, "license": "MIT", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/solhint-plugin-mud/package.json b/packages/solhint-plugin-mud/package.json index a001b43e7c..72ad25dfa2 100644 --- a/packages/solhint-plugin-mud/package.json +++ b/packages/solhint-plugin-mud/package.json @@ -8,7 +8,10 @@ }, "license": "MIT", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/stash/package.json b/packages/stash/package.json index 13ba1c6697..d167d3cf69 100644 --- a/packages/stash/package.json +++ b/packages/stash/package.json @@ -10,9 +10,18 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js", - "./react": "./dist/react.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./react": { + "import": "./dist/react.js", + "require": "./dist/react.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/stash/tsup.config.ts b/packages/stash/tsup.config.ts index f0538dc123..6f10b25b16 100644 --- a/packages/stash/tsup.config.ts +++ b/packages/stash/tsup.config.ts @@ -4,4 +4,5 @@ import { baseConfig } from "../../tsup.config.base"; export default defineConfig((opts) => ({ ...baseConfig(opts), entry: ["src/exports/index.ts", "src/exports/internal.ts", "src/exports/react.ts"], + noExternal: ["@ark/util"], })); diff --git a/packages/store-indexer/package.json b/packages/store-indexer/package.json index f53d41888c..25e673b482 100644 --- a/packages/store-indexer/package.json +++ b/packages/store-indexer/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/store-indexer/src/bin/postgres-decoded-indexer.ts b/packages/store-indexer/src/bin/postgres-decoded-indexer.ts index ba49e79b7f..7c71b0c1b5 100644 --- a/packages/store-indexer/src/bin/postgres-decoded-indexer.ts +++ b/packages/store-indexer/src/bin/postgres-decoded-indexer.ts @@ -15,93 +15,95 @@ import { getClientOptions } from "./getClientOptions"; import { getChainId } from "viem/actions"; import { getRpcClient } from "@latticexyz/block-logs-stream"; -const env = parseEnv( - z.intersection( - indexerEnvSchema, - z.object({ - DATABASE_URL: z.string(), - HEALTHCHECK_HOST: z.string().optional(), - HEALTHCHECK_PORT: z.coerce.number().optional(), - SENTRY_DSN: z.string().optional(), - }), - ), -); +(async (): Promise => { + const env = parseEnv( + z.intersection( + indexerEnvSchema, + z.object({ + DATABASE_URL: z.string(), + HEALTHCHECK_HOST: z.string().optional(), + HEALTHCHECK_PORT: z.coerce.number().optional(), + SENTRY_DSN: z.string().optional(), + }), + ), + ); -const clientOptions = await getClientOptions(env); + const clientOptions = await getClientOptions(env); -const chainId = await getChainId(getRpcClient(clientOptions)); -const database = drizzle(postgres(env.DATABASE_URL, { prepare: false })); + const chainId = await getChainId(getRpcClient(clientOptions)); + const database = drizzle(postgres(env.DATABASE_URL, { prepare: false })); -const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database }); + const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database }); -let startBlock = env.START_BLOCK; + let startBlock = env.START_BLOCK; -// Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. -// TODO: query if the DB exists instead of try/catch -try { - const chainState = await database - .select() - .from(tables.configTable) - .where(eq(tables.configTable.chainId, chainId)) - .limit(1) - .execute() - // Get the first record in a way that returns a possible `undefined` - // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true` - .then((rows) => rows.find(() => true)); + // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. + // TODO: query if the DB exists instead of try/catch + try { + const chainState = await database + .select() + .from(tables.configTable) + .where(eq(tables.configTable.chainId, chainId)) + .limit(1) + .execute() + // Get the first record in a way that returns a possible `undefined` + // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true` + .then((rows) => rows.find(() => true)); - if (chainState?.blockNumber != null) { - startBlock = chainState.blockNumber + 1n; - console.log("resuming from block number", startBlock); + if (chainState?.blockNumber != null) { + startBlock = chainState.blockNumber + 1n; + console.log("resuming from block number", startBlock); + } + } catch (error) { + // ignore errors for now } -} catch (error) { - // ignore errors for now -} -const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({ - ...clientOptions, - storageAdapter, - followBlockTag: env.FOLLOW_BLOCK_TAG, - startBlock, - maxBlockRange: env.MAX_BLOCK_RANGE, - address: env.STORE_ADDRESS, -}); + const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({ + ...clientOptions, + storageAdapter, + followBlockTag: env.FOLLOW_BLOCK_TAG, + startBlock, + maxBlockRange: env.MAX_BLOCK_RANGE, + address: env.STORE_ADDRESS, + }); -storedBlockLogs$.subscribe(); + storedBlockLogs$.subscribe(); -let isCaughtUp = false; -combineLatest([latestBlockNumber$, storedBlockLogs$]) - .pipe( - filter( - ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => - latestBlockNumber === lastBlockNumberProcessed, - ), - first(), - ) - .subscribe(() => { - isCaughtUp = true; - console.log("all caught up"); - }); + let isCaughtUp = false; + combineLatest([latestBlockNumber$, storedBlockLogs$]) + .pipe( + filter( + ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => + latestBlockNumber === lastBlockNumberProcessed, + ), + first(), + ) + .subscribe(() => { + isCaughtUp = true; + console.log("all caught up"); + }); -if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { - const { default: Koa } = await import("koa"); - const { default: cors } = await import("@koa/cors"); + if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { + const { default: Koa } = await import("koa"); + const { default: cors } = await import("@koa/cors"); - const server = new Koa(); + const server = new Koa(); - if (env.SENTRY_DSN) { - server.use(sentry(env.SENTRY_DSN)); - } + if (env.SENTRY_DSN) { + server.use(sentry(env.SENTRY_DSN)); + } - server.use(cors()); - server.use( - healthcheck({ - isReady: () => isCaughtUp, - }), - ); - server.use(helloWorld()); + server.use(cors()); + server.use( + healthcheck({ + isReady: () => isCaughtUp, + }), + ); + server.use(helloWorld()); - server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT }); - console.log( - `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`, - ); -} + server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT }); + console.log( + `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`, + ); + } +})(); diff --git a/packages/store-indexer/src/bin/postgres-indexer.ts b/packages/store-indexer/src/bin/postgres-indexer.ts index ce6a36ab0d..c25f2aa2d9 100644 --- a/packages/store-indexer/src/bin/postgres-indexer.ts +++ b/packages/store-indexer/src/bin/postgres-indexer.ts @@ -12,118 +12,120 @@ import { getClientOptions } from "./getClientOptions"; import { getBlock, getChainId } from "viem/actions"; import { getRpcClient } from "@latticexyz/block-logs-stream"; -const env = parseEnv( - z.intersection( - indexerEnvSchema, - z.object({ - DATABASE_URL: z.string(), - HEALTHCHECK_HOST: z.string().optional(), - HEALTHCHECK_PORT: z.coerce.number().optional(), - }), - ), -); - -const clientOptions = await getClientOptions(env); - -const chainId = await getChainId(getRpcClient(clientOptions)); -const database = drizzle(postgres(env.DATABASE_URL, { prepare: false })); - -if (await shouldCleanDatabase(database, chainId)) { - console.log("outdated database detected, clearing data to start fresh"); - await cleanDatabase(database); -} - -const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database }); - -let startBlock = env.START_BLOCK; - -async function getLatestStoredBlockNumber(): Promise { - // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. - // TODO: query if the DB exists instead of try/catch - try { - const chainState = await database - .select() - .from(tables.configTable) - .where(eq(tables.configTable.chainId, chainId)) - .limit(1) - .execute() - // Get the first record in a way that returns a possible `undefined` - // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true` - .then((rows) => rows.find(() => true)); - - return chainState?.blockNumber; - } catch (error) { - // ignore errors for now - } -} - -async function getDistanceFromFollowBlock(): Promise { - const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([ - getLatestStoredBlockNumber(), - getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }), - ]); - return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n); -} - -const latestStoredBlockNumber = await getLatestStoredBlockNumber(); -if (latestStoredBlockNumber != null) { - startBlock = latestStoredBlockNumber + 1n; - console.log("resuming from block number", startBlock); -} - -const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({ - ...clientOptions, - storageAdapter, - followBlockTag: env.FOLLOW_BLOCK_TAG, - startBlock, - maxBlockRange: env.MAX_BLOCK_RANGE, - address: env.STORE_ADDRESS, -}); - -storedBlockLogs$.subscribe(); - -let isCaughtUp = false; -combineLatest([latestBlockNumber$, storedBlockLogs$]) - .pipe( - filter( - ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => - latestBlockNumber === lastBlockNumberProcessed, +(async (): Promise => { + const env = parseEnv( + z.intersection( + indexerEnvSchema, + z.object({ + DATABASE_URL: z.string(), + HEALTHCHECK_HOST: z.string().optional(), + HEALTHCHECK_PORT: z.coerce.number().optional(), + }), ), - first(), - ) - .subscribe(() => { - isCaughtUp = true; - console.log("all caught up"); - }); + ); -if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { - const { default: Koa } = await import("koa"); - const { default: cors } = await import("@koa/cors"); - const { healthcheck } = await import("../koa-middleware/healthcheck"); - const { metrics } = await import("../koa-middleware/metrics"); - const { helloWorld } = await import("../koa-middleware/helloWorld"); + const clientOptions = await getClientOptions(env); - const server = new Koa(); + const chainId = await getChainId(getRpcClient(clientOptions)); + const database = drizzle(postgres(env.DATABASE_URL, { prepare: false })); - server.use(cors()); - server.use( - healthcheck({ - isReady: () => isCaughtUp, - }), - ); - server.use( - metrics({ - isHealthy: () => true, - isReady: () => isCaughtUp, - getLatestStoredBlockNumber, - getDistanceFromFollowBlock, - followBlockTag: env.FOLLOW_BLOCK_TAG, - }), - ); - server.use(helloWorld()); + if (await shouldCleanDatabase(database, chainId)) { + console.log("outdated database detected, clearing data to start fresh"); + await cleanDatabase(database); + } - server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT }); - console.log( - `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`, - ); -} + const { storageAdapter, tables } = await createStorageAdapter({ ...clientOptions, database }); + + let startBlock = env.START_BLOCK; + + async function getLatestStoredBlockNumber(): Promise { + // Fetch latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. + // TODO: query if the DB exists instead of try/catch + try { + const chainState = await database + .select() + .from(tables.configTable) + .where(eq(tables.configTable.chainId, chainId)) + .limit(1) + .execute() + // Get the first record in a way that returns a possible `undefined` + // TODO: move this to `.findFirst` after upgrading drizzle or `rows[0]` after enabling `noUncheckedIndexedAccess: true` + .then((rows) => rows.find(() => true)); + + return chainState?.blockNumber; + } catch (error) { + // ignore errors for now + } + } + + async function getDistanceFromFollowBlock(): Promise { + const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([ + getLatestStoredBlockNumber(), + getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }), + ]); + return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n); + } + + const latestStoredBlockNumber = await getLatestStoredBlockNumber(); + if (latestStoredBlockNumber != null) { + startBlock = latestStoredBlockNumber + 1n; + console.log("resuming from block number", startBlock); + } + + const { latestBlockNumber$, storedBlockLogs$ } = await createStoreSync({ + ...clientOptions, + storageAdapter, + followBlockTag: env.FOLLOW_BLOCK_TAG, + startBlock, + maxBlockRange: env.MAX_BLOCK_RANGE, + address: env.STORE_ADDRESS, + }); + + storedBlockLogs$.subscribe(); + + let isCaughtUp = false; + combineLatest([latestBlockNumber$, storedBlockLogs$]) + .pipe( + filter( + ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => + latestBlockNumber === lastBlockNumberProcessed, + ), + first(), + ) + .subscribe(() => { + isCaughtUp = true; + console.log("all caught up"); + }); + + if (env.HEALTHCHECK_HOST != null || env.HEALTHCHECK_PORT != null) { + const { default: Koa } = await import("koa"); + const { default: cors } = await import("@koa/cors"); + const { healthcheck } = await import("../koa-middleware/healthcheck"); + const { metrics } = await import("../koa-middleware/metrics"); + const { helloWorld } = await import("../koa-middleware/helloWorld"); + + const server = new Koa(); + + server.use(cors()); + server.use( + healthcheck({ + isReady: () => isCaughtUp, + }), + ); + server.use( + metrics({ + isHealthy: () => true, + isReady: () => isCaughtUp, + getLatestStoredBlockNumber, + getDistanceFromFollowBlock, + followBlockTag: env.FOLLOW_BLOCK_TAG, + }), + ); + server.use(helloWorld()); + + server.listen({ host: env.HEALTHCHECK_HOST, port: env.HEALTHCHECK_PORT }); + console.log( + `postgres indexer healthcheck server listening on http://${env.HEALTHCHECK_HOST}:${env.HEALTHCHECK_PORT}`, + ); + } +})(); diff --git a/packages/store-indexer/src/bin/sqlite-indexer.ts b/packages/store-indexer/src/bin/sqlite-indexer.ts index f7137094db..0ce8c11c01 100644 --- a/packages/store-indexer/src/bin/sqlite-indexer.ts +++ b/packages/store-indexer/src/bin/sqlite-indexer.ts @@ -22,131 +22,133 @@ import { getClientOptions } from "./getClientOptions"; import { getRpcClient } from "@latticexyz/block-logs-stream"; import { getBlock, getChainId } from "viem/actions"; -const env = parseEnv( - z.intersection( - z.intersection(indexerEnvSchema, frontendEnvSchema), - z.object({ - SQLITE_FILENAME: z.string().default("indexer.db"), - SENTRY_DSN: z.string().optional(), - }), - ), -); +(async (): Promise => { + const env = parseEnv( + z.intersection( + z.intersection(indexerEnvSchema, frontendEnvSchema), + z.object({ + SQLITE_FILENAME: z.string().default("indexer.db"), + SENTRY_DSN: z.string().optional(), + }), + ), + ); -const clientOptions = await getClientOptions(env); + const clientOptions = await getClientOptions(env); -const chainId = await getChainId(getRpcClient(clientOptions)); -const database = drizzle(new Database(env.SQLITE_FILENAME)); + const chainId = await getChainId(getRpcClient(clientOptions)); + const database = drizzle(new Database(env.SQLITE_FILENAME)); -let startBlock = env.START_BLOCK; + let startBlock = env.START_BLOCK; -async function getCurrentChainState(): Promise< - | { - schemaVersion: number; - chainId: number; - lastUpdatedBlockNumber: bigint | null; - lastError: string | null; + async function getCurrentChainState(): Promise< + | { + schemaVersion: number; + chainId: number; + lastUpdatedBlockNumber: bigint | null; + lastError: string | null; + } + | undefined + > { + // This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. + try { + const currentChainStates = database.select().from(chainState).where(eq(chainState.chainId, chainId)).all(); + // TODO: replace this type workaround with `noUncheckedIndexedAccess: true` when we can fix all the issues related (https://github.com/latticexyz/mud/issues/1212) + const currentChainState: (typeof currentChainStates)[number] | undefined = currentChainStates[0]; + return currentChainState; + } catch (error) { + // ignore errors, this is optional } - | undefined -> { - // This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. - try { - const currentChainStates = database.select().from(chainState).where(eq(chainState.chainId, chainId)).all(); - // TODO: replace this type workaround with `noUncheckedIndexedAccess: true` when we can fix all the issues related (https://github.com/latticexyz/mud/issues/1212) - const currentChainState: (typeof currentChainStates)[number] | undefined = currentChainStates[0]; - return currentChainState; - } catch (error) { - // ignore errors, this is optional } -} - -async function getLatestStoredBlockNumber(): Promise { - const currentChainState = await getCurrentChainState(); - return currentChainState?.lastUpdatedBlockNumber ?? undefined; -} -async function getDistanceFromFollowBlock(): Promise { - const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([ - getLatestStoredBlockNumber(), - getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }), - ]); - return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n); -} + async function getLatestStoredBlockNumber(): Promise { + const currentChainState = await getCurrentChainState(); + return currentChainState?.lastUpdatedBlockNumber ?? undefined; + } -const currentChainState = await getCurrentChainState(); -if (currentChainState) { - // Reset the db if the version changed - if (currentChainState.schemaVersion != schemaVersion) { - console.log( - "schema version changed from", - currentChainState.schemaVersion, - "to", - schemaVersion, - "recreating database", - ); - fs.truncateSync(env.SQLITE_FILENAME); - } else if (currentChainState.lastUpdatedBlockNumber != null) { - // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. - console.log("resuming from block number", currentChainState.lastUpdatedBlockNumber + 1n); - startBlock = currentChainState.lastUpdatedBlockNumber + 1n; + async function getDistanceFromFollowBlock(): Promise { + const [latestStoredBlockNumber, latestFollowBlock] = await Promise.all([ + getLatestStoredBlockNumber(), + getBlock(getRpcClient(clientOptions), { blockTag: env.FOLLOW_BLOCK_TAG }), + ]); + return latestFollowBlock.number - (latestStoredBlockNumber ?? -1n); } -} -const { latestBlockNumber$, storedBlockLogs$ } = await syncToSqlite({ - ...clientOptions, - database, - followBlockTag: env.FOLLOW_BLOCK_TAG, - startBlock, - maxBlockRange: env.MAX_BLOCK_RANGE, - address: env.STORE_ADDRESS, -}); + const currentChainState = await getCurrentChainState(); + if (currentChainState) { + // Reset the db if the version changed + if (currentChainState.schemaVersion != schemaVersion) { + console.log( + "schema version changed from", + currentChainState.schemaVersion, + "to", + schemaVersion, + "recreating database", + ); + fs.truncateSync(env.SQLITE_FILENAME); + } else if (currentChainState.lastUpdatedBlockNumber != null) { + // Resume from latest block stored in DB. This will throw if the DB doesn't exist yet, so we wrap in a try/catch and ignore the error. + console.log("resuming from block number", currentChainState.lastUpdatedBlockNumber + 1n); + startBlock = currentChainState.lastUpdatedBlockNumber + 1n; + } + } -let isCaughtUp = false; -combineLatest([latestBlockNumber$, storedBlockLogs$]) - .pipe( - filter( - ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => - latestBlockNumber === lastBlockNumberProcessed, - ), - first(), - ) - .subscribe(() => { - isCaughtUp = true; - console.log("all caught up"); + const { latestBlockNumber$, storedBlockLogs$ } = await syncToSqlite({ + ...clientOptions, + database, + followBlockTag: env.FOLLOW_BLOCK_TAG, + startBlock, + maxBlockRange: env.MAX_BLOCK_RANGE, + address: env.STORE_ADDRESS, }); -const server = new Koa(); + let isCaughtUp = false; + combineLatest([latestBlockNumber$, storedBlockLogs$]) + .pipe( + filter( + ([latestBlockNumber, { blockNumber: lastBlockNumberProcessed }]) => + latestBlockNumber === lastBlockNumberProcessed, + ), + first(), + ) + .subscribe(() => { + isCaughtUp = true; + console.log("all caught up"); + }); -if (env.SENTRY_DSN) { - server.use(sentry(env.SENTRY_DSN)); -} + const server = new Koa(); -server.use(cors()); -server.use( - healthcheck({ - isReady: () => isCaughtUp, - }), -); -server.use( - metrics({ - isHealthy: () => true, - isReady: () => isCaughtUp, - getLatestStoredBlockNumber, - getDistanceFromFollowBlock, - followBlockTag: env.FOLLOW_BLOCK_TAG, - }), -); -server.use(helloWorld()); -server.use(apiRoutes(database)); + if (env.SENTRY_DSN) { + server.use(sentry(env.SENTRY_DSN)); + } + + server.use(cors()); + server.use( + healthcheck({ + isReady: () => isCaughtUp, + }), + ); + server.use( + metrics({ + isHealthy: () => true, + isReady: () => isCaughtUp, + getLatestStoredBlockNumber, + getDistanceFromFollowBlock, + followBlockTag: env.FOLLOW_BLOCK_TAG, + }), + ); + server.use(helloWorld()); + server.use(apiRoutes(database)); -server.use( - createKoaMiddleware({ - prefix: "/trpc", - router: createAppRouter(), - createContext: async () => ({ - queryAdapter: await createQueryAdapter(database), + server.use( + createKoaMiddleware({ + prefix: "/trpc", + router: createAppRouter(), + createContext: async () => ({ + queryAdapter: await createQueryAdapter(database), + }), }), - }), -); + ); -server.listen({ host: env.HOST, port: env.PORT }); -console.log(`sqlite indexer frontend listening on http://${env.HOST}:${env.PORT}`); + server.listen({ host: env.HOST, port: env.PORT }); + console.log(`sqlite indexer frontend listening on http://${env.HOST}:${env.PORT}`); +})(); diff --git a/packages/store-sync/package.json b/packages/store-sync/package.json index 0ad02e3acd..e27a5c4db3 100644 --- a/packages/store-sync/package.json +++ b/packages/store-sync/package.json @@ -10,17 +10,50 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./indexer-client": "./dist/indexer-client/index.js", - "./internal": "./dist/exports/internal.js", - "./react": "./dist/exports/react.js", - "./postgres": "./dist/postgres/index.js", - "./postgres-decoded": "./dist/postgres-decoded/index.js", - "./recs": "./dist/recs/index.js", - "./sqlite": "./dist/sqlite/index.js", - "./trpc-indexer": "./dist/trpc-indexer/index.js", - "./world": "./dist/world/index.js", - "./zustand": "./dist/zustand/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./indexer-client": { + "import": "./dist/indexer-client/index.js", + "require": "./dist/indexer-client/index.cjs" + }, + "./internal": { + "import": "./dist/exports/internal.js", + "require": "./dist/exports/internal.cjs" + }, + "./react": { + "import": "./dist/exports/react.js", + "require": "./dist/exports/react.cjs" + }, + "./postgres": { + "import": "./dist/postgres/index.js", + "require": "./dist/postgres/index.cjs" + }, + "./postgres-decoded": { + "import": "./dist/postgres-decoded/index.js", + "require": "./dist/postgres-decoded/index.cjs" + }, + "./recs": { + "import": "./dist/recs/index.js", + "require": "./dist/recs/index.cjs" + }, + "./sqlite": { + "import": "./dist/sqlite/index.js", + "require": "./dist/sqlite/index.cjs" + }, + "./trpc-indexer": { + "import": "./dist/trpc-indexer/index.js", + "require": "./dist/trpc-indexer/index.cjs" + }, + "./world": { + "import": "./dist/world/index.js", + "require": "./dist/world/index.cjs" + }, + "./zustand": { + "import": "./dist/zustand/index.js", + "require": "./dist/zustand/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/store-sync/src/common.ts b/packages/store-sync/src/common.ts index d05ca7bf60..0bc1a78cc0 100644 --- a/packages/store-sync/src/common.ts +++ b/packages/store-sync/src/common.ts @@ -9,12 +9,20 @@ import { getSchemaTypes, getValueSchema, } from "@latticexyz/protocol-parser/internal"; -import storeConfig from "@latticexyz/store/mud.config"; -import worldConfig from "@latticexyz/world/mud.config"; +import storeConfigRaw from "@latticexyz/store/mud.config"; +import worldConfigRaw from "@latticexyz/world/mud.config"; import { Table as ConfigTable, Schema } from "@latticexyz/config"; import { configToTables } from "./configToTables"; import { GetRpcClientOptions } from "@latticexyz/block-logs-stream"; +// TODO: move to utils +function normalizeImport(module: T): T { + return (module && typeof module === "object" && "default" in module ? module.default : module) as T; +} + +const storeConfig = normalizeImport(storeConfigRaw); +const worldConfig = normalizeImport(worldConfigRaw); + export const mudTables = { ...configToTables(storeConfig), ...configToTables(worldConfig), diff --git a/packages/store-sync/tsup.config.ts b/packages/store-sync/tsup.config.ts index 899fee714a..c5bbfed78d 100644 --- a/packages/store-sync/tsup.config.ts +++ b/packages/store-sync/tsup.config.ts @@ -16,4 +16,5 @@ export default defineConfig((opts) => ({ "src/exports/internal.ts", "src/exports/react.ts", ], + noExternal: ["@ark/util", "change-case"], })); diff --git a/packages/store/package.json b/packages/store/package.json index 25aa4cf6a1..10c8bef8b6 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -10,10 +10,22 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js", - "./mud.config": "./dist/mud.config.js", - "./codegen": "./dist/codegen.js", + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, + "./codegen": { + "import": "./dist/codegen.js", + "require": "./dist/codegen.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/store/tsup.config.ts b/packages/store/tsup.config.ts index 67faa149d8..5c57f37a7e 100644 --- a/packages/store/tsup.config.ts +++ b/packages/store/tsup.config.ts @@ -9,4 +9,5 @@ export default defineConfig((opts) => ({ internal: "ts/exports/internal.ts", codegen: "ts/codegen/index.ts", }, + noExternal: ["@ark/util"], })); diff --git a/packages/utils/package.json b/packages/utils/package.json index 0c670f27c6..84c3f10ef6 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -9,7 +9,10 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } }, "typesVersions": { "*": { diff --git a/packages/vite-plugin-mud/package.json b/packages/vite-plugin-mud/package.json index 806da03089..02889b53d3 100644 --- a/packages/vite-plugin-mud/package.json +++ b/packages/vite-plugin-mud/package.json @@ -11,7 +11,8 @@ "exports": { ".": { "types": "./dist/exports/index.d.ts", - "default": "./dist/exports/index.js" + "import": "./dist/exports/index.js", + "require": "./dist/exports/index.cjs" }, "./env": { "types": "./env.d.ts" diff --git a/packages/world-module-callwithsignature/package.json b/packages/world-module-callwithsignature/package.json index ad86760b7d..cb105f7e8b 100644 --- a/packages/world-module-callwithsignature/package.json +++ b/packages/world-module-callwithsignature/package.json @@ -10,8 +10,14 @@ "license": "MIT", "type": "module", "exports": { - "./internal": "./dist/internal.js", - "./mud.config": "./dist/mud.config.js", + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/world-module-erc20/package.json b/packages/world-module-erc20/package.json index a6c1bc1042..19f3add697 100644 --- a/packages/world-module-erc20/package.json +++ b/packages/world-module-erc20/package.json @@ -10,8 +10,14 @@ "license": "MIT", "type": "module", "exports": { - "./internal": "./dist/internal.js", - "./mud.config": "./dist/mud.config.js", + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/world-module-metadata/package.json b/packages/world-module-metadata/package.json index c3b793f5cb..63ec8210ee 100644 --- a/packages/world-module-metadata/package.json +++ b/packages/world-module-metadata/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - "./mud.config": "./dist/mud.config.js", + "./mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/world-modules/package.json b/packages/world-modules/package.json index 7678385b97..ba0dc6e51d 100644 --- a/packages/world-modules/package.json +++ b/packages/world-modules/package.json @@ -10,7 +10,10 @@ "license": "MIT", "type": "module", "exports": { - "./internal/mud.config": "./dist/mud.config.js", + "./internal/mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/world/package.json b/packages/world/package.json index 8cd2b73c77..05ef8cd92b 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -10,10 +10,22 @@ "license": "MIT", "type": "module", "exports": { - ".": "./dist/index.js", - "./internal": "./dist/internal.js", - "./mud.config": "./dist/mud.config.js", - "./node": "./dist/node.js", + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./internal": { + "import": "./dist/internal.js", + "require": "./dist/internal.cjs" + }, + "./mud.config": { + "import": "./dist/mud.config.js", + "require": "./dist/mud.config.cjs" + }, + "./node": { + "import": "./dist/node.js", + "require": "./dist/node.cjs" + }, "./out/*": "./out/*" }, "typesVersions": { diff --git a/packages/world/tsup.config.ts b/packages/world/tsup.config.ts index e46bf8c00d..7c7b7390f3 100644 --- a/packages/world/tsup.config.ts +++ b/packages/world/tsup.config.ts @@ -9,4 +9,5 @@ export default defineConfig((opts) => ({ internal: "ts/exports/internal.ts", node: "ts/node/index.ts", }, + noExternal: ["@ark/util"], })); diff --git a/tsup.config.base.ts b/tsup.config.base.ts index d2f705550e..9c4440d80a 100644 --- a/tsup.config.base.ts +++ b/tsup.config.base.ts @@ -7,7 +7,7 @@ import { Options } from "tsup"; export function baseConfig(opts: Options): Options { return { target: "esnext", - format: ["esm"], + format: ["esm", "cjs"], sourcemap: true, // don't generate DTS during watch mode because it's slow // we're likely using TS source in this mode anyway From bd856ec9e53a9f32809db6d8c3ad9b33c8b7cdd0 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Wed, 29 Jan 2025 16:14:52 -0700 Subject: [PATCH 3/4] feat(common): export getFeeRef --- packages/common/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 109e7e7817..94a6153e48 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -19,6 +19,7 @@ export * from "./sendTransaction"; export * from "./spliceHex"; export * from "./transportObserver"; export * from "./writeContract"; +export * from "./getFeeRef"; /** @deprecated use `getContract` instead */ export { createContract } from "./deprecated/createContract"; From d6db3b41cc1cd12f54426db2dd3bd26a32e54317 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Wed, 29 Jan 2025 16:15:16 -0700 Subject: [PATCH 4/4] feat(world-modules): add register erc20 and erc721 method without installing module --- .../modules/erc20-puppet/registerERC20.sol | 20 +++++++++++++++++++ .../modules/erc721-puppet/registerERC721.sol | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol b/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol index 1fb446ce7c..7471170e9a 100644 --- a/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol +++ b/packages/world-modules/src/modules/erc20-puppet/registerERC20.sol @@ -33,3 +33,23 @@ function registerERC20( // Return the newly created ERC20 token token = IERC20Mintable(ERC20Registry.get(ERC20_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); } + +/** + * @notice Register a new ERC20 token with the given metadata in a given namespace + * @dev This function must be called within a Store context (i.e. using StoreSwitch.setStoreAddress()) + */ +function registerERC20Strict( + IBaseWorld world, + bytes14 namespace, + ERC20MetadataData memory metadata +) returns (IERC20Mintable token) { + // Get the ERC20 module + ERC20Module erc20Module = ERC20Module(NamespaceOwner.get(MODULE_NAMESPACE_ID)); + require(address(erc20Module) != address(0), "ERC20Module not installed"); + + // Install the ERC20 module with the provided args + world.installModule(erc20Module, abi.encode(namespace, metadata)); + + // Return the newly created ERC20 token + token = IERC20Mintable(ERC20Registry.get(ERC20_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); +} diff --git a/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol index 52c43d80eb..6d5f06a98c 100644 --- a/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol +++ b/packages/world-modules/src/modules/erc721-puppet/registerERC721.sol @@ -35,3 +35,23 @@ function registerERC721( // Return the newly created ERC721 token token = IERC721Mintable(ERC721Registry.get(ERC721_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); } + +/** + * @notice Register a new ERC721 token with the given metadata in a given namespace + * @dev This function must be called within a Store context (i.e. using StoreSwitch.setStoreAddress()) + */ +function registerERC721Strict( + IBaseWorld world, + bytes14 namespace, + ERC721MetadataData memory metadata +) returns (IERC721Mintable token) { + // Get the ERC721 module + ERC721Module erc721Module = ERC721Module(NamespaceOwner.get(MODULE_NAMESPACE_ID)); + require(address(erc721Module) != address(0), "ERC721Module not installed"); + + // Install the ERC721 module with the provided args + world.installModule(erc721Module, abi.encode(namespace, metadata)); + + // Return the newly created ERC721 token + token = IERC721Mintable(ERC721Registry.get(ERC721_REGISTRY_TABLE_ID, WorldResourceIdLib.encodeNamespace(namespace))); +}