From d84219379198b5c3753e53b233e70a15392bd2f3 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:15:27 -0600 Subject: [PATCH 01/16] world: add ts/actions/delegation.ts --- packages/world/package.json | 4 ++ packages/world/ts/actions/delegation.ts | 56 +++++++++++++++++++++++ packages/world/ts/actions/index.ts | 1 + packages/world/ts/encodeSystemCall.ts | 8 ++-- packages/world/ts/encodeSystemCallFrom.ts | 8 ++-- packages/world/tsup.config.ts | 2 +- 6 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 packages/world/ts/actions/delegation.ts create mode 100644 packages/world/ts/actions/index.ts diff --git a/packages/world/package.json b/packages/world/package.json index dd3d3369e9..25a224e214 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -11,6 +11,7 @@ "type": "module", "exports": { ".": "./dist/ts/index.js", + "./actions": "./dist/ts/actions/index.js", "./mud.config": "./dist/mud.config.js", "./register": "./dist/ts/register/index.js", "./node": "./dist/ts/node/index.js", @@ -21,6 +22,9 @@ "index": [ "./ts/index.ts" ], + "actions": [ + "./ts/actions/index.ts" + ], "register": [ "./ts/register/index.ts" ], diff --git a/packages/world/ts/actions/delegation.ts b/packages/world/ts/actions/delegation.ts new file mode 100644 index 0000000000..955d210186 --- /dev/null +++ b/packages/world/ts/actions/delegation.ts @@ -0,0 +1,56 @@ +import type { WalletClient, Transport, Chain, Account, Hex, WalletActions, WriteContractReturnType } from "viem"; +import { getAction, getAbiItem, formatAbiItem, toFunctionSelector, type GetAbiItemParameters } from "viem/utils"; +import { writeContract } from "viem/actions"; +import { encodeSystemCallFrom, type SystemCallFrom } from "../encodeSystemCallFrom"; + +type DelegationParameters = { + worldAddress: Hex; + delegatorAddress: Hex; + getSystemId: (functionSelector: Hex) => Hex; +}; + +// By extending clients with this function after delegation, the delegation automatically applies +// to the World contract writes, meaning these calls are made on behalf of the delegator. +export function delegation({ + worldAddress, + delegatorAddress, + getSystemId, +}: DelegationParameters): ( + client: WalletClient, +) => Pick, "writeContract"> { + return (client) => ({ + // Applies to: `client.writeContract`, `getContract(client, ...).write` + writeContract: (originalArgs): Promise => { + // Skip if the contract isn't the World. + if (originalArgs.address !== worldAddress) { + return getAction(client, writeContract, "writeContract")(originalArgs); + } + + // Ensured not to be `undefined` because of the `writeContract` args type. + const functionAbiItem = getAbiItem({ + abi: originalArgs.abi, + name: originalArgs.functionName, + args: originalArgs.args, + } as unknown as GetAbiItemParameters)!; + + // `callFrom` requires `systemId`. + const functionSelector = toFunctionSelector(formatAbiItem(functionAbiItem)); + const systemId = getSystemId(functionSelector); + + // Construct args for `callFrom`. + const callFromArgs: typeof originalArgs = { + ...originalArgs, + functionName: "callFrom", + args: encodeSystemCallFrom({ + abi: originalArgs.abi, + from: delegatorAddress, + systemId, + functionName: originalArgs.functionName, + args: originalArgs.args, + } as unknown as SystemCallFrom), + }; + + return getAction(client, writeContract, "writeContract")(callFromArgs); + }, + }); +} diff --git a/packages/world/ts/actions/index.ts b/packages/world/ts/actions/index.ts new file mode 100644 index 0000000000..ede50d95b9 --- /dev/null +++ b/packages/world/ts/actions/index.ts @@ -0,0 +1 @@ +export * from "./delegation"; diff --git a/packages/world/ts/encodeSystemCall.ts b/packages/world/ts/encodeSystemCall.ts index a6a36e735b..5ba6eb74f8 100644 --- a/packages/world/ts/encodeSystemCall.ts +++ b/packages/world/ts/encodeSystemCall.ts @@ -2,10 +2,10 @@ import { Abi, EncodeFunctionDataParameters, Hex, encodeFunctionData, type Contra import type { AbiParametersToPrimitiveTypes, ExtractAbiFunction } from "abitype"; import IWorldCallAbi from "../out/IWorldKernel.sol/IWorldCall.abi.json"; -export type SystemCall> = EncodeFunctionDataParameters< - abi, - functionName -> & { +export type SystemCall< + abi extends Abi = Abi, + functionName extends ContractFunctionName = ContractFunctionName, +> = EncodeFunctionDataParameters & { readonly systemId: Hex; }; diff --git a/packages/world/ts/encodeSystemCallFrom.ts b/packages/world/ts/encodeSystemCallFrom.ts index 691675684e..b6d01a2fed 100644 --- a/packages/world/ts/encodeSystemCallFrom.ts +++ b/packages/world/ts/encodeSystemCallFrom.ts @@ -3,10 +3,10 @@ import type { AbiParametersToPrimitiveTypes, ExtractAbiFunction } from "abitype" import IWorldCallAbi from "../out/IWorldKernel.sol/IWorldCall.abi.json"; import { SystemCall } from "./encodeSystemCall"; -export type SystemCallFrom> = SystemCall< - abi, - functionName -> & { +export type SystemCallFrom< + abi extends Abi = Abi, + functionName extends ContractFunctionName = ContractFunctionName, +> = SystemCall & { readonly from: Address; }; diff --git a/packages/world/tsup.config.ts b/packages/world/tsup.config.ts index d7d1d07098..bd9651164a 100644 --- a/packages/world/tsup.config.ts +++ b/packages/world/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entry: ["mud.config.ts", "ts/index.ts", "ts/register/index.ts", "ts/node/index.ts"], + entry: ["mud.config.ts", "ts/index.ts", "ts/actions/index.ts", "ts/register/index.ts", "ts/node/index.ts"], target: "esnext", format: ["esm"], dts: true, From 8b414612936e0ecbd6cfdfe61f07aeab926541c6 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:53:06 -0600 Subject: [PATCH 02/16] add changeset --- .changeset/nervous-nails-notice.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .changeset/nervous-nails-notice.md diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md new file mode 100644 index 0000000000..3049da7c4e --- /dev/null +++ b/.changeset/nervous-nails-notice.md @@ -0,0 +1,18 @@ +--- +"@latticexyz/world": minor +--- + +Added viem custom client actions for delegation. By extending viem clients with this function after delegation, the delegation is automatically applied to World contract writes. Internally, it transforms the `writeContract` arguments to incorporate `callFrom`. + +Usage example: + +```ts +walletClient.extend( + delegation({ + worldAddress, + delegatorAddress, + getSystemId: (functionSelector) => + network.useStore.getState().getValue(network.tables.FunctionSelectors, { functionSelector })!.systemId, + }), +); +``` From a49f45ccd8e8ecbb8b8f70d563a16f8854c119a8 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:57:15 -0600 Subject: [PATCH 03/16] fix changeset --- .changeset/nervous-nails-notice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index 3049da7c4e..9e187dba92 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -12,7 +12,7 @@ walletClient.extend( worldAddress, delegatorAddress, getSystemId: (functionSelector) => - network.useStore.getState().getValue(network.tables.FunctionSelectors, { functionSelector })!.systemId, + useStore.getState().getValue(tables.FunctionSelectors, { functionSelector })!.systemId, }), ); ``` From 33ef129c5c8c930fd3a466b396373cd3f0cb6f71 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:26:04 -0600 Subject: [PATCH 04/16] fix namespaced system call issue and refactor --- .changeset/nervous-nails-notice.md | 10 ++- .../dev-tools/src/actions/WriteSummary.tsx | 24 +++++-- packages/world/ts/actions/callFrom.ts | 72 +++++++++++++++++++ packages/world/ts/actions/delegation.ts | 56 --------------- packages/world/ts/actions/index.ts | 2 +- packages/world/ts/encodeSystemCall.ts | 8 +-- packages/world/ts/encodeSystemCallFrom.ts | 8 +-- 7 files changed, 108 insertions(+), 72 deletions(-) create mode 100644 packages/world/ts/actions/callFrom.ts delete mode 100644 packages/world/ts/actions/delegation.ts diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index 9e187dba92..664e897fab 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -8,11 +8,15 @@ Usage example: ```ts walletClient.extend( - delegation({ + callFrom({ worldAddress, delegatorAddress, - getSystemId: (functionSelector) => - useStore.getState().getValue(tables.FunctionSelectors, { functionSelector })!.systemId, + worldFunctionToSystemFunction: async (worldFunctionSelector) => { + const systemFunction = useStore + .getState() + .getValue(tables.FunctionSelectors, { functionSelector: worldFunctionSelector })!; + return { systemId: systemFunction.systemId, systemFunctionSelector: systemFunction.systemFunctionSelector }; + }, }), ); ``` diff --git a/packages/dev-tools/src/actions/WriteSummary.tsx b/packages/dev-tools/src/actions/WriteSummary.tsx index 8221805752..20b19610fd 100644 --- a/packages/dev-tools/src/actions/WriteSummary.tsx +++ b/packages/dev-tools/src/actions/WriteSummary.tsx @@ -1,4 +1,10 @@ -import { decodeEventLog, AbiEventSignatureNotFoundError, decodeFunctionData, Hex } from "viem"; +import { + decodeEventLog, + AbiEventSignatureNotFoundError, + decodeFunctionData, + Hex, + AbiFunctionSignatureNotFoundError, +} from "viem"; import { twMerge } from "tailwind-merge"; import { isDefined } from "@latticexyz/common/utils"; import { PendingIcon } from "../icons/PendingIcon"; @@ -62,9 +68,19 @@ export function WriteSummary({ write }: Props) { const functionSelectorAndArgs: Hex = write.request?.args?.length ? (write.request.args[write.request.args.length - 1] as Hex) : `0x`; - const functionData = decodeFunctionData({ abi: worldAbi, data: functionSelectorAndArgs }); - functionName = functionData.functionName; - functionArgs = functionData.args; + + // TODO: Since `functionSelectorAndArgs` corresponds to a System's function, decoding it using + // the World ABI may not always be successful. For instance, namespaced system calls could + // result in an error. + try { + const functionData = decodeFunctionData({ abi: worldAbi, data: functionSelectorAndArgs }); + functionName = functionData.functionName; + functionArgs = functionData.args; + } catch (error) { + if (!(error instanceof AbiFunctionSignatureNotFoundError)) { + throw error; + } + } } return ( diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts new file mode 100644 index 0000000000..0976a40525 --- /dev/null +++ b/packages/world/ts/actions/callFrom.ts @@ -0,0 +1,72 @@ +import { + slice, + concat, + type WalletClient, + type Transport, + type Chain, + type Account, + type Hex, + type WalletActions, + type WriteContractReturnType, + type EncodeFunctionDataParameters, +} from "viem"; +import { getAction, encodeFunctionData } from "viem/utils"; +import { writeContract } from "viem/actions"; + +type CallFromParameters = { + worldAddress: Hex; + delegatorAddress: Hex; + worldFunctionToSystemFunction: ( + worldFunctionSelector: Hex, + ) => Promise<{ systemId: Hex; systemFunctionSelector: Hex }>; +}; + +// By extending viem clients with this function after delegation, the delegation is automatically +// applied to the World contract writes, meaning these writes are made on behalf of the delegator. +export function callFrom({ + worldAddress, + delegatorAddress, + worldFunctionToSystemFunction, +}: CallFromParameters): ( + client: WalletClient, +) => Pick, "writeContract"> { + return (client) => ({ + // Applies to: `client.writeContract`, `getContract(client, ...).write` + writeContract: async (writeArgs): Promise => { + // Skip if the contract isn't the World. + if (writeArgs.address !== worldAddress) { + return getAction(client, writeContract, "writeContract")(writeArgs); + } + + // Encode the World's calldata (which includes the World's function selector). + const worldCalldata = encodeFunctionData({ + abi: writeArgs.abi, + functionName: writeArgs.functionName, + args: writeArgs.args, + } as unknown as EncodeFunctionDataParameters); + + // The first 4 bytes of calldata represent the function selector. + const worldFunctionSelector = slice(worldCalldata, 0, 4); + + // Get the systemId and System's function selector. + const { systemId, systemFunctionSelector } = await worldFunctionToSystemFunction(worldFunctionSelector); + + // Construct the System's calldata. + // If there's no args, use the System's function selector as calldata. + // Otherwise, use the World's calldata, replacing the World's function selector with the System's. + const systemCalldata = + worldCalldata === worldFunctionSelector + ? systemFunctionSelector + : concat([systemFunctionSelector, slice(worldCalldata, 4)]); + + // Construct args for `callFrom`. + const callFromArgs: typeof writeArgs = { + ...writeArgs, + functionName: "callFrom", + args: [delegatorAddress, systemId, systemCalldata], + }; + + return getAction(client, writeContract, "writeContract")(callFromArgs); + }, + }); +} diff --git a/packages/world/ts/actions/delegation.ts b/packages/world/ts/actions/delegation.ts deleted file mode 100644 index 955d210186..0000000000 --- a/packages/world/ts/actions/delegation.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { WalletClient, Transport, Chain, Account, Hex, WalletActions, WriteContractReturnType } from "viem"; -import { getAction, getAbiItem, formatAbiItem, toFunctionSelector, type GetAbiItemParameters } from "viem/utils"; -import { writeContract } from "viem/actions"; -import { encodeSystemCallFrom, type SystemCallFrom } from "../encodeSystemCallFrom"; - -type DelegationParameters = { - worldAddress: Hex; - delegatorAddress: Hex; - getSystemId: (functionSelector: Hex) => Hex; -}; - -// By extending clients with this function after delegation, the delegation automatically applies -// to the World contract writes, meaning these calls are made on behalf of the delegator. -export function delegation({ - worldAddress, - delegatorAddress, - getSystemId, -}: DelegationParameters): ( - client: WalletClient, -) => Pick, "writeContract"> { - return (client) => ({ - // Applies to: `client.writeContract`, `getContract(client, ...).write` - writeContract: (originalArgs): Promise => { - // Skip if the contract isn't the World. - if (originalArgs.address !== worldAddress) { - return getAction(client, writeContract, "writeContract")(originalArgs); - } - - // Ensured not to be `undefined` because of the `writeContract` args type. - const functionAbiItem = getAbiItem({ - abi: originalArgs.abi, - name: originalArgs.functionName, - args: originalArgs.args, - } as unknown as GetAbiItemParameters)!; - - // `callFrom` requires `systemId`. - const functionSelector = toFunctionSelector(formatAbiItem(functionAbiItem)); - const systemId = getSystemId(functionSelector); - - // Construct args for `callFrom`. - const callFromArgs: typeof originalArgs = { - ...originalArgs, - functionName: "callFrom", - args: encodeSystemCallFrom({ - abi: originalArgs.abi, - from: delegatorAddress, - systemId, - functionName: originalArgs.functionName, - args: originalArgs.args, - } as unknown as SystemCallFrom), - }; - - return getAction(client, writeContract, "writeContract")(callFromArgs); - }, - }); -} diff --git a/packages/world/ts/actions/index.ts b/packages/world/ts/actions/index.ts index ede50d95b9..d2f67cb6a5 100644 --- a/packages/world/ts/actions/index.ts +++ b/packages/world/ts/actions/index.ts @@ -1 +1 @@ -export * from "./delegation"; +export * from "./callFrom"; diff --git a/packages/world/ts/encodeSystemCall.ts b/packages/world/ts/encodeSystemCall.ts index 5ba6eb74f8..a6a36e735b 100644 --- a/packages/world/ts/encodeSystemCall.ts +++ b/packages/world/ts/encodeSystemCall.ts @@ -2,10 +2,10 @@ import { Abi, EncodeFunctionDataParameters, Hex, encodeFunctionData, type Contra import type { AbiParametersToPrimitiveTypes, ExtractAbiFunction } from "abitype"; import IWorldCallAbi from "../out/IWorldKernel.sol/IWorldCall.abi.json"; -export type SystemCall< - abi extends Abi = Abi, - functionName extends ContractFunctionName = ContractFunctionName, -> = EncodeFunctionDataParameters & { +export type SystemCall> = EncodeFunctionDataParameters< + abi, + functionName +> & { readonly systemId: Hex; }; diff --git a/packages/world/ts/encodeSystemCallFrom.ts b/packages/world/ts/encodeSystemCallFrom.ts index b6d01a2fed..691675684e 100644 --- a/packages/world/ts/encodeSystemCallFrom.ts +++ b/packages/world/ts/encodeSystemCallFrom.ts @@ -3,10 +3,10 @@ import type { AbiParametersToPrimitiveTypes, ExtractAbiFunction } from "abitype" import IWorldCallAbi from "../out/IWorldKernel.sol/IWorldCall.abi.json"; import { SystemCall } from "./encodeSystemCall"; -export type SystemCallFrom< - abi extends Abi = Abi, - functionName extends ContractFunctionName = ContractFunctionName, -> = SystemCall & { +export type SystemCallFrom> = SystemCall< + abi, + functionName +> & { readonly from: Address; }; From 89d9530c5760f5c242e08f511a848f76e577dd34 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:02:07 -0600 Subject: [PATCH 05/16] make arg optional, retrieve system func info from contract --- packages/world/ts/actions/callFrom.ts | 70 +++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index 0976a40525..ccde1a3929 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -1,6 +1,7 @@ import { slice, concat, + pad, type WalletClient, type Transport, type Chain, @@ -9,24 +10,43 @@ import { type WalletActions, type WriteContractReturnType, type EncodeFunctionDataParameters, + type PublicClient, } from "viem"; import { getAction, encodeFunctionData } from "viem/utils"; import { writeContract } from "viem/actions"; +import { resourceToHex } from "@latticexyz/common"; +import worldConfig from "../../mud.config"; +import IStoreReadAbi from "../../out/IStoreRead.sol/IStoreRead.abi.json"; -type CallFromParameters = { +type CallFromParameters = CallFromFunctionParameters | CallFromClientParameters; + +type CallFromBaseParameters = { worldAddress: Hex; delegatorAddress: Hex; - worldFunctionToSystemFunction: ( - worldFunctionSelector: Hex, - ) => Promise<{ systemId: Hex; systemFunctionSelector: Hex }>; }; +type CallFromFunctionParameters = CallFromBaseParameters & { + worldFunctionToSystemFunction: (worldFunctionSelector: Hex) => Promise; + publicClient?: never; +}; + +type CallFromClientParameters = CallFromBaseParameters & { + worldFunctionToSystemFunction?: never; + publicClient: PublicClient; +}; + +type SystemFunction = { systemId: Hex; systemFunctionSelector: Hex }; + // By extending viem clients with this function after delegation, the delegation is automatically // applied to the World contract writes, meaning these writes are made on behalf of the delegator. +// +// Accepts either `worldFunctionToSystemFunction` or `publicClient` as an argument. +// If `publicClient` is provided, a read request to the World contract will occur. export function callFrom({ worldAddress, delegatorAddress, worldFunctionToSystemFunction, + publicClient, }: CallFromParameters): ( client: WalletClient, ) => Pick, "writeContract"> { @@ -49,7 +69,9 @@ export function callFrom({ const worldFunctionSelector = slice(worldCalldata, 0, 4); // Get the systemId and System's function selector. - const { systemId, systemFunctionSelector } = await worldFunctionToSystemFunction(worldFunctionSelector); + const { systemId, systemFunctionSelector } = worldFunctionToSystemFunction + ? await worldFunctionToSystemFunction(worldFunctionSelector) + : await retrieveSystemFunction(publicClient, worldAddress, worldFunctionSelector); // Construct the System's calldata. // If there's no args, use the System's function selector as calldata. @@ -66,7 +88,45 @@ export function callFrom({ args: [delegatorAddress, systemId, systemCalldata], }; + // Call `writeContract` with the new args. return getAction(client, writeContract, "writeContract")(callFromArgs); }, }); } + +const functionSelectorsTableId = resourceToHex({ + type: worldConfig.tables.FunctionSelectors.offchainOnly ? "offchainTable" : "table", + namespace: worldConfig.namespace, + name: worldConfig.tables.FunctionSelectors.name, +}); + +const systemFunctionCache = new Map(); + +async function retrieveSystemFunction( + publicClient: PublicClient, + worldAddress: Hex, + worldFunctionSelector: Hex, +): Promise { + const cacheKey = concat([worldAddress, worldFunctionSelector]); + + // Skip the request if it has been called previously. + const cached = systemFunctionCache.get(cacheKey); + if (cached) return cached; + + // Refer to the corresponding Solidity code to understand the data structure. + const [staticData] = await publicClient.readContract({ + address: worldAddress, + abi: IStoreReadAbi, + functionName: "getRecord", + args: [functionSelectorsTableId, [pad(worldFunctionSelector, { dir: "right", size: 32 })]], + }); + + const systemFunction: SystemFunction = { + systemId: slice(staticData, 0, 32), + systemFunctionSelector: slice(staticData, 32, 36), + }; + + systemFunctionCache.set(cacheKey, systemFunction); + + return systemFunction; +} From 7c6adb8ac686433820ebe025143241e794003f37 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:10:04 -0600 Subject: [PATCH 06/16] update comment --- .changeset/nervous-nails-notice.md | 13 +++++++------ packages/dev-tools/src/actions/WriteSummary.tsx | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index 664e897fab..d0a55d55a2 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -11,12 +11,13 @@ walletClient.extend( callFrom({ worldAddress, delegatorAddress, - worldFunctionToSystemFunction: async (worldFunctionSelector) => { - const systemFunction = useStore - .getState() - .getValue(tables.FunctionSelectors, { functionSelector: worldFunctionSelector })!; - return { systemId: systemFunction.systemId, systemFunctionSelector: systemFunction.systemFunctionSelector }; - }, + publicClient, // Instead of passing `publicClient`, you can pass a function like below for more control. + // worldFunctionToSystemFunction: async (worldFunctionSelector) => { + // const systemFunction = useStore + // .getState() + // .getValue(tables.FunctionSelectors, { functionSelector: worldFunctionSelector })!; + // return { systemId: systemFunction.systemId, systemFunctionSelector: systemFunction.systemFunctionSelector }; + // }, }), ); ``` diff --git a/packages/dev-tools/src/actions/WriteSummary.tsx b/packages/dev-tools/src/actions/WriteSummary.tsx index 20b19610fd..e00ecd7096 100644 --- a/packages/dev-tools/src/actions/WriteSummary.tsx +++ b/packages/dev-tools/src/actions/WriteSummary.tsx @@ -72,6 +72,7 @@ export function WriteSummary({ write }: Props) { // TODO: Since `functionSelectorAndArgs` corresponds to a System's function, decoding it using // the World ABI may not always be successful. For instance, namespaced system calls could // result in an error. + // See also https://github.com/latticexyz/mud/issues/2382 try { const functionData = decodeFunctionData({ abi: worldAbi, data: functionSelectorAndArgs }); functionName = functionData.functionName; From e100e70d573c98f7bdf308b89a3ee4f4d9bfbab9 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:27:56 -0600 Subject: [PATCH 07/16] no need to manually decode getRecord result --- packages/world/package.json | 1 + packages/world/ts/actions/callFrom.ts | 31 +++++++++++++++++---------- pnpm-lock.yaml | 3 +++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/world/package.json b/packages/world/package.json index 25a224e214..019eca38e8 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -52,6 +52,7 @@ "dependencies": { "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", + "@latticexyz/protocol-parser": "workspace:*", "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", "abitype": "1.0.0", diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index ccde1a3929..bb773e18db 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -1,7 +1,6 @@ import { slice, concat, - pad, type WalletClient, type Transport, type Chain, @@ -15,6 +14,8 @@ import { import { getAction, encodeFunctionData } from "viem/utils"; import { writeContract } from "viem/actions"; import { resourceToHex } from "@latticexyz/common"; +import { resolveUserTypes } from "@latticexyz/store"; +import { decodeValueArgs, encodeKey } from "@latticexyz/protocol-parser"; import worldConfig from "../../mud.config"; import IStoreReadAbi from "../../out/IStoreRead.sol/IStoreRead.abi.json"; @@ -94,11 +95,15 @@ export function callFrom({ }); } -const functionSelectorsTableId = resourceToHex({ - type: worldConfig.tables.FunctionSelectors.offchainOnly ? "offchainTable" : "table", - namespace: worldConfig.namespace, - name: worldConfig.tables.FunctionSelectors.name, -}); +const functionSelectorsTable = { + tableId: resourceToHex({ + type: worldConfig.tables.FunctionSelectors.offchainOnly ? "offchainTable" : "table", + namespace: worldConfig.namespace, + name: worldConfig.tables.FunctionSelectors.name, + }), + keySchema: resolveUserTypes(worldConfig.tables.FunctionSelectors.keySchema, worldConfig.userTypes), + valueSchema: resolveUserTypes(worldConfig.tables.FunctionSelectors.valueSchema, worldConfig.userTypes), +}; const systemFunctionCache = new Map(); @@ -113,17 +118,21 @@ async function retrieveSystemFunction( const cached = systemFunctionCache.get(cacheKey); if (cached) return cached; - // Refer to the corresponding Solidity code to understand the data structure. - const [staticData] = await publicClient.readContract({ + const [staticData, encodedLengths, dynamicData] = await publicClient.readContract({ address: worldAddress, abi: IStoreReadAbi, functionName: "getRecord", - args: [functionSelectorsTableId, [pad(worldFunctionSelector, { dir: "right", size: 32 })]], + args: [ + functionSelectorsTable.tableId, + encodeKey(functionSelectorsTable.keySchema, { functionSelector: worldFunctionSelector }), + ], }); + const decoded = decodeValueArgs(functionSelectorsTable.valueSchema, { staticData, encodedLengths, dynamicData }); + const systemFunction: SystemFunction = { - systemId: slice(staticData, 0, 32), - systemFunctionSelector: slice(staticData, 32, 36), + systemId: decoded.systemId, + systemFunctionSelector: decoded.systemFunctionSelector, }; systemFunctionCache.set(cacheKey, systemFunction); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98e9d8303f..f4a6c5c335 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1000,6 +1000,9 @@ importers: '@latticexyz/config': specifier: workspace:* version: link:../config + '@latticexyz/protocol-parser': + specifier: workspace:* + version: link:../protocol-parser '@latticexyz/schema-type': specifier: workspace:* version: link:../schema-type From 46cfec0df2e78a401cc57f5c9ba9e6b8e206c85c Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:28:59 -0600 Subject: [PATCH 08/16] follow rename https://github.com/latticexyz/mud/pull/2391 --- .changeset/nervous-nails-notice.md | 2 +- packages/world/ts/actions/callFrom.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index d0a55d55a2..b3472998cd 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -15,7 +15,7 @@ walletClient.extend( // worldFunctionToSystemFunction: async (worldFunctionSelector) => { // const systemFunction = useStore // .getState() - // .getValue(tables.FunctionSelectors, { functionSelector: worldFunctionSelector })!; + // .getValue(tables.FunctionSelectors, { worldFunctionSelector })!; // return { systemId: systemFunction.systemId, systemFunctionSelector: systemFunction.systemFunctionSelector }; // }, }), diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index bb773e18db..009a16b7d5 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -122,10 +122,7 @@ async function retrieveSystemFunction( address: worldAddress, abi: IStoreReadAbi, functionName: "getRecord", - args: [ - functionSelectorsTable.tableId, - encodeKey(functionSelectorsTable.keySchema, { functionSelector: worldFunctionSelector }), - ], + args: [functionSelectorsTable.tableId, encodeKey(functionSelectorsTable.keySchema, { worldFunctionSelector })], }); const decoded = decodeValueArgs(functionSelectorsTable.valueSchema, { staticData, encodedLengths, dynamicData }); From 35af4ede7ffacf00d53d33d850442beee7098320 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Fri, 8 Mar 2024 22:59:52 -0600 Subject: [PATCH 09/16] remove dev-tools diff as it's addressed in #2399 --- .../dev-tools/src/actions/WriteSummary.tsx | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/packages/dev-tools/src/actions/WriteSummary.tsx b/packages/dev-tools/src/actions/WriteSummary.tsx index e00ecd7096..8221805752 100644 --- a/packages/dev-tools/src/actions/WriteSummary.tsx +++ b/packages/dev-tools/src/actions/WriteSummary.tsx @@ -1,10 +1,4 @@ -import { - decodeEventLog, - AbiEventSignatureNotFoundError, - decodeFunctionData, - Hex, - AbiFunctionSignatureNotFoundError, -} from "viem"; +import { decodeEventLog, AbiEventSignatureNotFoundError, decodeFunctionData, Hex } from "viem"; import { twMerge } from "tailwind-merge"; import { isDefined } from "@latticexyz/common/utils"; import { PendingIcon } from "../icons/PendingIcon"; @@ -68,20 +62,9 @@ export function WriteSummary({ write }: Props) { const functionSelectorAndArgs: Hex = write.request?.args?.length ? (write.request.args[write.request.args.length - 1] as Hex) : `0x`; - - // TODO: Since `functionSelectorAndArgs` corresponds to a System's function, decoding it using - // the World ABI may not always be successful. For instance, namespaced system calls could - // result in an error. - // See also https://github.com/latticexyz/mud/issues/2382 - try { - const functionData = decodeFunctionData({ abi: worldAbi, data: functionSelectorAndArgs }); - functionName = functionData.functionName; - functionArgs = functionData.args; - } catch (error) { - if (!(error instanceof AbiFunctionSignatureNotFoundError)) { - throw error; - } - } + const functionData = decodeFunctionData({ abi: worldAbi, data: functionSelectorAndArgs }); + functionName = functionData.functionName; + functionArgs = functionData.args; } return ( From aa7b88176da11e70ad827ecd410c31b9b0d0a963 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Sat, 23 Mar 2024 11:01:59 -0500 Subject: [PATCH 10/16] update to align with tsup config change in main --- packages/world/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/world/package.json b/packages/world/package.json index 390a30817a..d169661e32 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -11,7 +11,7 @@ "type": "module", "exports": { ".": "./dist/index.js", - "./actions": "./dist/ts/actions/index.js", + "./actions": "./dist/actions.js", "./internal": "./dist/internal.js", "./mud.config": "./dist/mud.config.js", "./config/v2": "./dist/config/v2.js", From 762f96c310e9fc2c6ea7b64a6e8d7421a4fbba37 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Sat, 23 Mar 2024 13:15:14 -0500 Subject: [PATCH 11/16] use new config --- packages/world/ts/actions/callFrom.ts | 33 +++++++++++++++------------ 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index 009a16b7d5..fd3d6daf43 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -13,9 +13,8 @@ import { } from "viem"; import { getAction, encodeFunctionData } from "viem/utils"; import { writeContract } from "viem/actions"; -import { resourceToHex } from "@latticexyz/common"; -import { resolveUserTypes } from "@latticexyz/store"; -import { decodeValueArgs, encodeKey } from "@latticexyz/protocol-parser"; +import { mapObject } from "@latticexyz/common/utils"; +import { getKeySchema, getValueSchema, decodeValueArgs, encodeKey } from "@latticexyz/protocol-parser/internal"; import worldConfig from "../../mud.config"; import IStoreReadAbi from "../../out/IStoreRead.sol/IStoreRead.abi.json"; @@ -95,16 +94,6 @@ export function callFrom({ }); } -const functionSelectorsTable = { - tableId: resourceToHex({ - type: worldConfig.tables.FunctionSelectors.offchainOnly ? "offchainTable" : "table", - namespace: worldConfig.namespace, - name: worldConfig.tables.FunctionSelectors.name, - }), - keySchema: resolveUserTypes(worldConfig.tables.FunctionSelectors.keySchema, worldConfig.userTypes), - valueSchema: resolveUserTypes(worldConfig.tables.FunctionSelectors.valueSchema, worldConfig.userTypes), -}; - const systemFunctionCache = new Map(); async function retrieveSystemFunction( @@ -118,14 +107,28 @@ async function retrieveSystemFunction( const cached = systemFunctionCache.get(cacheKey); if (cached) return cached; + const table = worldConfig.tables.world__FunctionSelectors; + + const _keySchema = getKeySchema(table); + const keySchema = mapObject( + _keySchema, + ({ type }) => type, + ); + + const _valueSchema = getValueSchema(table); + const valueSchema = mapObject< + typeof _valueSchema, + { [K in keyof typeof _valueSchema]: (typeof _valueSchema)[K]["type"] } + >(_valueSchema, ({ type }) => type); + const [staticData, encodedLengths, dynamicData] = await publicClient.readContract({ address: worldAddress, abi: IStoreReadAbi, functionName: "getRecord", - args: [functionSelectorsTable.tableId, encodeKey(functionSelectorsTable.keySchema, { worldFunctionSelector })], + args: [table.tableId, encodeKey(keySchema, { worldFunctionSelector })], }); - const decoded = decodeValueArgs(functionSelectorsTable.valueSchema, { staticData, encodedLengths, dynamicData }); + const decoded = decodeValueArgs(valueSchema, { staticData, encodedLengths, dynamicData }); const systemFunction: SystemFunction = { systemId: decoded.systemId, From 36ea82bfa9cf94cb7db6521dd38c16067445d282 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:10:05 -0500 Subject: [PATCH 12/16] add caching for `worldFunctionToSystemFunction` results, in addition to contract call --- packages/world/ts/actions/callFrom.ts | 42 +++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index fd3d6daf43..bf701acd0a 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -42,19 +42,14 @@ type SystemFunction = { systemId: Hex; systemFunctionSelector: Hex }; // // Accepts either `worldFunctionToSystemFunction` or `publicClient` as an argument. // If `publicClient` is provided, a read request to the World contract will occur. -export function callFrom({ - worldAddress, - delegatorAddress, - worldFunctionToSystemFunction, - publicClient, -}: CallFromParameters): ( - client: WalletClient, -) => Pick, "writeContract"> { +export function callFrom( + params: CallFromParameters, +): (client: WalletClient) => Pick, "writeContract"> { return (client) => ({ // Applies to: `client.writeContract`, `getContract(client, ...).write` writeContract: async (writeArgs): Promise => { // Skip if the contract isn't the World. - if (writeArgs.address !== worldAddress) { + if (writeArgs.address !== params.worldAddress) { return getAction(client, writeContract, "writeContract")(writeArgs); } @@ -69,9 +64,7 @@ export function callFrom({ const worldFunctionSelector = slice(worldCalldata, 0, 4); // Get the systemId and System's function selector. - const { systemId, systemFunctionSelector } = worldFunctionToSystemFunction - ? await worldFunctionToSystemFunction(worldFunctionSelector) - : await retrieveSystemFunction(publicClient, worldAddress, worldFunctionSelector); + const { systemId, systemFunctionSelector } = await worldFunctionToSystemFunction(params, worldFunctionSelector); // Construct the System's calldata. // If there's no args, use the System's function selector as calldata. @@ -85,7 +78,7 @@ export function callFrom({ const callFromArgs: typeof writeArgs = { ...writeArgs, functionName: "callFrom", - args: [delegatorAddress, systemId, systemCalldata], + args: [params.delegatorAddress, systemId, systemCalldata], }; // Call `writeContract` with the new args. @@ -96,17 +89,30 @@ export function callFrom({ const systemFunctionCache = new Map(); -async function retrieveSystemFunction( - publicClient: PublicClient, - worldAddress: Hex, +async function worldFunctionToSystemFunction( + params: CallFromParameters, worldFunctionSelector: Hex, ): Promise { - const cacheKey = concat([worldAddress, worldFunctionSelector]); + const cacheKey = concat([params.worldAddress, worldFunctionSelector]); // Skip the request if it has been called previously. const cached = systemFunctionCache.get(cacheKey); if (cached) return cached; + const systemFunction = params.worldFunctionToSystemFunction + ? await params.worldFunctionToSystemFunction(worldFunctionSelector) + : await retrieveSystemFunctionFromContract(params.publicClient, params.worldAddress, worldFunctionSelector); + + systemFunctionCache.set(cacheKey, systemFunction); + + return systemFunction; +} + +async function retrieveSystemFunctionFromContract( + publicClient: PublicClient, + worldAddress: Hex, + worldFunctionSelector: Hex, +): Promise { const table = worldConfig.tables.world__FunctionSelectors; const _keySchema = getKeySchema(table); @@ -135,7 +141,5 @@ async function retrieveSystemFunction( systemFunctionSelector: decoded.systemFunctionSelector, }; - systemFunctionCache.set(cacheKey, systemFunction); - return systemFunction; } From 8431b65a608514db9f0ce929d945311183086887 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Sat, 23 Mar 2024 15:04:44 -0500 Subject: [PATCH 13/16] update comments --- .changeset/nervous-nails-notice.md | 4 ++-- packages/world/ts/actions/callFrom.ts | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index b3472998cd..a817da2777 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -2,7 +2,7 @@ "@latticexyz/world": minor --- -Added viem custom client actions for delegation. By extending viem clients with this function after delegation, the delegation is automatically applied to World contract writes. Internally, it transforms the `writeContract` arguments to incorporate `callFrom`. +Added viem custom client actions for delegation. By extending viem clients with this function after delegation, the delegation is automatically applied to World contract writes. This means that these writes are made on behalf of the delegator. Internally, it transforms the write arguments to use `callFrom`. Usage example: @@ -11,7 +11,7 @@ walletClient.extend( callFrom({ worldAddress, delegatorAddress, - publicClient, // Instead of passing `publicClient`, you can pass a function like below for more control. + publicClient, // Instead of using `publicClient`, you can pass a mapping function as shown below. This allows you to use your client store and avoid read requests. // worldFunctionToSystemFunction: async (worldFunctionSelector) => { // const systemFunction = useStore // .getState() diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index bf701acd0a..c3d82cb72c 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -18,18 +18,16 @@ import { getKeySchema, getValueSchema, decodeValueArgs, encodeKey } from "@latti import worldConfig from "../../mud.config"; import IStoreReadAbi from "../../out/IStoreRead.sol/IStoreRead.abi.json"; +// Accepts either `worldFunctionToSystemFunction` or `publicClient`, but not both. type CallFromParameters = CallFromFunctionParameters | CallFromClientParameters; - type CallFromBaseParameters = { worldAddress: Hex; delegatorAddress: Hex; }; - type CallFromFunctionParameters = CallFromBaseParameters & { worldFunctionToSystemFunction: (worldFunctionSelector: Hex) => Promise; publicClient?: never; }; - type CallFromClientParameters = CallFromBaseParameters & { worldFunctionToSystemFunction?: never; publicClient: PublicClient; @@ -37,11 +35,15 @@ type CallFromClientParameters = CallFromBaseParameters & { type SystemFunction = { systemId: Hex; systemFunctionSelector: Hex }; -// By extending viem clients with this function after delegation, the delegation is automatically -// applied to the World contract writes, meaning these writes are made on behalf of the delegator. +// By extending viem clients with this function after delegation, the delegation is automatically applied to World contract writes. +// This means that these writes are made on behalf of the delegator. +// Internally, it transforms the write arguments to use `callFrom`. // // Accepts either `worldFunctionToSystemFunction` or `publicClient` as an argument. -// If `publicClient` is provided, a read request to the World contract will occur. +// `worldFunctionToSystemFunction` allows manually providing the mapping function, thus users can utilize their client store for the lookup. +// If `publicClient` is provided instead, this function retrieves the corresponding system function from the World contract. +// +// The function mapping is cached to avoid redundant retrievals for the same World function. export function callFrom( params: CallFromParameters, ): (client: WalletClient) => Pick, "writeContract"> { @@ -95,10 +97,11 @@ async function worldFunctionToSystemFunction( ): Promise { const cacheKey = concat([params.worldAddress, worldFunctionSelector]); - // Skip the request if it has been called previously. + // Use cache if the function has been called previously. const cached = systemFunctionCache.get(cacheKey); if (cached) return cached; + // If a mapping function is provided, use it. Otherwise, call the World contract. const systemFunction = params.worldFunctionToSystemFunction ? await params.worldFunctionToSystemFunction(worldFunctionSelector) : await retrieveSystemFunctionFromContract(params.publicClient, params.worldAddress, worldFunctionSelector); From af6b3a3bb42cf455311436a25209afee754f8b95 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Sun, 24 Mar 2024 09:06:11 -0500 Subject: [PATCH 14/16] use `getSchemaTypes` --- packages/world/ts/actions/callFrom.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index c3d82cb72c..c2dfef5705 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -13,8 +13,13 @@ import { } from "viem"; import { getAction, encodeFunctionData } from "viem/utils"; import { writeContract } from "viem/actions"; -import { mapObject } from "@latticexyz/common/utils"; -import { getKeySchema, getValueSchema, decodeValueArgs, encodeKey } from "@latticexyz/protocol-parser/internal"; +import { + getKeySchema, + getValueSchema, + getSchemaTypes, + decodeValueArgs, + encodeKey, +} from "@latticexyz/protocol-parser/internal"; import worldConfig from "../../mud.config"; import IStoreReadAbi from "../../out/IStoreRead.sol/IStoreRead.abi.json"; @@ -118,17 +123,8 @@ async function retrieveSystemFunctionFromContract( ): Promise { const table = worldConfig.tables.world__FunctionSelectors; - const _keySchema = getKeySchema(table); - const keySchema = mapObject( - _keySchema, - ({ type }) => type, - ); - - const _valueSchema = getValueSchema(table); - const valueSchema = mapObject< - typeof _valueSchema, - { [K in keyof typeof _valueSchema]: (typeof _valueSchema)[K]["type"] } - >(_valueSchema, ({ type }) => type); + const keySchema = getSchemaTypes(getKeySchema(table)); + const valueSchema = getSchemaTypes(getValueSchema(table)); const [staticData, encodedLengths, dynamicData] = await publicClient.readContract({ address: worldAddress, From 733bdbbb77855048868a8bf4822ac8c8d024757d Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Mon, 25 Mar 2024 12:50:17 -0500 Subject: [PATCH 15/16] make `callFrom` action as internal and document it in changeset --- .changeset/nervous-nails-notice.md | 4 +++- packages/world/package.json | 4 ---- packages/world/ts/actions/index.ts | 1 - packages/world/ts/exports/internal.ts | 2 ++ packages/world/tsup.config.ts | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 packages/world/ts/actions/index.ts diff --git a/.changeset/nervous-nails-notice.md b/.changeset/nervous-nails-notice.md index a817da2777..cab1c47d4b 100644 --- a/.changeset/nervous-nails-notice.md +++ b/.changeset/nervous-nails-notice.md @@ -1,7 +1,9 @@ --- -"@latticexyz/world": minor +"@latticexyz/world": patch --- +This is an internal feature and is not ready for stable consumption yet. + Added viem custom client actions for delegation. By extending viem clients with this function after delegation, the delegation is automatically applied to World contract writes. This means that these writes are made on behalf of the delegator. Internally, it transforms the write arguments to use `callFrom`. Usage example: diff --git a/packages/world/package.json b/packages/world/package.json index d169661e32..701c7340df 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -11,7 +11,6 @@ "type": "module", "exports": { ".": "./dist/index.js", - "./actions": "./dist/actions.js", "./internal": "./dist/internal.js", "./mud.config": "./dist/mud.config.js", "./config/v2": "./dist/config/v2.js", @@ -24,9 +23,6 @@ "index": [ "./ts/exports/index.ts" ], - "actions": [ - "./ts/actions/index.ts" - ], "internal": [ "./ts/exports/internal.ts" ], diff --git a/packages/world/ts/actions/index.ts b/packages/world/ts/actions/index.ts deleted file mode 100644 index d2f67cb6a5..0000000000 --- a/packages/world/ts/actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./callFrom"; diff --git a/packages/world/ts/exports/internal.ts b/packages/world/ts/exports/internal.ts index 9866fe55e4..a07919cb95 100644 --- a/packages/world/ts/exports/internal.ts +++ b/packages/world/ts/exports/internal.ts @@ -7,3 +7,5 @@ export * from "../encodeSystemCall"; export * from "../encodeSystemCallFrom"; export * from "../encodeSystemCalls"; export * from "../encodeSystemCallsFrom"; + +export * from "../actions/callFrom"; diff --git a/packages/world/tsup.config.ts b/packages/world/tsup.config.ts index b5fadb947e..cb091b605f 100644 --- a/packages/world/tsup.config.ts +++ b/packages/world/tsup.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ entry: { "mud.config": "mud.config.ts", index: "ts/exports/index.ts", - actions: "ts/actions/index.ts", internal: "ts/exports/internal.ts", register: "ts/register/index.ts", "config/v2": "ts/config/v2/index.ts", From 42a802ba03b876b08849ef0b7b33c1c8b8f5be88 Mon Sep 17 00:00:00 2001 From: tash-2s <81064017+tash-2s@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:10:18 -0500 Subject: [PATCH 16/16] refactor: simplify code around system calldata --- packages/world/ts/actions/callFrom.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/world/ts/actions/callFrom.ts b/packages/world/ts/actions/callFrom.ts index c2dfef5705..8261b729a0 100644 --- a/packages/world/ts/actions/callFrom.ts +++ b/packages/world/ts/actions/callFrom.ts @@ -13,6 +13,7 @@ import { } from "viem"; import { getAction, encodeFunctionData } from "viem/utils"; import { writeContract } from "viem/actions"; +import { readHex } from "@latticexyz/common"; import { getKeySchema, getValueSchema, @@ -73,13 +74,9 @@ export function callFrom( // Get the systemId and System's function selector. const { systemId, systemFunctionSelector } = await worldFunctionToSystemFunction(params, worldFunctionSelector); - // Construct the System's calldata. - // If there's no args, use the System's function selector as calldata. - // Otherwise, use the World's calldata, replacing the World's function selector with the System's. - const systemCalldata = - worldCalldata === worldFunctionSelector - ? systemFunctionSelector - : concat([systemFunctionSelector, slice(worldCalldata, 4)]); + // Construct the System's calldata by replacing the World's function selector with the System's. + // Use `readHex` instead of `slice` to prevent out-of-bounds errors with calldata that has no args. + const systemCalldata = concat([systemFunctionSelector, readHex(worldCalldata, 4)]); // Construct args for `callFrom`. const callFromArgs: typeof writeArgs = {