@latticexyz/cli 2.0.0-main-e241c056 → 2.0.0-main-29c3f508
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.
- package/dist/index.js +0 -1
- package/dist/mud.js +13 -13
- package/dist/mud.js.map +1 -1
- package/package.json +15 -12
- package/src/commands/deploy.ts +7 -30
- package/src/commands/dev-contracts.ts +62 -137
- package/src/commands/test.ts +26 -26
- package/src/commands/trace.ts +4 -2
- package/src/debug.ts +3 -0
- package/src/deploy/assertNamespaceOwner.ts +42 -0
- package/src/deploy/common.ts +67 -0
- package/src/deploy/configToTables.ts +60 -0
- package/src/deploy/create2/README.md +9 -0
- package/src/deploy/create2/deployment.json +7 -0
- package/src/deploy/debug.ts +3 -0
- package/src/deploy/deploy.ts +84 -0
- package/src/deploy/deployWorld.ts +33 -0
- package/src/deploy/ensureContract.ts +33 -0
- package/src/deploy/ensureDeployer.ts +36 -0
- package/src/deploy/ensureFunctions.ts +62 -0
- package/src/deploy/ensureModules.ts +48 -0
- package/src/deploy/ensureSystems.ts +128 -0
- package/src/deploy/ensureTables.ts +52 -0
- package/src/deploy/ensureWorldFactory.ts +33 -0
- package/src/deploy/getFunctions.ts +58 -0
- package/src/deploy/getResourceAccess.ts +51 -0
- package/src/deploy/getResourceIds.ts +31 -0
- package/src/deploy/getSystems.ts +48 -0
- package/src/deploy/getTableValue.ts +30 -0
- package/src/deploy/getTables.ts +59 -0
- package/src/deploy/getWorldDeploy.ts +39 -0
- package/src/deploy/logsToWorldDeploy.ts +49 -0
- package/src/deploy/resolveConfig.ts +154 -0
- package/src/deploy/resourceLabel.ts +3 -0
- package/src/index.ts +1 -1
- package/src/runDeploy.ts +131 -0
- package/src/utils/modules/constants.ts +1 -2
- package/src/utils/utils/getContractData.ts +2 -5
- package/dist/chunk-K6DUKTDI.js +0 -11
- package/dist/chunk-K6DUKTDI.js.map +0 -1
- package/src/utils/deploy.ts +0 -254
- package/src/utils/deployHandler.ts +0 -93
- package/src/utils/modules/getInstallModuleCallData.ts +0 -27
- package/src/utils/modules/getUserModules.ts +0 -5
- package/src/utils/modules/types.ts +0 -14
- package/src/utils/systems/getGrantAccessCallData.ts +0 -29
- package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +0 -57
- package/src/utils/systems/getRegisterSystemCallData.ts +0 -17
- package/src/utils/systems/types.ts +0 -9
- package/src/utils/systems/utils.ts +0 -42
- package/src/utils/tables/getRegisterTableCallData.ts +0 -49
- package/src/utils/tables/getTableIds.ts +0 -18
- package/src/utils/tables/types.ts +0 -12
- package/src/utils/utils/confirmNonce.ts +0 -24
- package/src/utils/utils/deployContract.ts +0 -33
- package/src/utils/utils/fastTxExecute.ts +0 -56
- package/src/utils/utils/getChainId.ts +0 -10
- package/src/utils/utils/setInternalFeePerGas.ts +0 -49
- package/src/utils/utils/types.ts +0 -21
- package/src/utils/world.ts +0 -28
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Account, Address, Chain, Client, Transport } from "viem";
|
|
2
|
+
import { ensureDeployer } from "./ensureDeployer";
|
|
3
|
+
import { deployWorld } from "./deployWorld";
|
|
4
|
+
import { ensureTables } from "./ensureTables";
|
|
5
|
+
import { Config, ConfigInput, WorldDeploy, supportedStoreVersions, supportedWorldVersions } from "./common";
|
|
6
|
+
import { ensureSystems } from "./ensureSystems";
|
|
7
|
+
import { waitForTransactionReceipt } from "viem/actions";
|
|
8
|
+
import { getWorldDeploy } from "./getWorldDeploy";
|
|
9
|
+
import { ensureFunctions } from "./ensureFunctions";
|
|
10
|
+
import { ensureModules } from "./ensureModules";
|
|
11
|
+
import { Table } from "./configToTables";
|
|
12
|
+
import { getResourceIds } from "./getResourceIds";
|
|
13
|
+
import { assertNamespaceOwner } from "./assertNamespaceOwner";
|
|
14
|
+
|
|
15
|
+
type DeployOptions<configInput extends ConfigInput> = {
|
|
16
|
+
client: Client<Transport, Chain | undefined, Account>;
|
|
17
|
+
config: Config<configInput>;
|
|
18
|
+
worldAddress?: Address;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Given a viem client and MUD config, we attempt to introspect the world
|
|
23
|
+
* (or deploy a new one if no world address is provided) and do the minimal
|
|
24
|
+
* amount of work to make the world match the config (e.g. deploy new tables,
|
|
25
|
+
* replace systems, etc.)
|
|
26
|
+
*/
|
|
27
|
+
export async function deploy<configInput extends ConfigInput>({
|
|
28
|
+
client,
|
|
29
|
+
config,
|
|
30
|
+
worldAddress: existingWorldAddress,
|
|
31
|
+
}: DeployOptions<configInput>): Promise<WorldDeploy> {
|
|
32
|
+
await ensureDeployer(client);
|
|
33
|
+
|
|
34
|
+
const worldDeploy = existingWorldAddress
|
|
35
|
+
? await getWorldDeploy(client, existingWorldAddress)
|
|
36
|
+
: await deployWorld(client);
|
|
37
|
+
|
|
38
|
+
if (!supportedStoreVersions.includes(worldDeploy.storeVersion)) {
|
|
39
|
+
throw new Error(`Unsupported Store version: ${worldDeploy.storeVersion}`);
|
|
40
|
+
}
|
|
41
|
+
if (!supportedWorldVersions.includes(worldDeploy.worldVersion)) {
|
|
42
|
+
throw new Error(`Unsupported World version: ${worldDeploy.worldVersion}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const tables = Object.values(config.tables) as Table[];
|
|
46
|
+
const systems = Object.values(config.systems);
|
|
47
|
+
|
|
48
|
+
await assertNamespaceOwner({
|
|
49
|
+
client,
|
|
50
|
+
worldDeploy,
|
|
51
|
+
resourceIds: [...tables.map((table) => table.tableId), ...systems.map((system) => system.systemId)],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const tableTxs = await ensureTables({
|
|
55
|
+
client,
|
|
56
|
+
worldDeploy,
|
|
57
|
+
tables,
|
|
58
|
+
});
|
|
59
|
+
const systemTxs = await ensureSystems({
|
|
60
|
+
client,
|
|
61
|
+
worldDeploy,
|
|
62
|
+
systems,
|
|
63
|
+
});
|
|
64
|
+
const functionTxs = await ensureFunctions({
|
|
65
|
+
client,
|
|
66
|
+
worldDeploy,
|
|
67
|
+
functions: systems.flatMap((system) => system.functions),
|
|
68
|
+
});
|
|
69
|
+
const moduleTxs = await ensureModules({
|
|
70
|
+
client,
|
|
71
|
+
worldDeploy,
|
|
72
|
+
modules: config.modules,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const receipts = await Promise.all(
|
|
76
|
+
[...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs].map((tx) =>
|
|
77
|
+
waitForTransactionReceipt(client, { hash: tx })
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// TODO: throw if there was a revert? or attempt to re-run deploy?
|
|
82
|
+
|
|
83
|
+
return worldDeploy;
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Account, Chain, Client, Log, Transport } from "viem";
|
|
2
|
+
import { waitForTransactionReceipt } from "viem/actions";
|
|
3
|
+
import { ensureWorldFactory, worldFactory } from "./ensureWorldFactory";
|
|
4
|
+
import WorldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" };
|
|
5
|
+
import { writeContract } from "@latticexyz/common";
|
|
6
|
+
import { debug } from "./debug";
|
|
7
|
+
import { logsToWorldDeploy } from "./logsToWorldDeploy";
|
|
8
|
+
import { WorldDeploy } from "./common";
|
|
9
|
+
|
|
10
|
+
export async function deployWorld(client: Client<Transport, Chain | undefined, Account>): Promise<WorldDeploy> {
|
|
11
|
+
await ensureWorldFactory(client);
|
|
12
|
+
|
|
13
|
+
debug("deploying world");
|
|
14
|
+
const tx = await writeContract(client, {
|
|
15
|
+
chain: client.chain ?? null,
|
|
16
|
+
address: worldFactory,
|
|
17
|
+
abi: WorldFactoryAbi,
|
|
18
|
+
functionName: "deployWorld",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
debug("waiting for world deploy");
|
|
22
|
+
const receipt = await waitForTransactionReceipt(client, { hash: tx });
|
|
23
|
+
if (receipt.status !== "success") {
|
|
24
|
+
console.error("world deploy failed", receipt);
|
|
25
|
+
throw new Error("world deploy failed");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// TODO: remove type casting once https://github.com/wagmi-dev/viem/pull/1330 is merged
|
|
29
|
+
const deploy = logsToWorldDeploy(receipt.logs.map((log) => log as Log<bigint, number, false>));
|
|
30
|
+
debug("deployed world to", deploy.address, "at block", deploy.deployBlock);
|
|
31
|
+
|
|
32
|
+
return { ...deploy, stateBlock: deploy.deployBlock };
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Client, Transport, Chain, Account, concatHex, getCreate2Address, Hex } from "viem";
|
|
2
|
+
import { getBytecode } from "viem/actions";
|
|
3
|
+
import { deployer } from "./ensureDeployer";
|
|
4
|
+
import { salt } from "./common";
|
|
5
|
+
import { sendTransaction } from "@latticexyz/common";
|
|
6
|
+
import { debug } from "./debug";
|
|
7
|
+
|
|
8
|
+
export async function ensureContract({
|
|
9
|
+
client,
|
|
10
|
+
bytecode,
|
|
11
|
+
label = "contract",
|
|
12
|
+
}: {
|
|
13
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
14
|
+
readonly bytecode: Hex;
|
|
15
|
+
readonly label?: string;
|
|
16
|
+
}): Promise<readonly Hex[]> {
|
|
17
|
+
const address = getCreate2Address({ from: deployer, salt, bytecode });
|
|
18
|
+
|
|
19
|
+
const contractCode = await getBytecode(client, { address, blockTag: "pending" });
|
|
20
|
+
if (contractCode) {
|
|
21
|
+
debug("found", label, "at", address);
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
debug("deploying", label, "at", address);
|
|
26
|
+
return [
|
|
27
|
+
await sendTransaction(client, {
|
|
28
|
+
chain: client.chain ?? null,
|
|
29
|
+
to: deployer,
|
|
30
|
+
data: concatHex([salt, bytecode]),
|
|
31
|
+
}),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Account, Chain, Client, Transport } from "viem";
|
|
2
|
+
import { getBytecode, sendRawTransaction, sendTransaction, waitForTransactionReceipt } from "viem/actions";
|
|
3
|
+
import deployment from "./create2/deployment.json";
|
|
4
|
+
import { debug } from "./debug";
|
|
5
|
+
|
|
6
|
+
export const deployer = `0x${deployment.address}` as const;
|
|
7
|
+
|
|
8
|
+
export async function ensureDeployer(client: Client<Transport, Chain | undefined, Account>): Promise<void> {
|
|
9
|
+
const bytecode = await getBytecode(client, { address: deployer });
|
|
10
|
+
if (bytecode) {
|
|
11
|
+
debug("found create2 deployer at", deployer);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// send gas to signer
|
|
16
|
+
debug("sending gas for create2 deployer to signer at", deployment.signerAddress);
|
|
17
|
+
const gasTx = await sendTransaction(client, {
|
|
18
|
+
chain: client.chain ?? null,
|
|
19
|
+
to: `0x${deployment.signerAddress}`,
|
|
20
|
+
value: BigInt(deployment.gasLimit) * BigInt(deployment.gasPrice),
|
|
21
|
+
});
|
|
22
|
+
const gasReceipt = await waitForTransactionReceipt(client, { hash: gasTx });
|
|
23
|
+
if (gasReceipt.status !== "success") {
|
|
24
|
+
console.error("failed to send gas to deployer signer", gasReceipt);
|
|
25
|
+
throw new Error("failed to send gas to deployer signer");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// deploy the deployer
|
|
29
|
+
debug("deploying create2 deployer at", deployer);
|
|
30
|
+
const deployTx = await sendRawTransaction(client, { serializedTransaction: `0x${deployment.transaction}` });
|
|
31
|
+
const deployReceipt = await waitForTransactionReceipt(client, { hash: deployTx });
|
|
32
|
+
if (deployReceipt.contractAddress !== deployer) {
|
|
33
|
+
console.error("unexpected contract address for deployer", deployReceipt);
|
|
34
|
+
throw new Error("unexpected contract address for deployer");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Client, Transport, Chain, Account, Hex } from "viem";
|
|
2
|
+
import { hexToResource, writeContract } from "@latticexyz/common";
|
|
3
|
+
import { WorldDeploy, WorldFunction, worldAbi } from "./common";
|
|
4
|
+
import { debug } from "./debug";
|
|
5
|
+
import { getFunctions } from "./getFunctions";
|
|
6
|
+
|
|
7
|
+
export async function ensureFunctions({
|
|
8
|
+
client,
|
|
9
|
+
worldDeploy,
|
|
10
|
+
functions,
|
|
11
|
+
}: {
|
|
12
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
13
|
+
readonly worldDeploy: WorldDeploy;
|
|
14
|
+
readonly functions: readonly WorldFunction[];
|
|
15
|
+
}): Promise<readonly Hex[]> {
|
|
16
|
+
const worldFunctions = await getFunctions({ client, worldDeploy });
|
|
17
|
+
const worldSelectorToFunction = Object.fromEntries(worldFunctions.map((func) => [func.selector, func]));
|
|
18
|
+
|
|
19
|
+
const toSkip = functions.filter((func) => worldSelectorToFunction[func.selector]);
|
|
20
|
+
const toAdd = functions.filter((func) => !toSkip.includes(func));
|
|
21
|
+
|
|
22
|
+
if (toSkip.length) {
|
|
23
|
+
debug("functions already registered:", toSkip.map((func) => func.signature).join(", "));
|
|
24
|
+
const wrongSystem = toSkip.filter((func) => func.systemId !== worldSelectorToFunction[func.selector]?.systemId);
|
|
25
|
+
if (wrongSystem.length) {
|
|
26
|
+
console.warn(
|
|
27
|
+
"found",
|
|
28
|
+
wrongSystem.length,
|
|
29
|
+
"functions already registered but pointing at a different system ID:",
|
|
30
|
+
wrongSystem.map((func) => func.signature).join(", ")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!toAdd.length) return [];
|
|
36
|
+
|
|
37
|
+
debug("registering functions:", toAdd.map((func) => func.signature).join(", "));
|
|
38
|
+
|
|
39
|
+
return Promise.all(
|
|
40
|
+
toAdd.map((func) => {
|
|
41
|
+
const { namespace } = hexToResource(func.systemId);
|
|
42
|
+
if (namespace === "") {
|
|
43
|
+
return writeContract(client, {
|
|
44
|
+
chain: client.chain ?? null,
|
|
45
|
+
address: worldDeploy.address,
|
|
46
|
+
abi: worldAbi,
|
|
47
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
48
|
+
functionName: "registerRootFunctionSelector",
|
|
49
|
+
args: [func.systemId, func.systemFunctionSignature, func.systemFunctionSelector],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return writeContract(client, {
|
|
53
|
+
chain: client.chain ?? null,
|
|
54
|
+
address: worldDeploy.address,
|
|
55
|
+
abi: worldAbi,
|
|
56
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
57
|
+
functionName: "registerFunctionSelector",
|
|
58
|
+
args: [func.systemId, func.systemFunctionSignature],
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Client, Transport, Chain, Account, Hex } from "viem";
|
|
2
|
+
import { writeContract } from "@latticexyz/common";
|
|
3
|
+
import { Module, WorldDeploy, worldAbi } from "./common";
|
|
4
|
+
import { ensureContract } from "./ensureContract";
|
|
5
|
+
import { debug } from "./debug";
|
|
6
|
+
|
|
7
|
+
export async function ensureModules({
|
|
8
|
+
client,
|
|
9
|
+
worldDeploy,
|
|
10
|
+
modules,
|
|
11
|
+
}: {
|
|
12
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
13
|
+
readonly worldDeploy: WorldDeploy;
|
|
14
|
+
readonly modules: readonly Module[];
|
|
15
|
+
}): Promise<readonly Hex[]> {
|
|
16
|
+
if (!modules.length) return [];
|
|
17
|
+
|
|
18
|
+
// kick off contract deployments first, otherwise installing modules can fail
|
|
19
|
+
const contractTxs = await Promise.all(
|
|
20
|
+
modules.map((mod) => ensureContract({ client, bytecode: mod.bytecode, label: `${mod.name} module` }))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// then start installing modules
|
|
24
|
+
debug("installing modules:", modules.map((mod) => mod.name).join(", "));
|
|
25
|
+
const installTxs = await Promise.all(
|
|
26
|
+
modules.map((mod) =>
|
|
27
|
+
mod.installAsRoot
|
|
28
|
+
? writeContract(client, {
|
|
29
|
+
chain: client.chain ?? null,
|
|
30
|
+
address: worldDeploy.address,
|
|
31
|
+
abi: worldAbi,
|
|
32
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
33
|
+
functionName: "installRootModule",
|
|
34
|
+
args: [mod.address, mod.installData],
|
|
35
|
+
})
|
|
36
|
+
: writeContract(client, {
|
|
37
|
+
chain: client.chain ?? null,
|
|
38
|
+
address: worldDeploy.address,
|
|
39
|
+
abi: worldAbi,
|
|
40
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
41
|
+
functionName: "installModule",
|
|
42
|
+
args: [mod.address, mod.installData],
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return (await Promise.all([...contractTxs, ...installTxs])).flat();
|
|
48
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Client, Transport, Chain, Account, Hex, getAddress } from "viem";
|
|
2
|
+
import { writeContract } from "@latticexyz/common";
|
|
3
|
+
import { System, WorldDeploy, worldAbi } from "./common";
|
|
4
|
+
import { ensureContract } from "./ensureContract";
|
|
5
|
+
import { debug } from "./debug";
|
|
6
|
+
import { resourceLabel } from "./resourceLabel";
|
|
7
|
+
import { getSystems } from "./getSystems";
|
|
8
|
+
import { getResourceAccess } from "./getResourceAccess";
|
|
9
|
+
|
|
10
|
+
export async function ensureSystems({
|
|
11
|
+
client,
|
|
12
|
+
worldDeploy,
|
|
13
|
+
systems,
|
|
14
|
+
}: {
|
|
15
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
16
|
+
readonly worldDeploy: WorldDeploy;
|
|
17
|
+
readonly systems: readonly System[];
|
|
18
|
+
}): Promise<readonly Hex[]> {
|
|
19
|
+
const [worldSystems, worldAccess] = await Promise.all([
|
|
20
|
+
getSystems({ client, worldDeploy }),
|
|
21
|
+
getResourceAccess({ client, worldDeploy }),
|
|
22
|
+
]);
|
|
23
|
+
const systemIds = systems.map((system) => system.systemId);
|
|
24
|
+
const currentAccess = worldAccess.filter(({ resourceId }) => systemIds.includes(resourceId));
|
|
25
|
+
const desiredAccess = systems.flatMap((system) =>
|
|
26
|
+
system.allowedAddresses.map((address) => ({ resourceId: system.systemId, address }))
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const accessToAdd = desiredAccess.filter(
|
|
30
|
+
(access) =>
|
|
31
|
+
!currentAccess.some(
|
|
32
|
+
({ resourceId, address }) =>
|
|
33
|
+
resourceId === access.resourceId && getAddress(address) === getAddress(access.address)
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const accessToRemove = currentAccess.filter(
|
|
38
|
+
(access) =>
|
|
39
|
+
!desiredAccess.some(
|
|
40
|
+
({ resourceId, address }) =>
|
|
41
|
+
resourceId === access.resourceId && getAddress(address) === getAddress(access.address)
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// TODO: move each system access+registration to batch call to be atomic
|
|
46
|
+
|
|
47
|
+
if (accessToRemove.length) {
|
|
48
|
+
debug("revoking", accessToRemove.length, "access grants");
|
|
49
|
+
}
|
|
50
|
+
if (accessToAdd.length) {
|
|
51
|
+
debug("adding", accessToAdd.length, "access grants");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const accessTxs = await Promise.all([
|
|
55
|
+
...accessToRemove.map((access) =>
|
|
56
|
+
writeContract(client, {
|
|
57
|
+
chain: client.chain ?? null,
|
|
58
|
+
address: worldDeploy.address,
|
|
59
|
+
abi: worldAbi,
|
|
60
|
+
functionName: "revokeAccess",
|
|
61
|
+
args: [access.resourceId, access.address],
|
|
62
|
+
})
|
|
63
|
+
),
|
|
64
|
+
...accessToAdd.map((access) =>
|
|
65
|
+
writeContract(client, {
|
|
66
|
+
chain: client.chain ?? null,
|
|
67
|
+
address: worldDeploy.address,
|
|
68
|
+
abi: worldAbi,
|
|
69
|
+
functionName: "grantAccess",
|
|
70
|
+
args: [access.resourceId, access.address],
|
|
71
|
+
})
|
|
72
|
+
),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const existingSystems = systems.filter((system) =>
|
|
76
|
+
worldSystems.some(
|
|
77
|
+
(worldSystem) =>
|
|
78
|
+
worldSystem.systemId === system.systemId && getAddress(worldSystem.address) === getAddress(system.address)
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
if (existingSystems.length) {
|
|
82
|
+
debug("existing systems", existingSystems.map(resourceLabel).join(", "));
|
|
83
|
+
}
|
|
84
|
+
const existingSystemIds = existingSystems.map((system) => system.systemId);
|
|
85
|
+
|
|
86
|
+
const missingSystems = systems.filter((system) => !existingSystemIds.includes(system.systemId));
|
|
87
|
+
if (!missingSystems.length) return [];
|
|
88
|
+
|
|
89
|
+
const systemsToUpgrade = missingSystems.filter((system) =>
|
|
90
|
+
worldSystems.some(
|
|
91
|
+
(worldSystem) =>
|
|
92
|
+
worldSystem.systemId === system.systemId && getAddress(worldSystem.address) !== getAddress(system.address)
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
if (systemsToUpgrade.length) {
|
|
96
|
+
debug("upgrading systems", systemsToUpgrade.map(resourceLabel).join(", "));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const systemsToAdd = missingSystems.filter(
|
|
100
|
+
(system) => !worldSystems.some((worldSystem) => worldSystem.systemId === system.systemId)
|
|
101
|
+
);
|
|
102
|
+
if (systemsToAdd.length) {
|
|
103
|
+
debug("registering new systems", systemsToAdd.map(resourceLabel).join(", "));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// kick off contract deployments first, otherwise registering systems can fail
|
|
107
|
+
const contractTxs = await Promise.all(
|
|
108
|
+
missingSystems.map((system) =>
|
|
109
|
+
ensureContract({ client, bytecode: system.bytecode, label: `${resourceLabel(system)} system` })
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// then start registering systems
|
|
114
|
+
const registerTxs = await Promise.all(
|
|
115
|
+
missingSystems.map((system) =>
|
|
116
|
+
writeContract(client, {
|
|
117
|
+
chain: client.chain ?? null,
|
|
118
|
+
address: worldDeploy.address,
|
|
119
|
+
abi: worldAbi,
|
|
120
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
121
|
+
functionName: "registerSystem",
|
|
122
|
+
args: [system.systemId, system.address, system.allowAll],
|
|
123
|
+
})
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
return (await Promise.all([...accessTxs, ...contractTxs, ...registerTxs])).flat();
|
|
128
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Client, Transport, Chain, Account, Hex } from "viem";
|
|
2
|
+
import { Table } from "./configToTables";
|
|
3
|
+
import { writeContract } from "@latticexyz/common";
|
|
4
|
+
import { WorldDeploy, worldAbi } from "./common";
|
|
5
|
+
import { valueSchemaToFieldLayoutHex, keySchemaToHex, valueSchemaToHex } from "@latticexyz/protocol-parser";
|
|
6
|
+
import { debug } from "./debug";
|
|
7
|
+
import { resourceLabel } from "./resourceLabel";
|
|
8
|
+
import { getTables } from "./getTables";
|
|
9
|
+
|
|
10
|
+
export async function ensureTables({
|
|
11
|
+
client,
|
|
12
|
+
worldDeploy,
|
|
13
|
+
tables,
|
|
14
|
+
}: {
|
|
15
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
16
|
+
readonly worldDeploy: WorldDeploy;
|
|
17
|
+
readonly tables: readonly Table[];
|
|
18
|
+
}): Promise<readonly Hex[]> {
|
|
19
|
+
const worldTables = await getTables({ client, worldDeploy });
|
|
20
|
+
const worldTableIds = worldTables.map((table) => table.tableId);
|
|
21
|
+
|
|
22
|
+
const existingTables = tables.filter((table) => worldTableIds.includes(table.tableId));
|
|
23
|
+
if (existingTables.length) {
|
|
24
|
+
debug("existing tables", existingTables.map(resourceLabel).join(", "));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const missingTables = tables.filter((table) => !worldTableIds.includes(table.tableId));
|
|
28
|
+
if (missingTables.length) {
|
|
29
|
+
debug("registering tables", missingTables.map(resourceLabel).join(", "));
|
|
30
|
+
return await Promise.all(
|
|
31
|
+
missingTables.map((table) =>
|
|
32
|
+
writeContract(client, {
|
|
33
|
+
chain: client.chain ?? null,
|
|
34
|
+
address: worldDeploy.address,
|
|
35
|
+
abi: worldAbi,
|
|
36
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
|
37
|
+
functionName: "registerTable",
|
|
38
|
+
args: [
|
|
39
|
+
table.tableId,
|
|
40
|
+
valueSchemaToFieldLayoutHex(table.valueSchema),
|
|
41
|
+
keySchemaToHex(table.keySchema),
|
|
42
|
+
valueSchemaToHex(table.valueSchema),
|
|
43
|
+
Object.keys(table.keySchema),
|
|
44
|
+
Object.keys(table.valueSchema),
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import coreModuleBuild from "@latticexyz/world/out/CoreModule.sol/CoreModule.json" assert { type: "json" };
|
|
2
|
+
import worldFactoryBuild from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.json" assert { type: "json" };
|
|
3
|
+
import { Client, Transport, Chain, Account, Hex, parseAbi, getCreate2Address, encodeDeployData } from "viem";
|
|
4
|
+
import { ensureContract } from "./ensureContract";
|
|
5
|
+
import { deployer } from "./ensureDeployer";
|
|
6
|
+
import { salt } from "./common";
|
|
7
|
+
|
|
8
|
+
const coreModuleBytecode = encodeDeployData({
|
|
9
|
+
bytecode: coreModuleBuild.bytecode.object as Hex,
|
|
10
|
+
abi: [],
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const coreModule = getCreate2Address({ from: deployer, bytecode: coreModuleBytecode, salt });
|
|
14
|
+
|
|
15
|
+
const worldFactoryBytecode = encodeDeployData({
|
|
16
|
+
bytecode: worldFactoryBuild.bytecode.object as Hex,
|
|
17
|
+
abi: parseAbi(["constructor(address)"]),
|
|
18
|
+
args: [coreModule],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const worldFactory = getCreate2Address({ from: deployer, bytecode: worldFactoryBytecode, salt });
|
|
22
|
+
|
|
23
|
+
export async function ensureWorldFactory(
|
|
24
|
+
client: Client<Transport, Chain | undefined, Account>
|
|
25
|
+
): Promise<readonly Hex[]> {
|
|
26
|
+
// WorldFactory constructor doesn't call CoreModule, only sets its address, so we can do these in parallel since the address is deterministic
|
|
27
|
+
return (
|
|
28
|
+
await Promise.all([
|
|
29
|
+
ensureContract({ client, bytecode: coreModuleBytecode, label: "core module" }),
|
|
30
|
+
ensureContract({ client, bytecode: worldFactoryBytecode, label: "world factory" }),
|
|
31
|
+
])
|
|
32
|
+
).flat();
|
|
33
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Client, getFunctionSelector, parseAbiItem } from "viem";
|
|
2
|
+
import { WorldDeploy, WorldFunction, worldTables } from "./common";
|
|
3
|
+
import { debug } from "./debug";
|
|
4
|
+
import { storeSetRecordEvent } from "@latticexyz/store";
|
|
5
|
+
import { getLogs } from "viem/actions";
|
|
6
|
+
import { decodeValueArgs } from "@latticexyz/protocol-parser";
|
|
7
|
+
import { getTableValue } from "./getTableValue";
|
|
8
|
+
import { hexToResource } from "@latticexyz/common";
|
|
9
|
+
|
|
10
|
+
export async function getFunctions({
|
|
11
|
+
client,
|
|
12
|
+
worldDeploy,
|
|
13
|
+
}: {
|
|
14
|
+
readonly client: Client;
|
|
15
|
+
readonly worldDeploy: WorldDeploy;
|
|
16
|
+
}): Promise<readonly WorldFunction[]> {
|
|
17
|
+
// This assumes we only use `FunctionSelectors._set(...)`, which is true as of this writing.
|
|
18
|
+
debug("looking up function signatures for", worldDeploy.address);
|
|
19
|
+
const logs = await getLogs(client, {
|
|
20
|
+
strict: true,
|
|
21
|
+
fromBlock: worldDeploy.deployBlock,
|
|
22
|
+
toBlock: worldDeploy.stateBlock,
|
|
23
|
+
address: worldDeploy.address,
|
|
24
|
+
event: parseAbiItem(storeSetRecordEvent),
|
|
25
|
+
args: { tableId: worldTables.world_FunctionSignatures.tableId },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const signatures = logs.map((log) => {
|
|
29
|
+
const value = decodeValueArgs(worldTables.world_FunctionSignatures.valueSchema, log.args);
|
|
30
|
+
return value.functionSignature;
|
|
31
|
+
});
|
|
32
|
+
debug("found", signatures.length, "function signatures for", worldDeploy.address);
|
|
33
|
+
|
|
34
|
+
// TODO: parallelize with a bulk getRecords
|
|
35
|
+
const functions = await Promise.all(
|
|
36
|
+
signatures.map(async (signature) => {
|
|
37
|
+
const selector = getFunctionSelector(signature);
|
|
38
|
+
const { systemId, systemFunctionSelector } = await getTableValue({
|
|
39
|
+
client,
|
|
40
|
+
worldDeploy,
|
|
41
|
+
table: worldTables.world_FunctionSelectors,
|
|
42
|
+
key: { functionSelector: selector },
|
|
43
|
+
});
|
|
44
|
+
const { namespace, name } = hexToResource(systemId);
|
|
45
|
+
// TODO: find away around undoing contract logic (https://github.com/latticexyz/mud/issues/1708)
|
|
46
|
+
const systemFunctionSignature = namespace === "" ? signature : signature.replace(`${namespace}_${name}_`, "");
|
|
47
|
+
return {
|
|
48
|
+
signature,
|
|
49
|
+
selector,
|
|
50
|
+
systemId,
|
|
51
|
+
systemFunctionSignature,
|
|
52
|
+
systemFunctionSelector,
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return functions;
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Client, parseAbiItem, Hex, Address, getAddress } from "viem";
|
|
2
|
+
import { WorldDeploy, worldTables } from "./common";
|
|
3
|
+
import { debug } from "./debug";
|
|
4
|
+
import { storeSpliceStaticDataEvent } from "@latticexyz/store";
|
|
5
|
+
import { getLogs } from "viem/actions";
|
|
6
|
+
import { decodeKey } from "@latticexyz/protocol-parser";
|
|
7
|
+
import { getTableValue } from "./getTableValue";
|
|
8
|
+
|
|
9
|
+
export async function getResourceAccess({
|
|
10
|
+
client,
|
|
11
|
+
worldDeploy,
|
|
12
|
+
}: {
|
|
13
|
+
readonly client: Client;
|
|
14
|
+
readonly worldDeploy: WorldDeploy;
|
|
15
|
+
}): Promise<readonly { readonly resourceId: Hex; readonly address: Address }[]> {
|
|
16
|
+
// This assumes we only use `ResourceAccess._set(...)`, which is true as of this writing.
|
|
17
|
+
// TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state
|
|
18
|
+
|
|
19
|
+
debug("looking up resource access for", worldDeploy.address);
|
|
20
|
+
|
|
21
|
+
const logs = await getLogs(client, {
|
|
22
|
+
strict: true,
|
|
23
|
+
fromBlock: worldDeploy.deployBlock,
|
|
24
|
+
toBlock: worldDeploy.stateBlock,
|
|
25
|
+
address: worldDeploy.address,
|
|
26
|
+
// our usage of `ResourceAccess._set(...)` emits a splice instead of set record
|
|
27
|
+
// TODO: https://github.com/latticexyz/mud/issues/479
|
|
28
|
+
event: parseAbiItem(storeSpliceStaticDataEvent),
|
|
29
|
+
args: { tableId: worldTables.world_ResourceAccess.tableId },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const keys = logs.map((log) => decodeKey(worldTables.world_ResourceAccess.keySchema, log.args.keyTuple));
|
|
33
|
+
|
|
34
|
+
const access = (
|
|
35
|
+
await Promise.all(
|
|
36
|
+
keys.map(
|
|
37
|
+
async (key) =>
|
|
38
|
+
[key, await getTableValue({ client, worldDeploy, table: worldTables.world_ResourceAccess, key })] as const
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
.filter(([, value]) => value.access)
|
|
43
|
+
.map(([key]) => ({
|
|
44
|
+
resourceId: key.resourceId,
|
|
45
|
+
address: getAddress(key.caller),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
debug("found", access.length, "resource<>address access pairs");
|
|
49
|
+
|
|
50
|
+
return access;
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Client, parseAbiItem, Hex } from "viem";
|
|
2
|
+
import { getLogs } from "viem/actions";
|
|
3
|
+
import { storeSpliceStaticDataEvent } from "@latticexyz/store";
|
|
4
|
+
import { WorldDeploy, storeTables } from "./common";
|
|
5
|
+
import { debug } from "./debug";
|
|
6
|
+
|
|
7
|
+
export async function getResourceIds({
|
|
8
|
+
client,
|
|
9
|
+
worldDeploy,
|
|
10
|
+
}: {
|
|
11
|
+
readonly client: Client;
|
|
12
|
+
readonly worldDeploy: WorldDeploy;
|
|
13
|
+
}): Promise<readonly Hex[]> {
|
|
14
|
+
// This assumes we only use `ResourceIds._setExists(true)`, which is true as of this writing.
|
|
15
|
+
// TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state
|
|
16
|
+
|
|
17
|
+
debug("looking up resource IDs for", worldDeploy.address);
|
|
18
|
+
const logs = await getLogs(client, {
|
|
19
|
+
strict: true,
|
|
20
|
+
address: worldDeploy.address,
|
|
21
|
+
fromBlock: worldDeploy.deployBlock,
|
|
22
|
+
toBlock: worldDeploy.stateBlock,
|
|
23
|
+
event: parseAbiItem(storeSpliceStaticDataEvent),
|
|
24
|
+
args: { tableId: storeTables.store_ResourceIds.tableId },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const resourceIds = logs.map((log) => log.args.keyTuple[0]);
|
|
28
|
+
debug("found", resourceIds.length, "resource IDs for", worldDeploy.address);
|
|
29
|
+
|
|
30
|
+
return resourceIds;
|
|
31
|
+
}
|