@latticexyz/cli 1.40.0 → 1.41.1-alpha.0
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/LICENSE +21 -0
- package/dist/chunk-ATAWDHWC.js +67 -0
- package/dist/{chunk-6AQ6LFVZ.js → chunk-J4DJQNIC.js} +743 -103
- package/dist/chunk-KD354QKC.js +23039 -0
- package/dist/{chunk-S3V3XX7N.js → chunk-SLIMIO4Z.js} +1 -1
- package/dist/config/index.d.ts +746 -8
- package/dist/config/index.js +63 -17
- package/dist/index.d.ts +1 -2
- package/dist/index.js +14 -10
- package/dist/mud.js +1055 -49
- package/dist/utils/deprecated/index.js +2 -2
- package/dist/utils/index.d.ts +56 -7
- package/dist/utils/index.js +17 -3
- package/package.json +16 -11
- package/src/commands/deploy-v2.ts +96 -0
- package/src/commands/deprecated/call-system.ts +1 -1
- package/src/commands/deprecated/deploy-contracts.ts +1 -1
- package/src/commands/deprecated/test.ts +9 -6
- package/src/commands/deprecated/trace.ts +1 -1
- package/src/commands/gas-report.ts +1 -1
- package/src/commands/index.ts +4 -0
- package/src/commands/tablegen.ts +4 -18
- package/src/commands/worldgen.ts +55 -0
- package/src/config/commonSchemas.ts +19 -5
- package/src/config/dynamicResolution.ts +49 -0
- package/src/config/index.ts +20 -0
- package/src/config/loadStoreConfig.ts +3 -89
- package/src/config/parseStoreConfig.test-d.ts +40 -0
- package/src/config/parseStoreConfig.ts +314 -0
- package/src/config/validation.ts +71 -0
- package/src/config/world/index.ts +4 -0
- package/src/config/world/loadWorldConfig.test-d.ts +11 -0
- package/src/config/world/loadWorldConfig.ts +26 -0
- package/src/config/world/parseWorldConfig.ts +55 -0
- package/src/config/world/resolveWorldConfig.ts +80 -0
- package/src/config/world/userTypes.ts +72 -0
- package/src/index.ts +13 -5
- package/src/mud.ts +4 -0
- package/src/render-solidity/common.ts +138 -0
- package/src/render-solidity/field.ts +137 -0
- package/src/render-solidity/index.ts +10 -0
- package/src/render-solidity/record.ts +154 -0
- package/src/render-solidity/renderSystemInterface.ts +31 -0
- package/src/render-solidity/renderTable.ts +164 -0
- package/src/render-solidity/renderTypeHelpers.ts +99 -0
- package/src/render-solidity/renderTypes.ts +19 -0
- package/src/render-solidity/renderTypesFromConfig.ts +13 -0
- package/src/render-solidity/renderWorld.ts +24 -0
- package/src/{render-table/renderTablesFromConfig.ts → render-solidity/tableOptions.ts} +45 -37
- package/src/render-solidity/tablegen.ts +33 -0
- package/src/render-solidity/types.ts +110 -0
- package/src/render-solidity/userType.ts +132 -0
- package/src/render-solidity/worldgen.ts +60 -0
- package/src/utils/contractToInterface.ts +130 -0
- package/src/utils/deploy-v2.ts +512 -0
- package/src/utils/deprecated/build.ts +1 -1
- package/src/utils/deprecated/typegen.ts +1 -1
- package/src/utils/errors.ts +12 -2
- package/src/utils/execLog.ts +22 -0
- package/src/utils/formatAndWrite.ts +12 -0
- package/src/utils/foundry.ts +94 -0
- package/src/utils/getChainId.ts +10 -0
- package/src/utils/index.ts +2 -1
- package/src/utils/typeUtils.ts +17 -0
- package/dist/chunk-B6VWCGHZ.js +0 -199
- package/dist/chunk-JKAA3WMC.js +0 -55
- package/dist/chunk-JNGSW4AP.js +0 -493
- package/dist/chunk-PJ6GS2R4.js +0 -22
- package/dist/chunk-UC3QPOON.js +0 -35
- package/dist/loadStoreConfig-37f99136.d.ts +0 -164
- package/dist/render-table/index.d.ts +0 -29
- package/dist/render-table/index.js +0 -24
- package/dist/renderTable-9e6410c5.d.ts +0 -72
- package/src/config/loadStoreConfig.test-d.ts +0 -11
- package/src/render-table/common.ts +0 -67
- package/src/render-table/field.ts +0 -132
- package/src/render-table/index.ts +0 -6
- package/src/render-table/record.ts +0 -176
- package/src/render-table/renderTable.ts +0 -109
- package/src/render-table/types.ts +0 -51
- package/src/utils/forgeConfig.ts +0 -45
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { UnrecognizedSystemErrorFactory } from "../../utils/errors.js";
|
|
2
|
+
import { ParsedWorldConfig } from "./parseWorldConfig.js";
|
|
3
|
+
import { SystemUserConfig } from "./userTypes.js";
|
|
4
|
+
|
|
5
|
+
export type ResolvedSystemConfig = ReturnType<typeof resolveSystemConfig>;
|
|
6
|
+
|
|
7
|
+
export type ResolvedWorldConfig = ReturnType<typeof resolveWorldConfig>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the world config by combining the default and overridden system configs,
|
|
11
|
+
* filtering out excluded systems, validate system names refer to existing contracts, and
|
|
12
|
+
* splitting the access list into addresses and system names.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveWorldConfig(config: ParsedWorldConfig, existingContracts?: string[]) {
|
|
15
|
+
// Include contract names ending in "System", but not the base "System" contract, and not Interfaces
|
|
16
|
+
const defaultSystemNames =
|
|
17
|
+
existingContracts?.filter((name) => name.endsWith("System") && name !== "System" && !name.match(/^I[A-Z]/)) ?? [];
|
|
18
|
+
const overriddenSystemNames = Object.keys(config.overrideSystems);
|
|
19
|
+
|
|
20
|
+
// Validate every key in overrideSystems refers to an existing system contract (and is not called "World")
|
|
21
|
+
if (existingContracts) {
|
|
22
|
+
for (const systemName of overriddenSystemNames) {
|
|
23
|
+
if (!existingContracts.includes(systemName) || systemName === "World") {
|
|
24
|
+
throw UnrecognizedSystemErrorFactory(["overrideSystems", systemName], systemName);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Combine the default and overridden system names and filter out excluded systems
|
|
30
|
+
const systemNames = [...new Set([...defaultSystemNames, ...overriddenSystemNames])].filter(
|
|
31
|
+
(name) => !config.excludeSystems.includes(name)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Resolve the config
|
|
35
|
+
const resolvedSystems: Record<string, ResolvedSystemConfig> = systemNames.reduce((acc, systemName) => {
|
|
36
|
+
return {
|
|
37
|
+
...acc,
|
|
38
|
+
[systemName]: resolveSystemConfig(systemName, config.overrideSystems[systemName], existingContracts),
|
|
39
|
+
};
|
|
40
|
+
}, {});
|
|
41
|
+
|
|
42
|
+
const { overrideSystems, excludeSystems, ...otherConfig } = config;
|
|
43
|
+
return { ...otherConfig, systems: resolvedSystems };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the system config by combining the default and overridden system configs,
|
|
48
|
+
* @param systemName name of the system
|
|
49
|
+
* @param config optional SystemConfig object, if none is provided the default config is used
|
|
50
|
+
* @param existingContracts optional list of existing contract names, used to validate system names in the access list. If not provided, no validation is performed.
|
|
51
|
+
* @returns ResolvedSystemConfig object
|
|
52
|
+
* Default value for fileSelector is `systemName`
|
|
53
|
+
* Default value for registerFunctionSelectors is true
|
|
54
|
+
* Default value for openAccess is true
|
|
55
|
+
* Default value for accessListAddresses is []
|
|
56
|
+
* Default value for accessListSystems is []
|
|
57
|
+
*/
|
|
58
|
+
export function resolveSystemConfig(systemName: string, config?: SystemUserConfig, existingContracts?: string[]) {
|
|
59
|
+
const fileSelector = config?.fileSelector ?? systemName;
|
|
60
|
+
const registerFunctionSelectors = config?.registerFunctionSelectors ?? true;
|
|
61
|
+
const openAccess = config?.openAccess ?? true;
|
|
62
|
+
const accessListAddresses: string[] = [];
|
|
63
|
+
const accessListSystems: string[] = [];
|
|
64
|
+
const accessList = config && !config.openAccess ? config.accessList : [];
|
|
65
|
+
|
|
66
|
+
// Split the access list into addresses and system names
|
|
67
|
+
for (const accessListItem of accessList) {
|
|
68
|
+
if (accessListItem.startsWith("0x")) {
|
|
69
|
+
accessListAddresses.push(accessListItem);
|
|
70
|
+
} else {
|
|
71
|
+
// Validate every system refers to an existing system contract
|
|
72
|
+
if (existingContracts && !existingContracts.includes(accessListItem)) {
|
|
73
|
+
throw UnrecognizedSystemErrorFactory(["overrideSystems", systemName, "accessList"], accessListItem);
|
|
74
|
+
}
|
|
75
|
+
accessListSystems.push(accessListItem);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { fileSelector, registerFunctionSelectors, openAccess, accessListAddresses, accessListSystems };
|
|
80
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { DynamicResolution } from "../dynamicResolution.js";
|
|
2
|
+
|
|
3
|
+
// zod doesn't preserve doc comments
|
|
4
|
+
export type SystemUserConfig =
|
|
5
|
+
| {
|
|
6
|
+
/** The full resource selector consists of namespace and fileSelector */
|
|
7
|
+
fileSelector?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Register function selectors for the system in the World.
|
|
10
|
+
* Defaults to true.
|
|
11
|
+
* Note:
|
|
12
|
+
* - For root systems all World function selectors will correspond to the system's function selectors.
|
|
13
|
+
* - For non-root systems, the World function selectors will be <namespace>_<system>_<function>.
|
|
14
|
+
*/
|
|
15
|
+
registerFunctionSelectors?: boolean;
|
|
16
|
+
} & (
|
|
17
|
+
| {
|
|
18
|
+
/** If openAccess is true, any address can call the system */
|
|
19
|
+
openAccess: true;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
/** If openAccess is false, only the addresses or systems in `access` can call the system */
|
|
23
|
+
openAccess: false;
|
|
24
|
+
/** An array of addresses or system names that can access the system */
|
|
25
|
+
accessList: string[];
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export type ValueWithType = {
|
|
30
|
+
value: string | number | Uint8Array;
|
|
31
|
+
type: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ModuleConfig = {
|
|
35
|
+
/** The name of the module */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Should this module be installed as a root module? */
|
|
38
|
+
root?: boolean;
|
|
39
|
+
/** Arguments to be passed to the module's install method */
|
|
40
|
+
args?: (ValueWithType | DynamicResolution)[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// zod doesn't preserve doc comments
|
|
44
|
+
export interface WorldUserConfig {
|
|
45
|
+
/** The namespace to register tables and systems at. Defaults to the root namespace (empty string) */
|
|
46
|
+
namespace?: string;
|
|
47
|
+
/** The name of the World contract to deploy. If no name is provided, a vanilla World is deployed */
|
|
48
|
+
worldContractName?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Contracts named *System will be deployed by default
|
|
51
|
+
* as public systems at `namespace/ContractName`, unless overridden
|
|
52
|
+
*
|
|
53
|
+
* The key is the system name (capitalized).
|
|
54
|
+
* The value is a SystemConfig object.
|
|
55
|
+
*/
|
|
56
|
+
overrideSystems?: Record<string, SystemUserConfig>;
|
|
57
|
+
/** Systems to exclude from automatic deployment */
|
|
58
|
+
excludeSystems?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Script to execute after the deployment is complete (Default "PostDeploy").
|
|
61
|
+
* Script must be placed in the forge scripts directory (see foundry.toml) and have a ".s.sol" extension.
|
|
62
|
+
*/
|
|
63
|
+
postDeployScript?: string;
|
|
64
|
+
/** Directory to write the deployment info to (Default "./deploys") */
|
|
65
|
+
deploysDirectory?: string;
|
|
66
|
+
/** Directory to output system and world interfaces of `worldgen` (Default "world") */
|
|
67
|
+
worldgenDirectory?: string;
|
|
68
|
+
/** Path for world package imports. Default is "@latticexyz/world/src/" */
|
|
69
|
+
worldImportPath?: string;
|
|
70
|
+
/** Modules to in the World */
|
|
71
|
+
modules?: ModuleConfig[];
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export { loadStoreConfig } from "./config/loadStoreConfig.js";
|
|
2
|
+
export { parseStoreConfig } from "./config/parseStoreConfig.js";
|
|
3
|
+
export { loadWorldConfig, resolveWorldConfig, parseWorldConfig } from "./config/world/index.js";
|
|
4
|
+
export { resolveTableId } from "./config/dynamicResolution.js";
|
|
4
5
|
|
|
5
|
-
export type {
|
|
6
|
+
export type {
|
|
7
|
+
StoreUserConfig,
|
|
8
|
+
StoreConfig,
|
|
9
|
+
WorldUserConfig,
|
|
10
|
+
ResolvedWorldConfig,
|
|
11
|
+
MUDUserConfig,
|
|
12
|
+
MUDConfig,
|
|
13
|
+
} from "./config/index.js";
|
|
6
14
|
|
|
7
|
-
export {
|
|
15
|
+
export { storeConfig, mudConfig } from "./config/index.js";
|
package/src/mud.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { hideBin } from "yargs/helpers";
|
|
|
5
5
|
import { commands } from "./commands/index.js";
|
|
6
6
|
import { logError } from "./utils/errors.js";
|
|
7
7
|
|
|
8
|
+
// Load .env file into process.env
|
|
9
|
+
import * as dotenv from "dotenv";
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
8
12
|
yargs(hideBin(process.argv))
|
|
9
13
|
// Explicit name to display in help (by default it's the entry file, which may not be "mud" for e.g. ts-node)
|
|
10
14
|
.scriptName("mud")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { ImportDatum, RenderTableOptions, RenderTableType, StaticResourceData } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const renderedSolidityHeader = `// SPDX-License-Identifier: MIT
|
|
5
|
+
pragma solidity >=0.8.0;
|
|
6
|
+
|
|
7
|
+
/* Autogenerated file. Do not edit manually. */`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders a list of lines
|
|
11
|
+
*/
|
|
12
|
+
export function renderList<T>(list: T[], renderItem: (item: T, index: number) => string) {
|
|
13
|
+
return internalRenderList("", list, renderItem);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders a comma-separated list of arguments for solidity functions, ignoring empty and undefined ones
|
|
18
|
+
*/
|
|
19
|
+
export function renderArguments(args: (string | undefined)[]) {
|
|
20
|
+
const filteredArgs = args.filter((arg) => arg !== undefined && arg !== "") as string[];
|
|
21
|
+
return internalRenderList(",", filteredArgs, (arg) => arg);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function renderCommonData({
|
|
25
|
+
staticResourceData,
|
|
26
|
+
primaryKeys,
|
|
27
|
+
}: Pick<RenderTableOptions, "staticResourceData" | "primaryKeys">) {
|
|
28
|
+
// static resource means static tableId as well, and no tableId arguments
|
|
29
|
+
const _tableId = staticResourceData ? "" : "_tableId";
|
|
30
|
+
const _typedTableId = staticResourceData ? "" : "uint256 _tableId";
|
|
31
|
+
|
|
32
|
+
const _keyArgs = renderArguments(primaryKeys.map(({ name }) => name));
|
|
33
|
+
const _typedKeyArgs = renderArguments(primaryKeys.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`));
|
|
34
|
+
|
|
35
|
+
const _primaryKeysDefinition = `
|
|
36
|
+
bytes32[] memory _primaryKeys = new bytes32[](${primaryKeys.length});
|
|
37
|
+
${renderList(
|
|
38
|
+
primaryKeys,
|
|
39
|
+
(primaryKey, index) => `_primaryKeys[${index}] = ${renderValueTypeToBytes32(primaryKey.name, primaryKey)};`
|
|
40
|
+
)}
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
_tableId,
|
|
45
|
+
_typedTableId,
|
|
46
|
+
_keyArgs,
|
|
47
|
+
_typedKeyArgs,
|
|
48
|
+
_primaryKeysDefinition,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** For 2 paths which are relative to a common root, create a relative import path from one to another */
|
|
53
|
+
export function solidityRelativeImportPath(fromPath: string, usedInPath: string) {
|
|
54
|
+
// 1st "./" must be added because path strips it,
|
|
55
|
+
// but solidity expects it unless there's "../" ("./../" is fine).
|
|
56
|
+
// 2nd and 3rd "./" forcefully avoid absolute paths (everything is relative to `src`).
|
|
57
|
+
return "./" + path.relative("./" + usedInPath, "./" + fromPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Aggregates, deduplicates and renders imports for symbols per path.
|
|
62
|
+
* Identical symbols from different paths are NOT handled, they should be checked before rendering.
|
|
63
|
+
*/
|
|
64
|
+
export function renderImports(imports: ImportDatum[]) {
|
|
65
|
+
// Aggregate symbols by import path, also deduplicating them
|
|
66
|
+
const aggregatedImports = new Map<string, Set<string>>();
|
|
67
|
+
for (const { symbol, fromPath, usedInPath } of imports) {
|
|
68
|
+
const path = solidityRelativeImportPath(fromPath, usedInPath);
|
|
69
|
+
if (!aggregatedImports.has(path)) {
|
|
70
|
+
aggregatedImports.set(path, new Set());
|
|
71
|
+
}
|
|
72
|
+
aggregatedImports.get(path)?.add(symbol);
|
|
73
|
+
}
|
|
74
|
+
// Render imports
|
|
75
|
+
const renderedImports = [];
|
|
76
|
+
for (const [path, symbols] of aggregatedImports) {
|
|
77
|
+
const renderedSymbols = [...symbols].join(", ");
|
|
78
|
+
renderedImports.push(`import { ${renderedSymbols} } from "${path}";`);
|
|
79
|
+
}
|
|
80
|
+
return renderedImports.join("\n");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function renderWithStore(
|
|
84
|
+
storeArgument: boolean,
|
|
85
|
+
callback: (
|
|
86
|
+
_typedStore: string | undefined,
|
|
87
|
+
_store: string,
|
|
88
|
+
_commentSuffix: string,
|
|
89
|
+
_untypedStore: string | undefined
|
|
90
|
+
) => string
|
|
91
|
+
) {
|
|
92
|
+
let result = "";
|
|
93
|
+
result += callback(undefined, "StoreSwitch", "", undefined);
|
|
94
|
+
|
|
95
|
+
if (storeArgument) {
|
|
96
|
+
result += "\n" + callback("IStore _store", "_store", " (using the specified store)", "_store");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function renderTableId(staticResourceData: StaticResourceData) {
|
|
103
|
+
const hardcodedTableId = `uint256(bytes32(abi.encodePacked(bytes16("${staticResourceData.namespace}"), bytes16("${staticResourceData.fileSelector}"))))`;
|
|
104
|
+
|
|
105
|
+
const tableIdDefinition = `
|
|
106
|
+
uint256 constant _tableId = ${hardcodedTableId};
|
|
107
|
+
uint256 constant ${staticResourceData.tableIdName} = _tableId;
|
|
108
|
+
`;
|
|
109
|
+
return {
|
|
110
|
+
hardcodedTableId,
|
|
111
|
+
tableIdDefinition,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function renderValueTypeToBytes32(name: string, { staticByteLength, typeUnwrap, internalTypeId }: RenderTableType) {
|
|
116
|
+
const bits = staticByteLength * 8;
|
|
117
|
+
const innerText = `${typeUnwrap}(${name})`;
|
|
118
|
+
|
|
119
|
+
if (internalTypeId.match(/^uint\d{1,3}$/)) {
|
|
120
|
+
return `bytes32(uint256(${innerText}))`;
|
|
121
|
+
} else if (internalTypeId.match(/^int\d{1,3}$/)) {
|
|
122
|
+
return `bytes32(uint256(uint${bits}(${innerText})))`;
|
|
123
|
+
} else if (internalTypeId.match(/^bytes\d{1,2}$/)) {
|
|
124
|
+
return `bytes32(${innerText})`;
|
|
125
|
+
} else if (internalTypeId === "address") {
|
|
126
|
+
return `bytes32(bytes20(${innerText}))`;
|
|
127
|
+
} else if (internalTypeId === "bool") {
|
|
128
|
+
return `_boolToBytes32(${innerText})`;
|
|
129
|
+
} else {
|
|
130
|
+
throw new Error(`Unknown value type id ${internalTypeId}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function internalRenderList<T>(lineTerminator: string, list: T[], renderItem: (item: T, index: number) => string) {
|
|
135
|
+
return list
|
|
136
|
+
.map((item, index) => renderItem(item, index) + (index === list.length - 1 ? "" : lineTerminator))
|
|
137
|
+
.join("\n");
|
|
138
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { renderArguments, renderCommonData, renderWithStore } from "./common.js";
|
|
2
|
+
import { RenderTableField, RenderTableOptions, RenderTableType } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export function renderFieldMethods(options: RenderTableOptions) {
|
|
5
|
+
const storeArgument = options.storeArgument;
|
|
6
|
+
const { _typedTableId, _typedKeyArgs, _primaryKeysDefinition } = renderCommonData(options);
|
|
7
|
+
|
|
8
|
+
let result = "";
|
|
9
|
+
for (const [index, field] of options.fields.entries()) {
|
|
10
|
+
const _typedFieldName = `${field.typeWithLocation} ${field.name}`;
|
|
11
|
+
|
|
12
|
+
result += renderWithStore(
|
|
13
|
+
storeArgument,
|
|
14
|
+
(_typedStore, _store, _commentSuffix) => `
|
|
15
|
+
/** Get ${field.name}${_commentSuffix} */
|
|
16
|
+
function get${field.methodNameSuffix}(${renderArguments([
|
|
17
|
+
_typedStore,
|
|
18
|
+
_typedTableId,
|
|
19
|
+
_typedKeyArgs,
|
|
20
|
+
])}) internal view returns (${_typedFieldName}) {
|
|
21
|
+
${_primaryKeysDefinition}
|
|
22
|
+
bytes memory _blob = ${_store}.getField(_tableId, _primaryKeys, ${index});
|
|
23
|
+
return ${renderDecodeFieldSingle(field)};
|
|
24
|
+
}
|
|
25
|
+
`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
result += renderWithStore(
|
|
29
|
+
storeArgument,
|
|
30
|
+
(_typedStore, _store, _commentSuffix) => `
|
|
31
|
+
/** Set ${field.name}${_commentSuffix} */
|
|
32
|
+
function set${field.methodNameSuffix}(${renderArguments([
|
|
33
|
+
_typedStore,
|
|
34
|
+
_typedTableId,
|
|
35
|
+
_typedKeyArgs,
|
|
36
|
+
_typedFieldName,
|
|
37
|
+
])}) internal {
|
|
38
|
+
${_primaryKeysDefinition}
|
|
39
|
+
${_store}.setField(_tableId, _primaryKeys, ${index}, ${renderEncodeField(field)});
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// TODO: this is super inefficient right now, need to add support for pushing to arrays to the store core library to avoid reading/writing the entire array
|
|
45
|
+
// (see https://github.com/latticexyz/mud/issues/438)
|
|
46
|
+
if (field.isDynamic) {
|
|
47
|
+
const portionData = fieldPortionData(field);
|
|
48
|
+
|
|
49
|
+
result += renderWithStore(
|
|
50
|
+
storeArgument,
|
|
51
|
+
(_typedStore, _store, _commentSuffix) => `
|
|
52
|
+
/** Push ${portionData.title} to ${field.name}${_commentSuffix} */
|
|
53
|
+
function push${field.methodNameSuffix}(${renderArguments([
|
|
54
|
+
_typedStore,
|
|
55
|
+
_typedTableId,
|
|
56
|
+
_typedKeyArgs,
|
|
57
|
+
`${portionData.typeWithLocation} ${portionData.name}`,
|
|
58
|
+
])}) internal {
|
|
59
|
+
${_primaryKeysDefinition}
|
|
60
|
+
${_store}.pushToField(_tableId, _primaryKeys, ${index}, ${portionData.encoded});
|
|
61
|
+
}
|
|
62
|
+
`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function renderEncodeField(field: RenderTableField) {
|
|
70
|
+
let func;
|
|
71
|
+
if (field.arrayElement) {
|
|
72
|
+
func = "EncodeArray.encode";
|
|
73
|
+
} else if (field.isDynamic) {
|
|
74
|
+
func = "bytes";
|
|
75
|
+
} else {
|
|
76
|
+
func = "abi.encodePacked";
|
|
77
|
+
}
|
|
78
|
+
return `${func}(${field.typeUnwrap}(${field.name}))`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function renderDecodeValueType(field: RenderTableType, offset: number) {
|
|
82
|
+
const { staticByteLength, internalTypeId } = field;
|
|
83
|
+
|
|
84
|
+
const innerSlice = `Bytes.slice${staticByteLength}(_blob, ${offset})`;
|
|
85
|
+
const bits = staticByteLength * 8;
|
|
86
|
+
|
|
87
|
+
let result;
|
|
88
|
+
if (internalTypeId.match(/^uint\d{1,3}$/) || internalTypeId === "address") {
|
|
89
|
+
result = `${internalTypeId}(${innerSlice})`;
|
|
90
|
+
} else if (internalTypeId.match(/^int\d{1,3}$/)) {
|
|
91
|
+
result = `${internalTypeId}(uint${bits}(${innerSlice}))`;
|
|
92
|
+
} else if (internalTypeId.match(/^bytes\d{1,2}$/)) {
|
|
93
|
+
result = innerSlice;
|
|
94
|
+
} else if (internalTypeId === "bool") {
|
|
95
|
+
result = `_toBool(uint8(${innerSlice}))`;
|
|
96
|
+
} else {
|
|
97
|
+
throw new Error(`Unknown value type id ${internalTypeId}`);
|
|
98
|
+
}
|
|
99
|
+
return `${field.typeWrap}(${result})`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** bytes/string are dynamic, but aren't really arrays */
|
|
103
|
+
function fieldPortionData(field: RenderTableField) {
|
|
104
|
+
const methodNameSuffix = "";
|
|
105
|
+
if (field.arrayElement) {
|
|
106
|
+
const name = "_element";
|
|
107
|
+
return {
|
|
108
|
+
typeWithLocation: field.arrayElement.typeWithLocation,
|
|
109
|
+
name: "_element",
|
|
110
|
+
encoded: renderEncodeField({ ...field.arrayElement, arrayElement: undefined, name, methodNameSuffix }),
|
|
111
|
+
title: "an element",
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
const name = "_slice";
|
|
115
|
+
return {
|
|
116
|
+
typeWithLocation: `${field.typeId} memory`,
|
|
117
|
+
name,
|
|
118
|
+
encoded: renderEncodeField({ ...field, name, methodNameSuffix }),
|
|
119
|
+
title: "a slice",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderDecodeFieldSingle(field: RenderTableField) {
|
|
125
|
+
const { isDynamic, arrayElement } = field;
|
|
126
|
+
if (arrayElement) {
|
|
127
|
+
// arrays
|
|
128
|
+
return `${field.typeWrap}(
|
|
129
|
+
SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_${arrayElement.internalTypeId}()
|
|
130
|
+
)`;
|
|
131
|
+
} else if (isDynamic) {
|
|
132
|
+
// bytes/string
|
|
133
|
+
return `${field.typeWrap}(${field.internalTypeId}(_blob))`;
|
|
134
|
+
} else {
|
|
135
|
+
return renderDecodeValueType(field, 0);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
export * from "./field.js";
|
|
3
|
+
export * from "./record.js";
|
|
4
|
+
export * from "./renderTable.js";
|
|
5
|
+
export * from "./renderTypes.js";
|
|
6
|
+
export * from "./renderTypesFromConfig.js";
|
|
7
|
+
export * from "./tablegen.js";
|
|
8
|
+
export * from "./tableOptions.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./userType.js";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { renderList, renderArguments, renderCommonData, renderWithStore } from "./common.js";
|
|
2
|
+
import { renderDecodeValueType } from "./field.js";
|
|
3
|
+
import { RenderTableDynamicField, RenderTableOptions } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export function renderRecordMethods(options: RenderTableOptions) {
|
|
6
|
+
const { structName, storeArgument } = options;
|
|
7
|
+
const { _tableId, _typedTableId, _keyArgs, _typedKeyArgs, _primaryKeysDefinition } = renderCommonData(options);
|
|
8
|
+
|
|
9
|
+
let result = renderWithStore(
|
|
10
|
+
storeArgument,
|
|
11
|
+
(_typedStore, _store, _commentSuffix) => `
|
|
12
|
+
/** Get the full data${_commentSuffix} */
|
|
13
|
+
function get(${renderArguments([
|
|
14
|
+
_typedStore,
|
|
15
|
+
_typedTableId,
|
|
16
|
+
_typedKeyArgs,
|
|
17
|
+
])}) internal view returns (${renderDecodedRecord(options)}) {
|
|
18
|
+
${_primaryKeysDefinition}
|
|
19
|
+
bytes memory _blob = ${_store}.getRecord(_tableId, _primaryKeys, getSchema());
|
|
20
|
+
return decode(_blob);
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
result += renderWithStore(
|
|
26
|
+
storeArgument,
|
|
27
|
+
(_typedStore, _store, _commentSuffix) => `
|
|
28
|
+
/** Set the full data using individual values${_commentSuffix} */
|
|
29
|
+
function set(${renderArguments([
|
|
30
|
+
_typedStore,
|
|
31
|
+
_typedTableId,
|
|
32
|
+
_typedKeyArgs,
|
|
33
|
+
renderArguments(options.fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`)),
|
|
34
|
+
])}) internal {
|
|
35
|
+
bytes memory _data = encode(${renderArguments(options.fields.map(({ name }) => name))});
|
|
36
|
+
|
|
37
|
+
${_primaryKeysDefinition}
|
|
38
|
+
|
|
39
|
+
${_store}.setRecord(_tableId, _primaryKeys, _data);
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (structName !== undefined) {
|
|
45
|
+
result += renderWithStore(
|
|
46
|
+
storeArgument,
|
|
47
|
+
(_typedStore, _store, _commentSuffix, _untypedStore) => `
|
|
48
|
+
/** Set the full data using the data struct${_commentSuffix} */
|
|
49
|
+
function set(${renderArguments([
|
|
50
|
+
_typedStore,
|
|
51
|
+
_typedTableId,
|
|
52
|
+
_typedKeyArgs,
|
|
53
|
+
`${structName} memory _table`,
|
|
54
|
+
])}) internal {
|
|
55
|
+
set(${renderArguments([
|
|
56
|
+
_untypedStore,
|
|
57
|
+
_tableId,
|
|
58
|
+
_keyArgs,
|
|
59
|
+
renderArguments(options.fields.map(({ name }) => `_table.${name}`)),
|
|
60
|
+
])});
|
|
61
|
+
}
|
|
62
|
+
`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
result += renderDecodeFunction(options);
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Renders the `decode` function that parses a bytes blob into the table data
|
|
72
|
+
function renderDecodeFunction({ structName, fields, staticFields, dynamicFields }: RenderTableOptions) {
|
|
73
|
+
// either set struct properties, or just variables
|
|
74
|
+
const renderedDecodedRecord = structName
|
|
75
|
+
? `${structName} memory _table`
|
|
76
|
+
: renderArguments(fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`));
|
|
77
|
+
const fieldNamePrefix = structName ? "_table." : "";
|
|
78
|
+
|
|
79
|
+
// Static field offsets
|
|
80
|
+
const staticOffsets = staticFields.map(() => 0);
|
|
81
|
+
let _acc = 0;
|
|
82
|
+
for (const [index, field] of staticFields.entries()) {
|
|
83
|
+
staticOffsets[index] = _acc;
|
|
84
|
+
_acc += field.staticByteLength;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (dynamicFields.length > 0) {
|
|
88
|
+
const totalStaticLength = staticFields.reduce((acc, { staticByteLength }) => acc + staticByteLength, 0);
|
|
89
|
+
// decode static (optionally) and dynamic data
|
|
90
|
+
return `
|
|
91
|
+
/** Decode the tightly packed blob using this table's schema */
|
|
92
|
+
function decode(bytes memory _blob) internal view returns (${renderedDecodedRecord}) {
|
|
93
|
+
// ${totalStaticLength} is the total byte length of static data
|
|
94
|
+
PackedCounter _encodedLengths = PackedCounter.wrap(Bytes.slice32(_blob, ${totalStaticLength}));
|
|
95
|
+
|
|
96
|
+
${renderList(
|
|
97
|
+
staticFields,
|
|
98
|
+
(field, index) => `
|
|
99
|
+
${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])};
|
|
100
|
+
`
|
|
101
|
+
)}
|
|
102
|
+
uint256 _start;
|
|
103
|
+
uint256 _end = ${totalStaticLength + 32};
|
|
104
|
+
${renderList(
|
|
105
|
+
dynamicFields,
|
|
106
|
+
(field, index) => `
|
|
107
|
+
_start = _end;
|
|
108
|
+
_end += _encodedLengths.atIndex(${index});
|
|
109
|
+
${fieldNamePrefix}${field.name} = ${renderDecodeDynamicFieldPartial(field)};
|
|
110
|
+
`
|
|
111
|
+
)}
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
} else {
|
|
115
|
+
// decode only static data
|
|
116
|
+
return `
|
|
117
|
+
/** Decode the tightly packed blob using this table's schema */
|
|
118
|
+
function decode(bytes memory _blob) internal pure returns (${renderedDecodedRecord}) {
|
|
119
|
+
${renderList(
|
|
120
|
+
staticFields,
|
|
121
|
+
(field, index) => `
|
|
122
|
+
${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])};
|
|
123
|
+
`
|
|
124
|
+
)}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// contents of `returns (...)` for record getter/decoder
|
|
131
|
+
function renderDecodedRecord({ structName, fields }: RenderTableOptions) {
|
|
132
|
+
if (structName) {
|
|
133
|
+
return `${structName} memory _table`;
|
|
134
|
+
} else {
|
|
135
|
+
return renderArguments(fields.map(({ name, typeWithLocation }) => `${typeWithLocation} ${name}`));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderDecodeDynamicFieldPartial(field: RenderTableDynamicField) {
|
|
140
|
+
const { typeId, arrayElement, typeWrap } = field;
|
|
141
|
+
if (arrayElement) {
|
|
142
|
+
// arrays
|
|
143
|
+
return `${typeWrap}(
|
|
144
|
+
SliceLib.getSubslice(_blob, _start, _end).decodeArray_${arrayElement.typeId}()
|
|
145
|
+
)`;
|
|
146
|
+
} else {
|
|
147
|
+
// bytes/string
|
|
148
|
+
return `${typeWrap}(
|
|
149
|
+
${typeId}(
|
|
150
|
+
SliceLib.getSubslice(_blob, _start, _end).toBytes()
|
|
151
|
+
)
|
|
152
|
+
)`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { renderArguments, renderList, renderedSolidityHeader, renderImports } from "./common.js";
|
|
2
|
+
import { RenderSystemInterfaceOptions } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export function renderSystemInterface(options: RenderSystemInterfaceOptions) {
|
|
5
|
+
const { imports, name, functionPrefix, functions } = options;
|
|
6
|
+
|
|
7
|
+
return `${renderedSolidityHeader}
|
|
8
|
+
|
|
9
|
+
${renderImports(imports)}
|
|
10
|
+
|
|
11
|
+
interface ${name} {
|
|
12
|
+
${renderList(
|
|
13
|
+
functions,
|
|
14
|
+
({ name, parameters, returnParameters }) => `
|
|
15
|
+
function ${functionPrefix}${name}(${renderArguments(parameters)}) external ${renderReturnParameters(
|
|
16
|
+
returnParameters
|
|
17
|
+
)};
|
|
18
|
+
`
|
|
19
|
+
)}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function renderReturnParameters(returnParameters: string[]) {
|
|
26
|
+
if (returnParameters.length > 0) {
|
|
27
|
+
return `returns (${renderArguments(returnParameters)})`;
|
|
28
|
+
} else {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
}
|