Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(world,cli): add system deploy config #3011

Merged
merged 9 commits into from
Aug 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/quiet-pugs-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@latticexyz/world": patch
---

Added `deploy` config options to systems in the MUD config:

- `disabled` to toggle deploying the system (defaults to `false`)
- `registerWorldFunctions` to toggle registering namespace-prefixed system functions on the world (defaults to `true`)

```ts
import { defineWorld } from "@latticexyz/world";

export default defineWorld({
systems: {
HiddenSystem: {
deploy: {
registerWorldFunctions: false,
},
},
},
});
```
9 changes: 9 additions & 0 deletions docs/pages/config/reference.mdx
Original file line number Diff line number Diff line change
@@ -65,6 +65,15 @@ The MUD config has two modes: single namespace and multiple namespaces. By defau
</Param>
<Param name="openAccess">Whether or not any address can call this system. Defaults to `true`.</Param>
<Param name="accessList">A list of contract addresses or system labels that can call this system, used with `openAccess: false`.</Param>
<Param name="deploy">
Customize how to deploy this system.

<Params>
<Param name="disabled">Disable deployment of this system. Defaults to `false`.</Param>
<Param name="registerWorldFunctions">Whether this system's functions should be registered on the world, prefixed with the system namespace. Defaults to `true`.</Param>
</Params>

</Param>
</Params>

</Param>
7 changes: 7 additions & 0 deletions e2e/packages/contracts/mud.config.ts
Original file line number Diff line number Diff line change
@@ -58,6 +58,13 @@ export default defineWorld({
key: [],
},
},
systems: {
HiddenSystem: {
deploy: {
registerWorldFunctions: false,
},
},
},
modules: [
{
artifactPath:
10 changes: 10 additions & 0 deletions e2e/packages/contracts/src/systems/HiddenSystem.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { System } from "@latticexyz/world/src/System.sol";

contract HiddenSystem is System {
function hidden() public {
// this system is only callable with system ID, not via world
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/deploy/common.ts
Original file line number Diff line number Diff line change
@@ -89,7 +89,7 @@ export type System = DeterministicContract & {
readonly allowAll: boolean;
readonly allowedAddresses: readonly Hex[];
readonly allowedSystemIds: readonly Hex[];
readonly functions: readonly WorldFunction[];
readonly worldFunctions: readonly WorldFunction[];
};

export type DeployedSystem = Omit<System, "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds"> & {
2 changes: 1 addition & 1 deletion packages/cli/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
@@ -115,7 +115,7 @@ export async function deploy({
const functionTxs = await ensureFunctions({
client,
worldDeploy,
functions: systems.flatMap((system) => system.functions),
functions: systems.flatMap((system) => system.worldFunctions),
});
const moduleTxs = await ensureModules({
client,
4 changes: 2 additions & 2 deletions packages/cli/src/deploy/getSystems.ts
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ export async function getSystems({
table: worldConfig.namespaces.world.tables.Systems,
key: { systemId: system.resourceId },
});
const systemFunctions = functions.filter((func) => func.systemId === system.resourceId);
const worldFunctions = functions.filter((func) => func.systemId === system.resourceId);
return {
address,
namespace: system.namespace,
@@ -41,7 +41,7 @@ export async function getSystems({
allowedAddresses: resourceAccess
.filter(({ resourceId }) => resourceId === system.resourceId)
.map(({ address }) => address),
functions: systemFunctions,
worldFunctions,
};
}),
);
76 changes: 40 additions & 36 deletions packages/cli/src/deploy/resolveConfig.ts
Original file line number Diff line number Diff line change
@@ -41,45 +41,49 @@ export async function resolveConfig({

const configSystems = await resolveSystems({ rootDir, config });

const systems = configSystems.map((system): System => {
const contractData = getContractData(`${system.label}.sol`, system.label, forgeOutDir);
const systems = configSystems
.filter((system) => !system.deploy.disabled)
.map((system): System => {
const contractData = getContractData(`${system.label}.sol`, system.label, forgeOutDir);

const systemFunctions = contractData.abi
.filter((item): item is typeof item & { type: "function" } => item.type === "function")
.map(toFunctionSignature)
.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 = system.namespace === "" ? sig : `${system.namespace}__${sig}`;
return {
signature: worldSignature,
selector: toFunctionSelector(worldSignature),
systemId: system.systemId,
systemFunctionSignature: sig,
systemFunctionSelector: toFunctionSelector(sig),
};
});
const worldFunctions = system.deploy.registerWorldFunctions
? contractData.abi
.filter((item): item is typeof item & { type: "function" } => item.type === "function")
.map(toFunctionSignature)
.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 = system.namespace === "" ? sig : `${system.namespace}__${sig}`;
return {
signature: worldSignature,
selector: toFunctionSelector(worldSignature),
systemId: system.systemId,
systemFunctionSignature: sig,
systemFunctionSelector: toFunctionSelector(sig),
};
})
: [];

// TODO: move to resolveSystems?
const allowedAddresses = system.accessList.filter((target): target is Hex => isHex(target));
const allowedSystemIds = system.accessList
.filter((target) => !isHex(target))
.map((label) => {
const system = configSystems.find((s) => s.label === label)!;
return system.systemId;
});
// TODO: move to resolveSystems?
const allowedAddresses = system.accessList.filter((target): target is Hex => isHex(target));
const allowedSystemIds = system.accessList
.filter((target) => !isHex(target))
.map((label) => {
const system = configSystems.find((s) => s.label === label)!;
return system.systemId;
});

return {
...system,
allowAll: system.openAccess,
allowedAddresses,
allowedSystemIds,
prepareDeploy: createPrepareDeploy(contractData.bytecode, contractData.placeholders),
deployedBytecodeSize: contractData.deployedBytecodeSize,
abi: contractData.abi,
functions: systemFunctions,
};
});
return {
...system,
allowAll: system.openAccess,
allowedAddresses,
allowedSystemIds,
prepareDeploy: createPrepareDeploy(contractData.bytecode, contractData.placeholders),
deployedBytecodeSize: contractData.deployedBytecodeSize,
abi: contractData.abi,
worldFunctions,
};
});

// Check for overlapping system IDs (since names get truncated when turning into IDs)
// TODO: move this into the world config resolve step once it resolves system IDs
11 changes: 9 additions & 2 deletions packages/world/ts/config/v2/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { CodegenInput, DeployInput, ModuleInput, SystemInput, WorldInput } from "./input";
import { CodegenInput, DeployInput, ModuleInput, SystemDeployInput, SystemInput, WorldInput } from "./input";

export const SYSTEM_DEPLOY_DEFAULTS = {
disabled: false,
registerWorldFunctions: true,
} as const satisfies Required<SystemDeployInput>;

export type SYSTEM_DEPLOY_DEFAULTS = typeof SYSTEM_DEPLOY_DEFAULTS;

export const SYSTEM_DEFAULTS = {
namespace: "",
openAccess: true,
accessList: [],
} as const satisfies Omit<Required<SystemInput>, "label" | "name">;
} as const satisfies Omit<Required<SystemInput>, "label" | "name" | "deploy">;

export type SYSTEM_DEFAULTS = typeof SYSTEM_DEFAULTS;

5 changes: 4 additions & 1 deletion packages/world/ts/config/v2/input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { StoreInput, NamespaceInput as StoreNamespaceInput } from "@latticexyz/store/config/v2";
import { DynamicResolution, ValueWithType } from "./dynamicResolution";
import { Codegen } from "./output";
import { Codegen, SystemDeploy } from "./output";

export type SystemDeployInput = Partial<SystemDeploy>;

export type SystemInput = {
/**
@@ -22,6 +24,7 @@ export type SystemInput = {
readonly openAccess?: boolean;
/** An array of addresses or system names that can access the system */
readonly accessList?: readonly string[];
readonly deploy?: SystemDeployInput;
};

export type SystemsInput = {
15 changes: 15 additions & 0 deletions packages/world/ts/config/v2/output.ts
Original file line number Diff line number Diff line change
@@ -25,6 +25,20 @@ export type Module = {
readonly artifactPath: string | undefined;
};

export type SystemDeploy = {
/**
* Whether or not to deploy the system.
* Defaults to `false`.
*/
readonly disabled: boolean;
/**
* Whether or not to register system functions on the world.
* System functions are prefixed with the system namespace when registering on the world, so system function names must be unique within their namespace.
* Defaults to `true`.
*/
readonly registerWorldFunctions: boolean;
};

export type System = {
/**
* Human-readable system label. Used as config keys, interface names, and filenames.
@@ -47,6 +61,7 @@ export type System = {
readonly openAccess: boolean;
/** An array of addresses or system names that can access the system */
readonly accessList: readonly string[];
readonly deploy: SystemDeploy;
};

export type Systems = {
12 changes: 12 additions & 0 deletions packages/world/ts/config/v2/system.test.ts
Original file line number Diff line number Diff line change
@@ -16,6 +16,10 @@ describe("resolveSystem", () => {
namespace: "",
name: "ExampleSystem" as string,
systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }),
deploy: {
disabled: false,
registerWorldFunctions: true,
},
} as const;

attest<typeof expected>(system).equals(expected);
@@ -33,6 +37,10 @@ describe("resolveSystem", () => {
namespace: "",
name: "ExampleSystem" as string,
systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }),
deploy: {
disabled: false,
registerWorldFunctions: true,
},
} as const;

attest<typeof expected>(system).equals(expected);
@@ -51,6 +59,10 @@ describe("resolveSystem", () => {
name: "ExampleSystem" as string,
systemId: resourceToHex({ type: "system", namespace: "", name: "ExampleSystem" }),
openAccess: false,
deploy: {
disabled: false,
registerWorldFunctions: true,
},
} as const;

attest<typeof expected>(system).equals(expected);
8 changes: 6 additions & 2 deletions packages/world/ts/config/v2/system.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SYSTEM_DEFAULTS } from "./defaults";
import { SYSTEM_DEFAULTS, SYSTEM_DEPLOY_DEFAULTS } from "./defaults";
import { SystemInput } from "./input";
import { hasOwnKey, mergeIfUndefined } from "@latticexyz/store/config/v2";
import { ErrorMessage, narrow, requiredKeyOf } from "@ark/util";
import { ErrorMessage, narrow, requiredKeyOf, show } from "@ark/util";
import { Hex } from "viem";
import { resourceToHex } from "@latticexyz/common";

@@ -52,6 +52,9 @@ export type resolveSystem<input> = input extends SystemInput
readonly systemId: Hex;
readonly openAccess: undefined extends input["openAccess"] ? SYSTEM_DEFAULTS["openAccess"] : input["openAccess"];
readonly accessList: undefined extends input["accessList"] ? SYSTEM_DEFAULTS["accessList"] : input["accessList"];
readonly deploy: show<
mergeIfUndefined<undefined extends input["deploy"] ? {} : input["deploy"], SYSTEM_DEPLOY_DEFAULTS>
>;
}
: never;

@@ -68,6 +71,7 @@ export function resolveSystem<input extends SystemInput>(input: input): resolveS
namespace,
name,
systemId,
deploy: mergeIfUndefined(input.deploy ?? {}, SYSTEM_DEPLOY_DEFAULTS),
},
SYSTEM_DEFAULTS,
) as never;
5 changes: 5 additions & 0 deletions packages/world/ts/config/v2/world.test.ts
Original file line number Diff line number Diff line change
@@ -581,6 +581,7 @@ describe("defineWorld", () => {
namespace: "app",
name: "Example",
systemId: "0x737961707000000000000000000000004578616d706c65000000000000000000",
deploy: { disabled: false, registerWorldFunctions: true },
openAccess: true,
accessList: [],
},
@@ -592,6 +593,10 @@ describe("defineWorld", () => {
readonly systemId: \`0x\${string}\`
readonly openAccess: true
readonly accessList: readonly []
readonly deploy: {
readonly disabled: false
readonly registerWorldFunctions: true
}
}
}`);
});
19 changes: 11 additions & 8 deletions packages/world/ts/node/render-solidity/worldgen.ts
Original file line number Diff line number Diff line change
@@ -28,14 +28,17 @@ export async function worldgen({

const outputPath = path.join(outDir, config.codegen.worldInterfaceName + ".sol");

const systems = (await resolveSystems({ rootDir, config })).map((system) => {
const interfaceName = `I${system.label}`;
return {
...system,
interfaceName,
interfacePath: path.join(path.dirname(outputPath), `${interfaceName}.sol`),
};
});
const systems = (await resolveSystems({ rootDir, config }))
// TODO: move to codegen option or generate "system manifest" and codegen from that
.filter((system) => system.deploy.registerWorldFunctions)
.map((system) => {
const interfaceName = `I${system.label}`;
return {
...system,
interfaceName,
interfacePath: path.join(path.dirname(outputPath), `${interfaceName}.sol`),
};
});

const worldImports = systems.map(
(system): ImportDatum => ({