Skip to content

Commit 08cb8d9

Browse files
authored
Merge pull request #6139 from NomicFoundation/config-override-rework
Config override rework
2 parents f57d015 + dbdbd35 commit 08cb8d9

File tree

11 files changed

+600
-446
lines changed

11 files changed

+600
-446
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type {
2+
EdrNetworkUserConfig,
3+
HttpNetworkUserConfig,
4+
NetworkConfig,
5+
NetworkConfigOverride,
6+
NetworkUserConfig,
7+
} from "../../../types/config.js";
8+
9+
import { isObject } from "@ignored/hardhat-vnext-utils/lang";
10+
11+
/**
12+
* Converts the NetworkConfigOverride into a valid NetworkUserConfig. This
13+
* function determines the network type based on the provided `networkConfig`
14+
* and sets default values for any required properties that are missing from
15+
* the `networkConfigOverride`.
16+
*
17+
* @warning
18+
* This function is not type-safe. It assumes that `networkConfigOverride` does
19+
* not contain mixed properties from different network types. Always validate
20+
* the resulting NetworkUserConfig before using it.
21+
*
22+
* @param networkConfigOverride The partial configuration override provided by
23+
* the user.
24+
* @param networkConfig The base network configuration used to infer defaults
25+
* and the network type.
26+
* @returns A fully resolved NetworkUserConfig with defaults applied.
27+
*/
28+
export async function normalizeNetworkConfigOverride(
29+
networkConfigOverride: NetworkConfigOverride,
30+
networkConfig: NetworkConfig,
31+
): Promise<NetworkUserConfig> {
32+
let networkConfigOverrideWithType: NetworkUserConfig;
33+
34+
if (networkConfig.type === "http") {
35+
const networkConfigOverrideAsHttp =
36+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
37+
-- Assumes that networkConfigOverride is a HttpNetworkUserConfig. */
38+
networkConfigOverride as HttpNetworkUserConfig;
39+
40+
networkConfigOverrideWithType = {
41+
...networkConfigOverrideAsHttp,
42+
type: "http",
43+
url: networkConfigOverrideAsHttp.url ?? (await networkConfig.url.get()),
44+
};
45+
} else {
46+
const networkConfigOverrideAsEdr =
47+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
48+
-- Assumes that networkConfigOverride is an EdrNetworkUserConfig. */
49+
networkConfigOverride as EdrNetworkUserConfig;
50+
51+
networkConfigOverrideWithType = {
52+
...networkConfigOverrideAsEdr,
53+
type: "edr",
54+
};
55+
}
56+
57+
return networkConfigOverrideWithType;
58+
}
59+
60+
/**
61+
* Merges two network configurations. This function is used to merge the
62+
* network configuration with the network configuration override. It recursively
63+
* merges nested objects.
64+
*
65+
* @param target The resolved network configuration.
66+
* @param source The partial network configuration override provided by the user. It
67+
* should be resolved and contain only the properties that the user wants to
68+
* override.
69+
* @returns A new network configuration object with the override applied.
70+
*/
71+
export function mergeConfigOverride<T extends object>(
72+
target: T,
73+
source: Partial<T> = {},
74+
): T {
75+
const result = { ...target };
76+
77+
for (const key in source) {
78+
if (
79+
isObject(source[key]) &&
80+
// Only merge recursively objects that are not class instances
81+
Object.getPrototypeOf(source[key]) === Object.prototype
82+
) {
83+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
84+
-- result[key] is either an object or undefined, so we default it to {} */
85+
result[key] = mergeConfigOverride(
86+
result[key] ?? {},
87+
source[key],
88+
) as T[Extract<keyof T, string>];
89+
} else {
90+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
91+
-- source[key] could be undefined, but as it is a resolved config as well,
92+
result[key] should allow it */
93+
result[key] = source[key] as T[Extract<keyof T, string>];
94+
}
95+
}
96+
97+
return result;
98+
}

v-next/hardhat/src/internal/builtin-plugins/network-manager/config-resolution.ts

+157-2
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ import type {
55
EdrNetworkChainConfig,
66
EdrNetworkChainsConfig,
77
EdrNetworkChainsUserConfig,
8+
EdrNetworkConfig,
89
EdrNetworkForkingConfig,
910
EdrNetworkForkingUserConfig,
1011
EdrNetworkMiningConfig,
1112
EdrNetworkMiningUserConfig,
13+
EdrNetworkUserConfig,
1214
GasConfig,
1315
GasUserConfig,
1416
HttpNetworkAccountsConfig,
1517
HttpNetworkAccountsUserConfig,
18+
HttpNetworkConfig,
19+
HttpNetworkUserConfig,
20+
NetworkConfig,
21+
NetworkUserConfig,
1622
} from "../../../types/config.js";
1723

1824
import path from "node:path";
@@ -21,14 +27,163 @@ import {
2127
hexStringToBytes,
2228
normalizeHexString,
2329
} from "@ignored/hardhat-vnext-utils/hex";
30+
import { isObject } from "micro-eth-signer/utils";
2431

2532
import { DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS } from "./accounts/constants.js";
2633
import {
2734
DEFAULT_EDR_NETWORK_HD_ACCOUNTS_CONFIG_PARAMS,
2835
EDR_NETWORK_DEFAULT_COINBASE,
2936
} from "./edr/edr-provider.js";
3037
import { HardforkName, LATEST_HARDFORK } from "./edr/types/hardfork.js";
31-
import { isHdAccountsUserConfig } from "./type-validation.js";
38+
import { isHttpNetworkHdAccountsUserConfig } from "./type-validation.js";
39+
40+
export function resolveHttpNetwork(
41+
networkConfig: HttpNetworkUserConfig,
42+
resolveConfigurationVariable: ConfigurationVariableResolver,
43+
): HttpNetworkConfig {
44+
return {
45+
type: "http",
46+
accounts: resolveHttpNetworkAccounts(
47+
networkConfig.accounts,
48+
resolveConfigurationVariable,
49+
),
50+
chainId: networkConfig.chainId,
51+
chainType: networkConfig.chainType,
52+
from: networkConfig.from,
53+
gas: resolveGasConfig(networkConfig.gas),
54+
gasMultiplier: networkConfig.gasMultiplier ?? 1,
55+
gasPrice: resolveGasConfig(networkConfig.gasPrice),
56+
url: resolveConfigurationVariable(networkConfig.url),
57+
timeout: networkConfig.timeout ?? 20_000,
58+
httpHeaders: networkConfig.httpHeaders ?? {},
59+
};
60+
}
61+
62+
export function resolveEdrNetwork(
63+
networkConfig: EdrNetworkUserConfig,
64+
cachePath: string,
65+
resolveConfigurationVariable: ConfigurationVariableResolver,
66+
): EdrNetworkConfig {
67+
return {
68+
type: "edr",
69+
accounts: resolveEdrNetworkAccounts(
70+
networkConfig.accounts,
71+
resolveConfigurationVariable,
72+
),
73+
chainId: networkConfig.chainId ?? 31337,
74+
chainType: networkConfig.chainType,
75+
from: networkConfig.from,
76+
gas: resolveGasConfig(networkConfig.gas),
77+
gasMultiplier: networkConfig.gasMultiplier ?? 1,
78+
gasPrice: resolveGasConfig(networkConfig.gasPrice),
79+
80+
allowBlocksWithSameTimestamp:
81+
networkConfig.allowBlocksWithSameTimestamp ?? false,
82+
allowUnlimitedContractSize:
83+
networkConfig.allowUnlimitedContractSize ?? false,
84+
blockGasLimit: BigInt(networkConfig.blockGasLimit ?? 30_000_000n),
85+
chains: resolveChains(networkConfig.chains),
86+
coinbase: resolveCoinbase(networkConfig.coinbase),
87+
enableRip7212: networkConfig.enableRip7212 ?? false,
88+
enableTransientStorage: networkConfig.enableTransientStorage ?? false,
89+
forking: resolveForkingConfig(
90+
networkConfig.forking,
91+
cachePath,
92+
resolveConfigurationVariable,
93+
),
94+
hardfork: resolveHardfork(
95+
networkConfig.hardfork,
96+
networkConfig.enableTransientStorage,
97+
),
98+
initialBaseFeePerGas: resolveInitialBaseFeePerGas(
99+
networkConfig.initialBaseFeePerGas,
100+
),
101+
initialDate: networkConfig.initialDate ?? new Date(),
102+
loggingEnabled: networkConfig.loggingEnabled ?? false,
103+
minGasPrice: BigInt(networkConfig.minGasPrice ?? 0),
104+
mining: resolveMiningConfig(networkConfig.mining),
105+
networkId: networkConfig.networkId ?? networkConfig.chainId ?? 31337,
106+
throwOnCallFailures: networkConfig.throwOnCallFailures ?? true,
107+
throwOnTransactionFailures:
108+
networkConfig.throwOnTransactionFailures ?? true,
109+
};
110+
}
111+
112+
/**
113+
* Resolves a NetworkUserConfig into a Partial<NetworkConfig> object.
114+
* This function processes the network configuration override using the appropriate
115+
* resolver (either HTTP or EDR) and ensures only the values explicitly specified
116+
* in the override are included in the final result, preventing defaults from
117+
* overwriting the user's configuration.
118+
*
119+
* @param networkUserConfigOverride The user's network configuration override.
120+
* @param resolveConfigurationVariable A function to resolve configuration variables.
121+
* @returns A Partial<NetworkConfig> containing the resolved values from the override.
122+
*/
123+
export function resolveNetworkConfigOverride(
124+
networkUserConfigOverride: NetworkUserConfig,
125+
resolveConfigurationVariable: ConfigurationVariableResolver,
126+
): Partial<NetworkConfig> {
127+
let resolvedConfigOverride: NetworkConfig;
128+
129+
if (networkUserConfigOverride.type === "http") {
130+
resolvedConfigOverride = resolveHttpNetwork(
131+
networkUserConfigOverride,
132+
resolveConfigurationVariable,
133+
);
134+
} else {
135+
resolvedConfigOverride = resolveEdrNetwork(
136+
networkUserConfigOverride,
137+
"",
138+
resolveConfigurationVariable,
139+
);
140+
}
141+
142+
/* Return only the resolved config of the values overridden by the user. This
143+
ensures that only the overridden values are merged into the config and
144+
indirectly removes cacheDir from the resolved forking config, as cacheDir
145+
is not part of the NetworkUserConfig. */
146+
return pickResolvedFromOverrides(
147+
resolvedConfigOverride,
148+
networkUserConfigOverride,
149+
);
150+
}
151+
152+
function pickResolvedFromOverrides<
153+
TResolved extends object,
154+
TOverride extends object,
155+
>(resolvedConfig: TResolved, overrides: TOverride): Partial<TResolved> {
156+
const result: Partial<TResolved> = {};
157+
158+
for (const key of Object.keys(overrides)) {
159+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
160+
-- As TResolved and TOverride are objects share the same keys, we can
161+
safely cast the key */
162+
const resolvedKey = key as keyof TResolved;
163+
const resolvedValue = resolvedConfig[resolvedKey];
164+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
165+
-- As TResolved and TOverride are objects share the same keys, we can
166+
safely cast the key */
167+
const overrideValue = overrides[key as keyof TOverride];
168+
169+
if (!(isObject(resolvedValue) && isObject(overrideValue))) {
170+
result[resolvedKey] = resolvedValue;
171+
continue;
172+
}
173+
174+
/* Some properties in NetworkConfig, such as accounts, forking, and mining,
175+
are objects themselves. To handle these, we process them recursively. */
176+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions
177+
-- The return type adheres to TResolved[keyof TResolved], but TS can't
178+
infer it */
179+
result[resolvedKey] = pickResolvedFromOverrides(
180+
resolvedValue,
181+
overrideValue,
182+
) as TResolved[keyof TResolved];
183+
}
184+
185+
return result;
186+
}
32187

33188
export function resolveGasConfig(value: GasUserConfig = "auto"): GasConfig {
34189
return value === "auto" ? value : BigInt(value);
@@ -48,7 +203,7 @@ export function resolveHttpNetworkAccounts(
48203
});
49204
}
50205

51-
if (isHdAccountsUserConfig(accounts)) {
206+
if (isHttpNetworkHdAccountsUserConfig(accounts)) {
52207
const { passphrase: defaultPassphrase, ...defaultHdAccountRest } =
53208
DEFAULT_HD_ACCOUNTS_CONFIG_PARAMS;
54209
const { mnemonic, passphrase, ...hdAccountRest } = accounts;

0 commit comments

Comments
 (0)