From 862eb421e28a1d60c1b5313d77d63402442f4d0f Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Fri, 6 Oct 2023 12:23:55 +0100 Subject: [PATCH] register functions --- .../src/codegen/world/IDoubleSystem.sol | 12 ++++ .../contracts/src/codegen/world/IWorld.sol | 3 +- .../contracts/src/systems/DoubleSystem.sol | 14 +++++ packages/cli/src/commands/deploy2.ts | 3 +- packages/cli/src/deploy/common.ts | 5 +- packages/cli/src/deploy/deploy.ts | 11 +++- packages/cli/src/deploy/deployWorld.ts | 4 +- packages/cli/src/deploy/ensureFunctions.ts | 63 +++++++++++++++++++ .../cli/src/deploy/ensureSystemFunctions.ts | 51 --------------- packages/cli/src/deploy/ensureSystems.ts | 10 +-- packages/cli/src/deploy/getFunctions.ts | 6 +- packages/cli/src/deploy/getResourceIds.ts | 10 +-- packages/cli/src/deploy/getTables.ts | 2 +- packages/cli/src/deploy/getWorldDeploy.ts | 13 +++- packages/cli/src/deploy/logsToWorldDeploy.ts | 10 +-- 15 files changed, 132 insertions(+), 85 deletions(-) create mode 100644 examples/minimal/packages/contracts/src/codegen/world/IDoubleSystem.sol create mode 100644 examples/minimal/packages/contracts/src/systems/DoubleSystem.sol create mode 100644 packages/cli/src/deploy/ensureFunctions.ts delete mode 100644 packages/cli/src/deploy/ensureSystemFunctions.ts diff --git a/examples/minimal/packages/contracts/src/codegen/world/IDoubleSystem.sol b/examples/minimal/packages/contracts/src/codegen/world/IDoubleSystem.sol new file mode 100644 index 0000000000..795ab6484f --- /dev/null +++ b/examples/minimal/packages/contracts/src/codegen/world/IDoubleSystem.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; + +/* Autogenerated file. Do not edit manually. */ + +/** + * @title IDoubleSystem + * @dev This interface is automatically generated from the corresponding system contract. Do not edit manually. + */ +interface IDoubleSystem { + function double() external returns (uint32); +} diff --git a/examples/minimal/packages/contracts/src/codegen/world/IWorld.sol b/examples/minimal/packages/contracts/src/codegen/world/IWorld.sol index d46a073b79..4ead03bf19 100644 --- a/examples/minimal/packages/contracts/src/codegen/world/IWorld.sol +++ b/examples/minimal/packages/contracts/src/codegen/world/IWorld.sol @@ -6,6 +6,7 @@ pragma solidity >=0.8.21; import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.sol"; import { IChatSystem } from "./IChatSystem.sol"; +import { IDoubleSystem } from "./IDoubleSystem.sol"; import { IIncrementSystem } from "./IIncrementSystem.sol"; import { IInventorySystem } from "./IInventorySystem.sol"; import { IStructSystem } from "./IStructSystem.sol"; @@ -16,6 +17,6 @@ import { IStructSystem } from "./IStructSystem.sol"; * that are dynamically registered in the World during deployment. * @dev This is an autogenerated file; do not edit manually. */ -interface IWorld is IBaseWorld, IChatSystem, IIncrementSystem, IInventorySystem, IStructSystem { +interface IWorld is IBaseWorld, IChatSystem, IDoubleSystem, IIncrementSystem, IInventorySystem, IStructSystem { } diff --git a/examples/minimal/packages/contracts/src/systems/DoubleSystem.sol b/examples/minimal/packages/contracts/src/systems/DoubleSystem.sol new file mode 100644 index 0000000000..89014f853e --- /dev/null +++ b/examples/minimal/packages/contracts/src/systems/DoubleSystem.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.21; +import { console } from "forge-std/console.sol"; +import { System } from "@latticexyz/world/src/System.sol"; +import { CounterTable } from "../codegen/index.sol"; + +contract DoubleSystem is System { + function double() public returns (uint32) { + uint32 counter = CounterTable.get(); + uint32 newValue = counter * 2; + CounterTable.set(newValue); + return newValue; + } +} diff --git a/packages/cli/src/commands/deploy2.ts b/packages/cli/src/commands/deploy2.ts index 7a7a625303..0060937196 100644 --- a/packages/cli/src/commands/deploy2.ts +++ b/packages/cli/src/commands/deploy2.ts @@ -80,7 +80,7 @@ const commandModule: CommandModule = { .filter((sig) => !baseSystemFunctions.includes(sig)) .map((sig): WorldFunction => { // TODO: figure out how to not duplicate contract behavior (https://github.com/latticexyz/mud/issues/1708) - const worldSignature = namespace === "" ? `${namespace}_${name}_${sig}` : sig; + const worldSignature = namespace === "" ? sig : `${namespace}_${name}_${sig}`; return { signature: worldSignature, selector: getFunctionSelector(worldSignature), @@ -110,6 +110,7 @@ const commandModule: CommandModule = { }) ); await deploy({ + worldAddress: args.worldAddress as Hex | undefined, client, config: { tables: configToTables(config), diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 970d72e0e7..1ec8c30a8e 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -23,7 +23,10 @@ export type WorldDeploy = { address: Address; worldVersion: string; storeVersion: string; - blockNumber: bigint; + /** Block number where the world was deployed */ + fromBlock: bigint; + /** Block number at the time of fetching world deploy, to keep further queries aligned to the same block number */ + toBlock: bigint; }; export type WorldFunction = { diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index 5f1ec53956..89d6186477 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -7,6 +7,7 @@ import { ensureSystems } from "./ensureSystems"; import { waitForTransactionReceipt } from "viem/actions"; import { getResourceIds } from "./getResourceIds"; import { getWorldDeploy } from "./getWorldDeploy"; +import { ensureFunctions } from "./ensureFunctions"; type DeployOptions = { client: Client; @@ -26,6 +27,8 @@ export async function deploy({ : await deployWorld(client); // TODO: check that world/store versions are compatible with our deploy + // TODO: update RPC get calls to use `worldDeploy.toBlock` to align the block number everywhere + const tableTxs = await ensureTables({ client, worldDeploy, @@ -36,9 +39,13 @@ export async function deploy({ worldDeploy, systems: Object.values(config.systems), }); + const functionTxs = await ensureFunctions({ + client, + worldDeploy, + functions: Object.values(config.systems).flatMap((system) => system.functions), + }); const receipts = await Promise.all( - [...tableTxs, ...systemTxs].map((tx) => waitForTransactionReceipt(client, { hash: tx })) + [...tableTxs, ...systemTxs, ...functionTxs].map((tx) => waitForTransactionReceipt(client, { hash: tx })) ); - // console.log(config); } diff --git a/packages/cli/src/deploy/deployWorld.ts b/packages/cli/src/deploy/deployWorld.ts index f0bd72a82e..013644fd52 100644 --- a/packages/cli/src/deploy/deployWorld.ts +++ b/packages/cli/src/deploy/deployWorld.ts @@ -37,7 +37,7 @@ export async function deployWorld(client: Client log as Log)); - debug("deployed world to", deploy.address, "at block", deploy.blockNumber); + debug("deployed world to", deploy.address, "at block", deploy.fromBlock); - return deploy; + return { ...deploy, toBlock: deploy.fromBlock }; } diff --git a/packages/cli/src/deploy/ensureFunctions.ts b/packages/cli/src/deploy/ensureFunctions.ts new file mode 100644 index 0000000000..73f31cbe5d --- /dev/null +++ b/packages/cli/src/deploy/ensureFunctions.ts @@ -0,0 +1,63 @@ +import { Client, Transport, Chain, Account, Hex, getFunctionSelector } from "viem"; +import { hexToResource, writeContract } from "@latticexyz/common"; +import { System, WorldDeploy, WorldFunction, worldAbi } from "./common"; +import { debug } from "./debug"; +import { getFunctions } from "./getFunctions"; + +export async function ensureFunctions({ + client, + worldDeploy, + functions, +}: { + client: Client; + worldDeploy: WorldDeploy; + functions: WorldFunction[]; +}): Promise { + const worldFunctions = await getFunctions({ client, worldDeploy }); + const worldSelectorToFunction = Object.fromEntries(worldFunctions.map((func) => [func.selector, func])); + + const toSkip = functions.filter((func) => worldSelectorToFunction[func.selector]); + const toAdd = functions.filter((func) => !toSkip.includes(func)); + + if (toSkip.length) { + debug("functions already registered:", toSkip.map((func) => func.signature).join(", ")); + const wrongSystem = toSkip.filter((func) => func.systemId !== worldSelectorToFunction[func.selector]?.systemId); + if (wrongSystem.length) { + console.warn( + "found", + wrongSystem.length, + "functions already registered but pointing at a different system ID:", + wrongSystem.map((func) => func.signature).join(", ") + ); + } + } + + if (!toAdd.length) return []; + + debug("registering functions:", toAdd.map((func) => func.signature).join(", ")); + + return Promise.all( + toAdd.map((func) => { + const { namespace } = hexToResource(func.systemId); + if (namespace === "") { + return writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall + functionName: "registerRootFunctionSelector", + // TODO: why do we have to specify the selector here? + args: [func.systemId, func.systemFunctionSignature, func.systemFunctionSelector], + }); + } + return writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall + functionName: "registerFunctionSelector", + args: [func.systemId, func.systemFunctionSignature], + }); + }) + ); +} diff --git a/packages/cli/src/deploy/ensureSystemFunctions.ts b/packages/cli/src/deploy/ensureSystemFunctions.ts deleted file mode 100644 index 24e9d99f33..0000000000 --- a/packages/cli/src/deploy/ensureSystemFunctions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Client, Transport, Chain, Account, Hex, getFunctionSelector } from "viem"; -import { writeContract } from "@latticexyz/common"; -import { System, WorldDeploy, worldAbi } from "./common"; -import { debug } from "./debug"; -import { getFunctions } from "./getFunctions"; - -export async function ensureSystemFunctions({ - client, - worldDeploy, - system, -}: { - client: Client; - worldDeploy: WorldDeploy; - system: System; -}): Promise { - const worldFunctions = await getFunctions({ client, worldDeploy }); - const existingSystemFunctions = worldFunctions.filter((func) => func.systemId === system.systemId); - - // TODO: figure out which system functions already exist (and are or aren't pointing to the right system ID) and which system functions to register - - debug("registering functions", system.functions.map((func) => func.signature).join(", ")); - - if (system.namespace === "") { - return await Promise.all( - system.functions.map(({ systemFunctionSignature, systemFunctionSelector }) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall - functionName: "registerRootFunctionSelector", - // TODO: why do we have to specify the selector here? - args: [system.systemId, systemFunctionSignature, systemFunctionSelector], - }) - ) - ); - } - - return await Promise.all( - system.functions.map(({ systemFunctionSignature }) => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall - functionName: "registerFunctionSelector", - args: [system.systemId, systemFunctionSignature], - }) - ) - ); -} diff --git a/packages/cli/src/deploy/ensureSystems.ts b/packages/cli/src/deploy/ensureSystems.ts index e764938c30..de17f0f0a6 100644 --- a/packages/cli/src/deploy/ensureSystems.ts +++ b/packages/cli/src/deploy/ensureSystems.ts @@ -5,7 +5,6 @@ import { ensureContract } from "./ensureContract"; import { debug } from "./debug"; import { resourceLabel } from "./resourceLabel"; import { getSystems } from "./getSystems"; -import { ensureSystemFunctions } from "./ensureSystemFunctions"; export async function ensureSystems({ client, @@ -70,12 +69,5 @@ export async function ensureSystems({ ) ); - // then register system functions - const functionTxs = await Promise.all( - missing.map((system) => ensureSystemFunctions({ client, worldDeploy, system })) - ); - - const txs = [...contractTxs, ...registerTxs, ...functionTxs]; - - return (await Promise.all(txs)).flat(); + return (await Promise.all([...contractTxs, ...registerTxs])).flat(); } diff --git a/packages/cli/src/deploy/getFunctions.ts b/packages/cli/src/deploy/getFunctions.ts index 0dabec4ee5..76cadbb08e 100644 --- a/packages/cli/src/deploy/getFunctions.ts +++ b/packages/cli/src/deploy/getFunctions.ts @@ -1,4 +1,4 @@ -import { Client, Hex, getFunctionSelector, parseAbiItem } from "viem"; +import { Client, getFunctionSelector, parseAbiItem } from "viem"; import { WorldDeploy, WorldFunction, worldTables } from "./common"; import { debug } from "./debug"; import { storeSetRecordEvent } from "@latticexyz/store"; @@ -7,8 +7,6 @@ import { decodeValueArgs } from "@latticexyz/protocol-parser"; import { getTableValue } from "./getTableValue"; import { hexToResource } from "@latticexyz/common"; -console.log(worldTables); - export async function getFunctions({ client, worldDeploy, @@ -20,7 +18,7 @@ export async function getFunctions({ debug("looking up function signatures for", worldDeploy.address); const logs = await getLogs(client, { strict: true, - fromBlock: worldDeploy.blockNumber, + fromBlock: worldDeploy.fromBlock, address: worldDeploy.address, event: parseAbiItem(storeSetRecordEvent), args: { tableId: worldTables.world_FunctionSignatures.tableId }, diff --git a/packages/cli/src/deploy/getResourceIds.ts b/packages/cli/src/deploy/getResourceIds.ts index f2ea20b714..62062ab17d 100644 --- a/packages/cli/src/deploy/getResourceIds.ts +++ b/packages/cli/src/deploy/getResourceIds.ts @@ -6,7 +6,7 @@ import { debug } from "./debug"; export async function getResourceIds({ client, - worldDeploy: { address, blockNumber }, + worldDeploy, }: { client: Client; worldDeploy: WorldDeploy; @@ -14,17 +14,17 @@ export async function getResourceIds({ // This assumes we only use `ResourceIds._setExists(true)`, which is true as of this writing. // TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state - debug("looking up resource IDs for", address); + debug("looking up resource IDs for", worldDeploy.address); const logs = await getLogs(client, { strict: true, - fromBlock: blockNumber, - address, + address: worldDeploy.address, + fromBlock: worldDeploy.fromBlock, event: parseAbiItem(storeSpliceStaticDataEvent), args: { tableId: storeTables.store_ResourceIds.tableId }, }); const resourceIds = logs.map((log) => log.args.keyTuple[0]); - debug("found", resourceIds.length, "resource IDs for", address); + debug("found", resourceIds.length, "resource IDs for", worldDeploy.address); return resourceIds; } diff --git a/packages/cli/src/deploy/getTables.ts b/packages/cli/src/deploy/getTables.ts index 5401385cf6..4571e0856a 100644 --- a/packages/cli/src/deploy/getTables.ts +++ b/packages/cli/src/deploy/getTables.ts @@ -21,7 +21,7 @@ export async function getTables({ debug("looking up tables for", worldDeploy.address); const logs = await getLogs(client, { strict: true, - fromBlock: worldDeploy.blockNumber, + fromBlock: worldDeploy.fromBlock, address: worldDeploy.address, event: parseAbiItem(storeSetRecordEvent), args: { tableId: storeTables.store_Tables.tableId }, diff --git a/packages/cli/src/deploy/getWorldDeploy.ts b/packages/cli/src/deploy/getWorldDeploy.ts index 846683e068..0353817478 100644 --- a/packages/cli/src/deploy/getWorldDeploy.ts +++ b/packages/cli/src/deploy/getWorldDeploy.ts @@ -1,5 +1,5 @@ import { Client, Address, getAddress, parseAbi } from "viem"; -import { getLogs } from "viem/actions"; +import { getBlockNumber, getLogs } from "viem/actions"; import { WorldDeploy, helloStoreEvent, helloWorldEvent } from "./common"; import { debug } from "./debug"; import { logsToWorldDeploy } from "./logsToWorldDeploy"; @@ -15,16 +15,23 @@ export async function getWorldDeploy(client: Client, worldAddress: Address): Pro } debug("looking up world deploy for", address); + + const toBlock = await getBlockNumber(client); const logs = await getLogs(client, { strict: true, address, events: parseAbi([helloWorldEvent, helloStoreEvent]), fromBlock: "earliest", + toBlock, }); - deploy = logsToWorldDeploy(logs); + deploy = { + ...logsToWorldDeploy(logs), + toBlock, + }; deploys.set(address, deploy); - debug("found world deploy for", address, "at block", deploy.blockNumber); + + debug("found world deploy for", address, "at block", deploy.fromBlock); return deploy; } diff --git a/packages/cli/src/deploy/logsToWorldDeploy.ts b/packages/cli/src/deploy/logsToWorldDeploy.ts index 218623fbdc..834be7c57b 100644 --- a/packages/cli/src/deploy/logsToWorldDeploy.ts +++ b/packages/cli/src/deploy/logsToWorldDeploy.ts @@ -2,7 +2,7 @@ import { AbiEventSignatureNotFoundError, Log, decodeEventLog, hexToString, parse import { WorldDeploy, helloStoreEvent, helloWorldEvent } from "./common"; import { isDefined } from "@latticexyz/common/utils"; -export function logsToWorldDeploy(logs: Log[]): WorldDeploy { +export function logsToWorldDeploy(logs: Log[]): Omit { const deployLogs = logs .map((log) => { try { @@ -25,11 +25,11 @@ export function logsToWorldDeploy(logs: Log[]): WorldDepl .filter(isDefined); // TODO: should this test for/validate that only one of each of these events is present? and that the address/block number don't change between each? - const { address, blockNumber, worldVersion, storeVersion } = deployLogs.reduce>( + const { address, fromBlock, worldVersion, storeVersion } = deployLogs.reduce>( (deploy, log) => ({ ...deploy, address: log.address, - blockNumber: log.blockNumber, + fromBlock: log.blockNumber, ...(log.eventName === "HelloWorld" ? { worldVersion: hexToString(trim(log.args.worldVersion, { dir: "right" })) } : null), @@ -41,9 +41,9 @@ export function logsToWorldDeploy(logs: Log[]): WorldDepl ); if (address == null) throw new Error("could not find world address"); - if (blockNumber == null) throw new Error("could not find world deploy block number"); + if (fromBlock == null) throw new Error("could not find world deploy block number"); if (worldVersion == null) throw new Error("could not find world version"); if (storeVersion == null) throw new Error("could not find store version"); - return { address, blockNumber, worldVersion, storeVersion }; + return { address, fromBlock, worldVersion, storeVersion }; }