@latticexyz/cli 2.0.0-skystrife-playtest-19728914 → 2.0.0-snapshot-test-32d38619

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/chunk-WERDORTY.js +11 -0
  2. package/dist/chunk-WERDORTY.js.map +1 -0
  3. package/dist/index.js +1 -1
  4. package/dist/mud.js +7 -7
  5. package/dist/mud.js.map +1 -1
  6. package/package.json +14 -12
  7. package/src/commands/deploy.ts +1 -1
  8. package/src/commands/dev-contracts.ts +13 -3
  9. package/src/commands/tablegen.ts +3 -2
  10. package/src/commands/test.ts +1 -1
  11. package/src/commands/trace.ts +9 -4
  12. package/src/commands/worldgen.ts +1 -1
  13. package/src/utils/deploy.ts +188 -553
  14. package/src/utils/deployHandler.ts +1 -1
  15. package/src/utils/modules/constants.ts +23 -0
  16. package/src/utils/modules/getInstallModuleCallData.ts +27 -0
  17. package/src/utils/modules/getUserModules.ts +5 -0
  18. package/src/utils/modules/types.ts +14 -0
  19. package/src/utils/systems/getGrantAccessCallData.ts +29 -0
  20. package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +57 -0
  21. package/src/utils/systems/getRegisterSystemCallData.ts +17 -0
  22. package/src/utils/systems/types.ts +9 -0
  23. package/src/utils/systems/utils.ts +42 -0
  24. package/src/utils/tables/getRegisterTableCallData.ts +49 -0
  25. package/src/utils/tables/getTableIds.ts +21 -0
  26. package/src/utils/tables/types.ts +12 -0
  27. package/src/utils/utils/confirmNonce.ts +24 -0
  28. package/src/utils/utils/deployContract.ts +33 -0
  29. package/src/utils/utils/fastTxExecute.ts +56 -0
  30. package/src/utils/utils/getContractData.ts +29 -0
  31. package/src/utils/utils/postDeploy.ts +25 -0
  32. package/src/utils/utils/setInternalFeePerGas.ts +49 -0
  33. package/src/utils/utils/toBytes16.ts +16 -0
  34. package/src/utils/utils/types.ts +21 -0
  35. package/src/utils/world.ts +28 -0
  36. package/dist/chunk-OJAPOMSC.js +0 -11
  37. package/dist/chunk-OJAPOMSC.js.map +0 -1
  38. package/src/utils/index.ts +0 -6
  39. /package/src/utils/{getChainId.ts → utils/getChainId.ts} +0 -0
@@ -7,9 +7,9 @@ import { WorldConfig } from "@latticexyz/world";
7
7
  import { deploy } from "../utils/deploy";
8
8
  import { forge, getRpcUrl, getSrcDirectory } from "@latticexyz/common/foundry";
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
- import { getChainId } from "../utils/getChainId";
11
10
  import { getExistingContracts } from "./getExistingContracts";
12
11
  import { execa } from "execa";
12
+ import { getChainId } from "./utils/getChainId";
13
13
 
14
14
  export type DeployOptions = {
15
15
  configPath?: string;
@@ -0,0 +1,23 @@
1
+ import KeysWithValueModuleData from "@latticexyz/world-modules/out/KeysWithValueModule.sol/KeysWithValueModule.json" assert { type: "json" };
2
+ import KeysInTableModuleData from "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json" assert { type: "json" };
3
+ import UniqueEntityModuleData from "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json" assert { type: "json" };
4
+ import { ContractCode } from "../utils/types";
5
+
6
+ // These modules are always deployed
7
+ export const defaultModuleContracts: ContractCode[] = [
8
+ {
9
+ name: "KeysWithValueModule",
10
+ abi: KeysWithValueModuleData.abi,
11
+ bytecode: KeysWithValueModuleData.bytecode,
12
+ },
13
+ {
14
+ name: "KeysInTableModule",
15
+ abi: KeysInTableModuleData.abi,
16
+ bytecode: KeysInTableModuleData.bytecode,
17
+ },
18
+ {
19
+ name: "UniqueEntityModule",
20
+ abi: UniqueEntityModuleData.abi,
21
+ bytecode: UniqueEntityModuleData.bytecode,
22
+ },
23
+ ];
@@ -0,0 +1,27 @@
1
+ import { defaultAbiCoder } from "ethers/lib/utils.js";
2
+ import { resolveWithContext } from "@latticexyz/config";
3
+ import { Module } from "./types";
4
+ import { CallData } from "../utils/types";
5
+ import { TableIds } from "../tables/types";
6
+
7
+ export async function getInstallModuleCallData(
8
+ moduleContracts: Record<string, Promise<string>>,
9
+ module: Module,
10
+ tableIds: TableIds
11
+ ): Promise<CallData> {
12
+ const moduleAddress = await moduleContracts[module.name];
13
+ if (!moduleAddress) throw new Error(`Module ${module.name} not found`);
14
+ // Resolve arguments
15
+ const resolvedArgs = module.args.map((arg) =>
16
+ resolveWithContext(arg, {
17
+ tableIds,
18
+ })
19
+ );
20
+ const values = resolvedArgs.map((arg) => arg.value);
21
+ const types = resolvedArgs.map((arg) => arg.type);
22
+
23
+ return {
24
+ func: module.root ? "installRootModule" : "installModule",
25
+ args: [moduleAddress, defaultAbiCoder.encode(types, values)],
26
+ };
27
+ }
@@ -0,0 +1,5 @@
1
+ import { Module } from "./types";
2
+
3
+ export function getUserModules(defaultModules: { name: string }[], configModules: Module[]): Omit<Module, "address">[] {
4
+ return configModules.filter((module) => !defaultModules.some((m) => m.name === module.name));
5
+ }
@@ -0,0 +1,14 @@
1
+ export type Module = {
2
+ name: string;
3
+ root: boolean;
4
+ args: (
5
+ | {
6
+ value: (string | number | Uint8Array) & (string | number | Uint8Array | undefined);
7
+ type: string;
8
+ }
9
+ | {
10
+ type: any;
11
+ input: string;
12
+ }
13
+ )[];
14
+ };
@@ -0,0 +1,29 @@
1
+ import { System } from "./types";
2
+ import { CallData } from "../utils/types";
3
+ import { resourceIdToHex } from "@latticexyz/common";
4
+
5
+ export async function getGrantAccessCallData(input: {
6
+ systems: System[];
7
+ systemContracts: Record<string, Promise<string>>;
8
+ namespace: string;
9
+ }): Promise<CallData[]> {
10
+ const { systems, namespace, systemContracts } = input;
11
+ const calls: CallData[] = [];
12
+ for (const { name, accessListAddresses, accessListSystems } of systems) {
13
+ // Grant access to addresses
14
+ accessListAddresses.map(async (address) => calls.push(getGrantSystemAccessCallData(name, namespace, address)));
15
+
16
+ // Grant access to other systems
17
+ accessListSystems.map(async (granteeSystem) =>
18
+ calls.push(getGrantSystemAccessCallData(name, namespace, await systemContracts[granteeSystem]))
19
+ );
20
+ }
21
+ return calls;
22
+ }
23
+
24
+ function getGrantSystemAccessCallData(name: string, namespace: string, address: string): CallData {
25
+ return {
26
+ func: "grantAccess",
27
+ args: [resourceIdToHex({ type: "system", namespace, name }), address],
28
+ };
29
+ }
@@ -0,0 +1,57 @@
1
+ import { resourceIdToHex } from "@latticexyz/common";
2
+ import { System } from "./types";
3
+ import { loadFunctionSignatures, toFunctionSelector } from "./utils";
4
+ import { CallData } from "../utils/types";
5
+
6
+ export function getRegisterFunctionSelectorsCallData(input: {
7
+ systemContractName: string;
8
+ system: System;
9
+ namespace: string;
10
+ forgeOutDirectory: string;
11
+ }): CallData[] {
12
+ // Register system at route
13
+ const callData: CallData[] = [];
14
+ const { systemContractName, namespace, forgeOutDirectory, system } = input;
15
+
16
+ if (system.registerFunctionSelectors) {
17
+ const baseSystemFunctionSignatures = loadFunctionSignatures("System", forgeOutDirectory);
18
+ const systemFunctionSignatures = loadFunctionSignatures(systemContractName, forgeOutDirectory).filter(
19
+ (functionSignature) =>
20
+ systemContractName === "System" || !baseSystemFunctionSignatures.includes(functionSignature)
21
+ );
22
+ const isRoot = namespace === "";
23
+ for (const systemFunctionSignature of systemFunctionSignatures) {
24
+ callData.push(
25
+ getRegisterFunctionSelectorCallData({
26
+ namespace,
27
+ name: system.name,
28
+ systemFunctionSignature,
29
+ isRoot,
30
+ })
31
+ );
32
+ }
33
+ }
34
+ return callData;
35
+ }
36
+
37
+ function getRegisterFunctionSelectorCallData(input: {
38
+ namespace: string;
39
+ name: string;
40
+ systemFunctionSignature: string;
41
+ isRoot: boolean;
42
+ }): CallData {
43
+ const { namespace, name, systemFunctionSignature, isRoot } = input;
44
+
45
+ if (isRoot) {
46
+ const functionSelector = toFunctionSelector(systemFunctionSignature);
47
+ return {
48
+ func: "registerRootFunctionSelector",
49
+ args: [resourceIdToHex({ type: "system", namespace, name }), systemFunctionSignature, functionSelector],
50
+ };
51
+ } else {
52
+ return {
53
+ func: "registerFunctionSelector",
54
+ args: [resourceIdToHex({ type: "system", namespace, name }), systemFunctionSignature],
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,17 @@
1
+ import { resourceIdToHex } from "@latticexyz/common";
2
+ import { System } from "./types";
3
+ import { CallData } from "../utils/types";
4
+
5
+ export async function getRegisterSystemCallData(input: {
6
+ systemContracts: Record<string, Promise<string>>;
7
+ systemKey: string;
8
+ system: System;
9
+ namespace: string;
10
+ }): Promise<CallData> {
11
+ const { namespace, systemContracts, systemKey, system } = input;
12
+ const systemAddress = await systemContracts[systemKey];
13
+ return {
14
+ func: "registerSystem",
15
+ args: [resourceIdToHex({ type: "system", namespace, name: system.name }), systemAddress, system.openAccess],
16
+ };
17
+ }
@@ -0,0 +1,9 @@
1
+ export type System = {
2
+ name: string;
3
+ registerFunctionSelectors: boolean;
4
+ openAccess: boolean;
5
+ accessListAddresses: string[];
6
+ accessListSystems: string[];
7
+ };
8
+
9
+ export type SystemsConfig = Record<string, System>;
@@ -0,0 +1,42 @@
1
+ import { ethers } from "ethers";
2
+ import { ParamType } from "ethers/lib/utils.js";
3
+ import { getContractData } from "../utils/getContractData";
4
+
5
+ export function loadFunctionSignatures(contractName: string, forgeOutDirectory: string): string[] {
6
+ const { abi } = getContractData(contractName, forgeOutDirectory);
7
+
8
+ return abi
9
+ .filter((item) => ["fallback", "function"].includes(item.type))
10
+ .map((item) => {
11
+ return `${item.name}${parseComponents(item.inputs)}`;
12
+ });
13
+ }
14
+
15
+ // TODO: move this to utils as soon as utils are usable inside cli
16
+ // (see https://github.com/latticexyz/mud/issues/499)
17
+ export function toFunctionSelector(functionSignature: string): string {
18
+ return sigHash(functionSignature);
19
+ }
20
+
21
+ /**
22
+ * Recursively turn (nested) structs in signatures into tuples
23
+ */
24
+ function parseComponents(params: ParamType[]): string {
25
+ const components = params.map((param) => {
26
+ const tupleMatch = param.type.match(/tuple(.*)/);
27
+ if (tupleMatch) {
28
+ // there can be arrays of tuples,
29
+ // `tupleMatch[1]` preserves the array brackets (or is empty string for non-arrays)
30
+ return parseComponents(param.components) + tupleMatch[1];
31
+ } else {
32
+ return param.type;
33
+ }
34
+ });
35
+ return `(${components})`;
36
+ }
37
+
38
+ // TODO: move this to utils as soon as utils are usable inside cli
39
+ // (see https://github.com/latticexyz/mud/issues/499)
40
+ function sigHash(signature: string) {
41
+ return ethers.utils.hexDataSlice(ethers.utils.keccak256(ethers.utils.toUtf8Bytes(signature)), 0, 4);
42
+ }
@@ -0,0 +1,49 @@
1
+ import { encodeSchema, getStaticByteLength } from "@latticexyz/schema-type/deprecated";
2
+ import { StoreConfig } from "@latticexyz/store";
3
+ import { resolveAbiOrUserType } from "@latticexyz/store/codegen";
4
+ import { resourceIdToHex } from "@latticexyz/common";
5
+ import { Table } from "./types";
6
+ import { fieldLayoutToHex } from "@latticexyz/protocol-parser";
7
+ import { CallData } from "../utils/types";
8
+ import { loadAndExtractUserTypes } from "@latticexyz/common/codegen";
9
+
10
+ export function getRegisterTableCallData(
11
+ table: Table,
12
+ storeConfig: StoreConfig,
13
+ outputBaseDirectory: string,
14
+ remappings: [string, string][]
15
+ ): CallData {
16
+ const { name, valueSchema, keySchema } = table;
17
+ if (!name) throw Error("Table missing name");
18
+
19
+ const solidityUserTypes = loadAndExtractUserTypes(storeConfig.userTypes, outputBaseDirectory, remappings);
20
+
21
+ const schemaTypes = Object.values(valueSchema).map((abiOrUserType) => {
22
+ const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig, solidityUserTypes);
23
+ return schemaType;
24
+ });
25
+
26
+ const schemaTypeLengths = schemaTypes.map((schemaType) => getStaticByteLength(schemaType));
27
+ const fieldLayout = {
28
+ staticFieldLengths: schemaTypeLengths.filter((schemaTypeLength) => schemaTypeLength > 0),
29
+ numDynamicFields: schemaTypeLengths.filter((schemaTypeLength) => schemaTypeLength === 0).length,
30
+ };
31
+
32
+ const keyTypes = Object.values(keySchema).map((abiOrUserType) => {
33
+ const { schemaType } = resolveAbiOrUserType(abiOrUserType, storeConfig, solidityUserTypes);
34
+ return schemaType;
35
+ });
36
+
37
+ return {
38
+ func: "registerTable",
39
+ args: [
40
+ // TODO: add support for table namespaces (https://github.com/latticexyz/mud/issues/994)
41
+ resourceIdToHex({ type: table.offchainOnly ? "offchainTable" : "table", namespace: storeConfig.namespace, name }),
42
+ fieldLayoutToHex(fieldLayout),
43
+ encodeSchema(keyTypes),
44
+ encodeSchema(schemaTypes),
45
+ Object.keys(keySchema),
46
+ Object.keys(valueSchema),
47
+ ],
48
+ };
49
+ }
@@ -0,0 +1,21 @@
1
+ import { StoreConfig } from "@latticexyz/store";
2
+ import { TableIds } from "./types";
3
+ import { toBytes16 } from "../utils/toBytes16";
4
+
5
+ export function getTableIds(storeConfig: StoreConfig): TableIds {
6
+ const tableIds: TableIds = {};
7
+ for (const [tableName, { name }] of Object.entries(storeConfig.tables)) {
8
+ tableIds[tableName] = toResourceSelector(storeConfig.namespace, name);
9
+ }
10
+ return tableIds;
11
+ }
12
+
13
+ // (see https://github.com/latticexyz/mud/issues/499)
14
+ function toResourceSelector(namespace: string, file: string): Uint8Array {
15
+ const namespaceBytes = toBytes16(namespace);
16
+ const fileBytes = toBytes16(file);
17
+ const result = new Uint8Array(32);
18
+ result.set(namespaceBytes);
19
+ result.set(fileBytes, 16);
20
+ return result;
21
+ }
@@ -0,0 +1,12 @@
1
+ export type Table = {
2
+ valueSchema: Record<string, string>;
3
+ keySchema: Record<string, string>;
4
+ directory: string;
5
+ tableIdArgument: boolean;
6
+ storeArgument: boolean;
7
+ offchainOnly: boolean;
8
+ name?: string | undefined;
9
+ dataStruct?: boolean | undefined;
10
+ };
11
+
12
+ export type TableIds = { [tableName: string]: Uint8Array };
@@ -0,0 +1,24 @@
1
+ import chalk from "chalk";
2
+ import { Wallet } from "ethers";
3
+ import { MUDError } from "@latticexyz/common/errors";
4
+
5
+ export async function confirmNonce(signer: Wallet, nonce: number, pollInterval: number): Promise<void> {
6
+ let remoteNonce = await signer.getTransactionCount();
7
+ let retryCount = 0;
8
+ const maxRetries = 100;
9
+ while (remoteNonce !== nonce && retryCount < maxRetries) {
10
+ console.log(
11
+ chalk.gray(
12
+ `Waiting for transactions to be included before executing postDeployScript (local nonce: ${nonce}, remote nonce: ${remoteNonce}, retry number ${retryCount}/${maxRetries})`
13
+ )
14
+ );
15
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
16
+ retryCount++;
17
+ remoteNonce = await signer.getTransactionCount();
18
+ }
19
+ if (remoteNonce !== nonce) {
20
+ throw new MUDError(
21
+ "Remote nonce doesn't match local nonce, indicating that not all deploy transactions were included."
22
+ );
23
+ }
24
+ }
@@ -0,0 +1,33 @@
1
+ import chalk from "chalk";
2
+ import { ethers } from "ethers";
3
+ import { MUDError } from "@latticexyz/common/errors";
4
+ import { TxConfig, ContractCode } from "./types";
5
+
6
+ export async function deployContract(input: TxConfig & { nonce: number; contract: ContractCode }): Promise<string> {
7
+ const { signer, nonce, maxPriorityFeePerGas, maxFeePerGas, debug, gasPrice, confirmations, contract } = input;
8
+
9
+ try {
10
+ const factory = new ethers.ContractFactory(contract.abi, contract.bytecode, signer);
11
+ console.log(chalk.gray(`executing deployment of ${contract.name} with nonce ${nonce}`));
12
+ const deployPromise = factory
13
+ .deploy({
14
+ nonce,
15
+ maxPriorityFeePerGas,
16
+ maxFeePerGas,
17
+ gasPrice,
18
+ })
19
+ .then((c) => (confirmations ? c : c.deployed()));
20
+ const { address } = await deployPromise;
21
+ console.log(chalk.green("Deployed", contract.name, "to", address));
22
+ return address;
23
+ } catch (error: any) {
24
+ if (debug) console.error(error);
25
+ if (error?.message.includes("invalid bytecode")) {
26
+ throw new MUDError(
27
+ `Error deploying ${contract.name}: invalid bytecode. Note that linking of public libraries is not supported yet, make sure none of your libraries use "external" functions.`
28
+ );
29
+ } else if (error?.message.includes("CreateContractLimit")) {
30
+ throw new MUDError(`Error deploying ${contract.name}: CreateContractLimit exceeded.`);
31
+ } else throw error;
32
+ }
33
+ }
@@ -0,0 +1,56 @@
1
+ import chalk from "chalk";
2
+ import { TransactionReceipt, TransactionResponse } from "@ethersproject/providers";
3
+ import { MUDError } from "@latticexyz/common/errors";
4
+ import { TxConfig } from "./types";
5
+
6
+ /**
7
+ * Only await gas estimation (for speed), only execute if gas estimation succeeds (for safety)
8
+ */
9
+ export async function fastTxExecute<
10
+ C extends { connect: any; estimateGas: any; [key: string]: any },
11
+ F extends keyof C
12
+ >(
13
+ input: TxConfig & {
14
+ nonce: number;
15
+ contract: C;
16
+ func: F;
17
+ args: Parameters<C[F]>;
18
+ confirmations: number;
19
+ }
20
+ ): Promise<TransactionResponse | TransactionReceipt> {
21
+ const {
22
+ func,
23
+ args,
24
+ contract,
25
+ signer,
26
+ nonce,
27
+ maxPriorityFeePerGas,
28
+ maxFeePerGas,
29
+ gasPrice,
30
+ confirmations = 1,
31
+ debug,
32
+ } = input;
33
+ const functionName = `${func as string}(${args.map((arg) => `'${arg}'`).join(",")})`;
34
+ try {
35
+ const contractWithSigner = contract.connect(signer);
36
+ const gasLimit = await contractWithSigner.estimateGas[func].apply(null, args);
37
+ console.log(chalk.gray(`executing transaction: ${functionName} with nonce ${nonce}`));
38
+ return contractWithSigner[func]
39
+ .apply(null, [
40
+ ...args,
41
+ {
42
+ gasLimit,
43
+ nonce: nonce,
44
+ maxPriorityFeePerGas: maxPriorityFeePerGas,
45
+ maxFeePerGas: maxFeePerGas,
46
+ gasPrice: gasPrice,
47
+ },
48
+ ])
49
+ .then((tx: TransactionResponse) => {
50
+ return confirmations === 0 ? tx : tx.wait(confirmations);
51
+ });
52
+ } catch (error: any) {
53
+ if (debug) console.error(error);
54
+ throw new MUDError(`Gas estimation error for ${functionName}: ${error?.reason}`);
55
+ }
56
+ }
@@ -0,0 +1,29 @@
1
+ import { readFileSync } from "fs";
2
+ import path from "path";
3
+ import { Fragment } from "ethers/lib/utils.js";
4
+ import { MUDError } from "@latticexyz/common/errors";
5
+
6
+ /**
7
+ * Load the contract's abi and bytecode from the file system
8
+ * @param contractName: Name of the contract to load
9
+ */
10
+ export function getContractData(
11
+ contractName: string,
12
+ forgeOutDirectory: string
13
+ ): { bytecode: string; abi: Fragment[] } {
14
+ let data: any;
15
+ const contractDataPath = path.join(forgeOutDirectory, contractName + ".sol", contractName + ".json");
16
+ try {
17
+ data = JSON.parse(readFileSync(contractDataPath, "utf8"));
18
+ } catch (error: any) {
19
+ throw new MUDError(`Error reading file at ${contractDataPath}`);
20
+ }
21
+
22
+ const bytecode = data?.bytecode?.object;
23
+ if (!bytecode) throw new MUDError(`No bytecode found in ${contractDataPath}`);
24
+
25
+ const abi = data?.abi;
26
+ if (!abi) throw new MUDError(`No ABI found in ${contractDataPath}`);
27
+
28
+ return { abi, bytecode };
29
+ }
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "fs";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { getScriptDirectory, forge } from "@latticexyz/common/foundry";
5
+
6
+ export async function postDeploy(
7
+ postDeployScript: string,
8
+ worldAddress: string,
9
+ rpc: string,
10
+ profile: string | undefined
11
+ ): Promise<void> {
12
+ // Execute postDeploy forge script
13
+ const postDeployPath = path.join(await getScriptDirectory(), postDeployScript + ".s.sol");
14
+ if (existsSync(postDeployPath)) {
15
+ console.log(chalk.blue(`Executing post deploy script at ${postDeployPath}`));
16
+ await forge(
17
+ ["script", postDeployScript, "--sig", "run(address)", worldAddress, "--broadcast", "--rpc-url", rpc, "-vvv"],
18
+ {
19
+ profile: profile,
20
+ }
21
+ );
22
+ } else {
23
+ console.log(`No script at ${postDeployPath}, skipping post deploy hook`);
24
+ }
25
+ }
@@ -0,0 +1,49 @@
1
+ import { BigNumber, Wallet } from "ethers";
2
+ import { MUDError } from "@latticexyz/common/errors";
3
+
4
+ /**
5
+ * Set the maxFeePerGas and maxPriorityFeePerGas based on the current base fee and the given multiplier.
6
+ * The multiplier is used to allow replacing pending transactions.
7
+ * @param multiplier Multiplier to apply to the base fee
8
+ */
9
+ export async function setInternalFeePerGas(
10
+ signer: Wallet,
11
+ multiplier: number
12
+ ): Promise<{
13
+ maxPriorityFeePerGas: number | undefined;
14
+ maxFeePerGas: BigNumber | undefined;
15
+ gasPrice: BigNumber | undefined;
16
+ }> {
17
+ // Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions
18
+ const feeData = await signer.provider.getFeeData();
19
+ let maxPriorityFeePerGas: number | undefined;
20
+ let maxFeePerGas: BigNumber | undefined;
21
+ let gasPrice: BigNumber | undefined;
22
+
23
+ if (feeData.lastBaseFeePerGas) {
24
+ if (!feeData.lastBaseFeePerGas.eq(0) && (await signer.getBalance()).eq(0)) {
25
+ throw new MUDError(`Attempting to deploy to a chain with non-zero base fee with an account that has no balance.
26
+ If you're deploying to the Lattice testnet, you can fund your account by running 'pnpm mud faucet --address ${await signer.getAddress()}'`);
27
+ }
28
+
29
+ // Set the priority fee to 0 for development chains with no base fee, to allow transactions from unfunded wallets
30
+ maxPriorityFeePerGas = feeData.lastBaseFeePerGas.eq(0) ? 0 : Math.floor(1_500_000_000 * multiplier);
31
+ maxFeePerGas = feeData.lastBaseFeePerGas.mul(2).add(maxPriorityFeePerGas);
32
+ } else if (feeData.gasPrice) {
33
+ // Legacy chains with gasPrice instead of maxFeePerGas
34
+ if (!feeData.gasPrice.eq(0) && (await signer.getBalance()).eq(0)) {
35
+ throw new MUDError(
36
+ `Attempting to deploy to a chain with non-zero gas price with an account that has no balance.`
37
+ );
38
+ }
39
+
40
+ gasPrice = feeData.gasPrice;
41
+ } else {
42
+ throw new MUDError("Can not fetch fee data from RPC");
43
+ }
44
+ return {
45
+ maxPriorityFeePerGas,
46
+ maxFeePerGas,
47
+ gasPrice,
48
+ };
49
+ }
@@ -0,0 +1,16 @@
1
+ // TODO: use stringToBytes16 from utils as soon as utils are usable inside cli
2
+ // (see https://github.com/latticexyz/mud/issues/499)
3
+ export function toBytes16(input: string) {
4
+ if (input.length > 16) throw new Error("String does not fit into 16 bytes");
5
+
6
+ const result = new Uint8Array(16);
7
+ // Set ascii bytes
8
+ for (let i = 0; i < input.length; i++) {
9
+ result[i] = input.charCodeAt(i);
10
+ }
11
+ // Set the remaining bytes to 0
12
+ for (let i = input.length; i < 16; i++) {
13
+ result[i] = 0;
14
+ }
15
+ return result;
16
+ }
@@ -0,0 +1,21 @@
1
+ import { BigNumber, ContractInterface, ethers } from "ethers";
2
+
3
+ export type CallData = {
4
+ func: string;
5
+ args: unknown[];
6
+ };
7
+
8
+ export type ContractCode = {
9
+ name: string;
10
+ abi: ContractInterface;
11
+ bytecode: string | { object: string };
12
+ };
13
+
14
+ export type TxConfig = {
15
+ signer: ethers.Wallet;
16
+ maxPriorityFeePerGas: number | undefined;
17
+ maxFeePerGas: BigNumber | undefined;
18
+ gasPrice: BigNumber | undefined;
19
+ debug: boolean;
20
+ confirmations: number;
21
+ };
@@ -0,0 +1,28 @@
1
+ import chalk from "chalk";
2
+
3
+ import WorldData from "@latticexyz/world/out/World.sol/World.json" assert { type: "json" };
4
+ import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
5
+ import { deployContract } from "./utils/deployContract";
6
+ import { getContractData } from "./utils/getContractData";
7
+ import { TxConfig } from "./utils/types";
8
+
9
+ export async function deployWorldContract(
10
+ ip: TxConfig & {
11
+ nonce: number;
12
+ worldContractName: string | undefined;
13
+ forgeOutDirectory: string;
14
+ }
15
+ ): Promise<string> {
16
+ console.log(chalk.blue(`Deploying World`));
17
+ const contractData = ip.worldContractName
18
+ ? {
19
+ name: "World",
20
+ ...getContractData(ip.worldContractName, ip.forgeOutDirectory),
21
+ }
22
+ : { abi: IBaseWorldAbi, bytecode: WorldData.bytecode, name: "World" };
23
+ return deployContract({
24
+ ...ip,
25
+ nonce: ip.nonce,
26
+ contract: contractData,
27
+ });
28
+ }
@@ -1,11 +0,0 @@
1
- import B from"chalk";import Y from"path";import{MUDError as Ge}from"@latticexyz/common/errors";import{loadConfig as Ue}from"@latticexyz/config/node";import{existsSync as Se,readFileSync as De}from"fs";import ie from"path";import s from"chalk";import{ethers as k}from"ethers";import{defaultAbiCoder as Fe}from"ethers/lib/utils.js";import{getOutDirectory as ve,getScriptDirectory as xe,cast as Ie,forge as Re}from"@latticexyz/common/foundry";import{resolveWithContext as ke}from"@latticexyz/config";import{MUDError as $}from"@latticexyz/common/errors";import{encodeSchema as se,getStaticByteLength as Ae}from"@latticexyz/schema-type/deprecated";import{resolveAbiOrUserType as ae}from"@latticexyz/store/codegen";import{resolveWorldConfig as Te}from"@latticexyz/world";import le from"@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json"assert{type:"json"};import je from"@latticexyz/world/out/World.sol/World.json"assert{type:"json"};import ce from"@latticexyz/world/out/CoreModule.sol/CoreModule.json"assert{type:"json"};import de from"@latticexyz/world/out/KeysWithValueModule.sol/KeysWithValueModule.json"assert{type:"json"};import ue from"@latticexyz/world/out/KeysInTableModule.sol/KeysInTableModule.json"assert{type:"json"};import pe from"@latticexyz/world/out/UniqueEntityModule.sol/UniqueEntityModule.json"assert{type:"json"};import{tableIdToHex as A}from"@latticexyz/common";import{fieldLayoutToHex as Me}from"@latticexyz/protocol-parser";async function me(i,g,p){let f=Te(i,g),x=Date.now(),{worldContractName:E,namespace:a,postDeployScript:T}=i,{profile:j,rpc:y,privateKey:O,priorityFeeMultiplier:w,debug:D,worldAddress:F,disableTxWait:b,pollInterval:M}=p,we=await ve(j),he=(await oe("System")).map(e=>e.functionName),U=new k.providers.StaticJsonRpcProvider(y);U.pollingInterval=M;let P=new k.Wallet(O,U);console.log("Deploying from",P.address);let v=await P.getTransactionCount();console.log("Initial nonce",v);let N,K,L;await V(w);let d=[],Q=Number(await Ie(["block-number","--rpc-url",y],{profile:j}));console.log("Start deployment at block",Q);let $e={World:F?Promise.resolve(F):E?J(E,b):R(le,je.bytecode,b,"World")},Pe=Object.keys(f.systems).reduce((e,t)=>(e[t]=J(t,b),e),{}),X={CoreModule:R(ce.abi,ce.bytecode,b,"CoreModule"),KeysWithValueModule:R(de.abi,de.bytecode,b,"KeysWithValueModule"),KeysInTableModule:R(ue.abi,ue.bytecode,b,"KeysInTableModule"),UniqueEntityModule:R(pe.abi,pe.bytecode,b,"UniqueEntityModule")},Z=i.modules.filter(e=>!X[e.name]).reduce((e,t)=>(e[t.name]=J(t.name,b),e),X),I={...$e,...Pe,...Z},C=new k.Contract(await I.World,le,P),S=b?0:1;F||(console.log(s.blue("Installing core World modules")),await h(C,"initialize",[await Z.CoreModule],S),console.log(s.green("Installed core World modules"))),a&&await h(C,"registerNamespace",[H(a)],S);let ee={};d=[...d,...Object.entries(i.tables).map(async([e,{name:t,schema:r,keySchema:o}])=>{console.log(s.blue(`Registering table ${e} at ${a}/${t}`)),ee[e]=We(a,t);let l=Object.values(r).map(c=>{let{schemaType:W}=ae(c,i);return W}),n=l.map(c=>Ae(c)),u={staticFieldLengths:n.filter(c=>c>0),numDynamicFields:n.filter(c=>c===0).length},m=Object.values(o).map(c=>{let{schemaType:W}=ae(c,i);return W});await h(C,"registerTable",[A(a,t),Me(u),se(m),se(l),Object.keys(o),Object.keys(r)],S),console.log(s.green(`Registered table ${e} at ${t}`))})],d=[...d,...Object.entries(f.systems).map(async([e,{name:t,openAccess:r,registerFunctionSelectors:o}])=>{if(console.log(s.blue(`Registering system ${e} at ${a}/${t}`)),await h(C,"registerSystem",[A(a,t),await I[e],r],S),console.log(s.green(`Registered system ${e} at ${a}/${t}`)),o){let l=await oe(e),n=a==="";await Promise.all(l.map(async({functionName:u,functionArgs:m})=>{let c=n?u+m:`${a}_${t}_${u}${m}`;if(console.log(s.blue(`Registering function "${c}"`)),n){let W=ge(c===""?{functionName:e,functionArgs:m}:{functionName:u,functionArgs:m}),Ce=ge({functionName:u,functionArgs:m});await h(C,"registerRootFunctionSelector",[A(a,t),W,Ce],S)}else await h(C,"registerFunctionSelector",[A(a,t),u,m],S);console.log(s.green(`Registered function "${c}"`))}))}})],await Promise.all(d),d=[];for(let[e,{name:t,accessListAddresses:r,accessListSystems:o}]of Object.entries(f.systems)){let l=`${a}/${t}`;d=[...d,...r.map(async n=>{console.log(s.blue(`Grant ${n} access to ${e} (${l})`)),await h(C,"grantAccess",[A(a,t),n],S),console.log(s.green(`Granted ${n} access to ${e} (${a}/${t})`))})],d=[...d,...o.map(async n=>{console.log(s.blue(`Grant ${n} access to ${e} (${l})`)),await h(C,"grantAccess",[A(a,t),await I[n]],S),console.log(s.green(`Granted ${n} access to ${e} (${l})`))})]}await Promise.all(d),d=[],d=[...d,...i.modules.map(async e=>{console.log(s.blue(`Installing${e.root?" root ":" "}module ${e.name}`));let t=await Promise.all(e.args.map(n=>ke(n,{tableIds:ee,systemAddresses:I}))),r=t.map(n=>n.value),o=t.map(n=>n.type),l=await I[e.name];if(!l)throw new Error(`Module ${e.name} not found`);await h(C,e.root?"installRootModule":"installModule",[l,Fe.encode(o,r)],S),console.log(s.green(`Installed${e.root?" root ":" "}module ${e.name}`))})],await Promise.all(d);let G=await P.getTransactionCount(),_=0,te=100;for(;G!==v&&_<te;)console.log(s.gray(`Waiting for transactions to be included before executing ${T} (local nonce: ${v}, remote nonce: ${G}, retry number ${_}/${te})`)),await new Promise(e=>setTimeout(e,M)),_++,G=await P.getTransactionCount();if(G!==v)throw new $("Remote nonce doesn't match local nonce, indicating that not all deploy transactions were included.");d=[];let q=ie.join(await xe(),T+".s.sol");return Se(q)?(console.log(s.blue(`Executing post deploy script at ${q}`)),await Re(["script",T,"--sig","run(address)",await I.World,"--broadcast","--rpc-url",y,"-vvv"],{profile:j})):console.log(`No script at ${q}, skipping post deploy hook`),console.log(s.green("Deployment completed in",(Date.now()-x)/1e3,"seconds")),{worldAddress:await I.World,blockNumber:Q};async function J(e,t){console.log(s.blue("Deploying",e));let{abi:r,bytecode:o}=await re(e);return R(r,o,t,e)}async function R(e,t,r,o,l=0){try{let n=new k.ContractFactory(e,t,P);console.log(s.gray(`executing deployment of ${o} with nonce ${v}`));let u=n.deploy({nonce:v++,maxPriorityFeePerGas:N,maxFeePerGas:K,gasPrice:L}).then(c=>r?c:c.deployed());d.push(u);let{address:m}=await u;return console.log(s.green("Deployed",o,"to",m)),m}catch(n){if(D&&console.error(n),l===0&&n?.message.includes("transaction already imported"))return V(w*1.1),R(e,t,r,o,l++);throw n?.message.includes("invalid bytecode")?new $(`Error deploying ${o}: invalid bytecode. Note that linking of public libraries is not supported yet, make sure none of your libraries use "external" functions.`):n?.message.includes("CreateContractLimit")?new $(`Error deploying ${o}: CreateContractLimit exceeded.`):n}}async function oe(e){let{abi:t}=await re(e),r=t.filter(o=>["fallback","function"].includes(o.type)).map(o=>o.type==="fallback"?{functionName:"",functionArgs:""}:{functionName:o.name,functionArgs:ne(o.inputs)});return e==="System"?r:r.filter(o=>!he.includes(o.functionName))}function ne(e){return`(${e.map(r=>{let o=r.type.match(/tuple(.*)/);return o?ne(r.components)+o[1]:r.type})})`}async function h(e,t,r,o=1,l=0){let n=`${t}(${r.map(u=>`'${u}'`).join(",")})`;try{let u=await e.estimateGas[t].apply(null,r);console.log(s.gray(`executing transaction: ${n} with nonce ${v}`));let m=e[t].apply(null,[...r,{gasLimit:u,nonce:v++,maxPriorityFeePerGas:N,maxFeePerGas:K,gasPrice:L}]).then(c=>o===0?c:c.wait(o));return d.push(m),m}catch(u){if(D&&console.error(u),l===0&&u?.message.includes("transaction already imported"))return V(w*1.1),h(e,t,r,o,l++);throw new $(`Gas estimation error for ${n}: ${u?.reason}`)}}async function re(e){let t,r=ie.join(we,e+".sol",e+".json");try{t=JSON.parse(De(r,"utf8"))}catch{throw new $(`Error reading file at ${r}`)}let o=t?.bytecode?.object;if(!o)throw new $(`No bytecode found in ${r}`);let l=t?.abi;if(!l)throw new $(`No ABI found in ${r}`);return{abi:l,bytecode:o}}async function V(e){let t=await U.getFeeData();if(t.lastBaseFeePerGas){if(!t.lastBaseFeePerGas.eq(0)&&(await P.getBalance()).eq(0))throw new $(`Attempting to deploy to a chain with non-zero base fee with an account that has no balance.
2
- If you're deploying to the Lattice testnet, you can fund your account by running 'pnpm mud faucet --address ${await P.getAddress()}'`);N=t.lastBaseFeePerGas.eq(0)?0:Math.floor(15e8*e),K=t.lastBaseFeePerGas.mul(2).add(N)}else if(t.gasPrice){if(!t.gasPrice.eq(0)&&(await P.getBalance()).eq(0))throw new $("Attempting to deploy to a chain with non-zero gas price with an account that has no balance.");L=t.gasPrice}else throw new $("Can not fetch fee data from RPC")}}function H(i){if(i.length>16)throw new Error("String does not fit into 16 bytes");let g=new Uint8Array(16);for(let p=0;p<i.length;p++)g[p]=i.charCodeAt(p);for(let p=i.length;p<16;p++)g[p]=0;return g}function We(i,g){let p=H(i),f=H(g),x=new Uint8Array(32);return x.set(p),x.set(f,16),x}function ge({functionName:i,functionArgs:g}){let p=i+g;return p===""?"0x":Be(p)}function Be(i){return k.utils.hexDataSlice(k.utils.keccak256(k.utils.toUtf8Bytes(i)),0,4)}import{forge as be,getRpcUrl as Ke,getSrcDirectory as Le}from"@latticexyz/common/foundry";import{existsSync as _e,mkdirSync as qe,readFileSync as Je,writeFileSync as z}from"fs";import{ethers as Ee}from"ethers";async function fe(i){let{result:g}=await Ee.utils.fetchJson(i,'{ "id": 42, "jsonrpc": "2.0", "method": "eth_chainId", "params": [ ] }');return Number(g)}import Oe from"glob";import{basename as Ne}from"path";function ye(i){return Oe.sync(`${i}/**/*.sol`).map(g=>({path:g,basename:Ne(g,".sol")}))}import{execa as Ve}from"execa";async function Mt(i){i.profile??=process.env.FOUNDRY_PROFILE;let{configPath:g,printConfig:p,profile:f,clean:x,skipBuild:E}=i,a=i.rpc??await Ke(f);console.log(B.bgBlue(B.whiteBright(`
3
- Deploying MUD contracts${f?" with profile "+f:""} to RPC ${a}
4
- `))),x&&await be(["clean"],{profile:f}),E||(await be(["build","--skip","test","script"],{profile:f}),await Ve("mud",["abi-ts"],{stdio:"inherit"}));let T=i?.srcDir??await Le(),j=ye(T).map(({basename:D})=>D),y=await Ue(g);p&&console.log(B.green(`
5
- Resolved config:
6
- `),JSON.stringify(y,null,2));let O=process.env.PRIVATE_KEY;if(!O)throw new Ge(`Missing PRIVATE_KEY environment variable.
7
- Run 'echo "PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" > .env'
8
- in your contracts directory to use the default anvil private key.`);let w=await me(y,j,{...i,rpc:a,privateKey:O});if(i.saveDeployment){let D=await fe(a),F=Y.join(y.deploysDirectory,D.toString());qe(F,{recursive:!0}),z(Y.join(F,"latest.json"),JSON.stringify(w,null,2)),z(Y.join(F,Date.now()+".json"),JSON.stringify(w,null,2));let b=[1337,31337],M=_e(y.worldsFile)?JSON.parse(Je(y.worldsFile,"utf-8")):{};M[D]={address:w.worldAddress,blockNumber:b.includes(D)?void 0:w.blockNumber},z(y.worldsFile,JSON.stringify(M,null,2)),console.log(B.bgGreen(B.whiteBright(`
9
- Deployment result (written to ${y.worldsFile} and ${F}):
10
- `)))}return console.log(w),w}export{fe as a,ye as b,Mt as c};
11
- //# sourceMappingURL=chunk-OJAPOMSC.js.map