@diamondslab/diamonds 1.0.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.
Files changed (229) hide show
  1. package/README.md +618 -0
  2. package/diamonds/README.md +3 -0
  3. package/dist/core/CallbackManager.d.ts +13 -0
  4. package/dist/core/CallbackManager.d.ts.map +1 -0
  5. package/dist/core/CallbackManager.js +95 -0
  6. package/dist/core/CallbackManager.js.map +1 -0
  7. package/dist/core/DeploymentManager.d.ts +10 -0
  8. package/dist/core/DeploymentManager.d.ts.map +1 -0
  9. package/dist/core/DeploymentManager.js +50 -0
  10. package/dist/core/DeploymentManager.js.map +1 -0
  11. package/dist/core/Diamond.d.ts +58 -0
  12. package/dist/core/Diamond.d.ts.map +1 -0
  13. package/dist/core/Diamond.js +146 -0
  14. package/dist/core/Diamond.js.map +1 -0
  15. package/dist/core/DiamondDeployer.d.ts +10 -0
  16. package/dist/core/DiamondDeployer.d.ts.map +1 -0
  17. package/dist/core/DiamondDeployer.js +33 -0
  18. package/dist/core/DiamondDeployer.js.map +1 -0
  19. package/dist/core/index.d.ts +5 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +12 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/repositories/DBDeploymentRepository.d.ts +1 -0
  28. package/dist/repositories/DBDeploymentRepository.d.ts.map +1 -0
  29. package/dist/repositories/DBDeploymentRepository.js +20 -0
  30. package/dist/repositories/DBDeploymentRepository.js.map +1 -0
  31. package/dist/repositories/DeploymentRepository.d.ts +8 -0
  32. package/dist/repositories/DeploymentRepository.d.ts.map +1 -0
  33. package/dist/repositories/DeploymentRepository.js +7 -0
  34. package/dist/repositories/DeploymentRepository.js.map +1 -0
  35. package/dist/repositories/FileDeploymentRepository.d.ts +18 -0
  36. package/dist/repositories/FileDeploymentRepository.d.ts.map +1 -0
  37. package/dist/repositories/FileDeploymentRepository.js +58 -0
  38. package/dist/repositories/FileDeploymentRepository.js.map +1 -0
  39. package/dist/repositories/databaseHandler.d.ts +1 -0
  40. package/dist/repositories/databaseHandler.d.ts.map +1 -0
  41. package/dist/repositories/databaseHandler.js +13 -0
  42. package/dist/repositories/databaseHandler.js.map +1 -0
  43. package/dist/repositories/index.d.ts +4 -0
  44. package/dist/repositories/index.d.ts.map +1 -0
  45. package/dist/repositories/index.js +20 -0
  46. package/dist/repositories/index.js.map +1 -0
  47. package/dist/repositories/jsonFileHandler.d.ts +81 -0
  48. package/dist/repositories/jsonFileHandler.d.ts.map +1 -0
  49. package/dist/repositories/jsonFileHandler.js +223 -0
  50. package/dist/repositories/jsonFileHandler.js.map +1 -0
  51. package/dist/repositories/prismaDBHandler.d.ts +1 -0
  52. package/dist/repositories/prismaDBHandler.d.ts.map +1 -0
  53. package/dist/repositories/prismaDBHandler.js +11 -0
  54. package/dist/repositories/prismaDBHandler.js.map +1 -0
  55. package/dist/schemas/DeploymentSchema.d.ts +309 -0
  56. package/dist/schemas/DeploymentSchema.d.ts.map +1 -0
  57. package/dist/schemas/DeploymentSchema.js +56 -0
  58. package/dist/schemas/DeploymentSchema.js.map +1 -0
  59. package/dist/schemas/index.d.ts +2 -0
  60. package/dist/schemas/index.d.ts.map +1 -0
  61. package/dist/schemas/index.js +18 -0
  62. package/dist/schemas/index.js.map +1 -0
  63. package/dist/strategies/BaseDeploymentStrategy.d.ts +41 -0
  64. package/dist/strategies/BaseDeploymentStrategy.d.ts.map +1 -0
  65. package/dist/strategies/BaseDeploymentStrategy.js +545 -0
  66. package/dist/strategies/BaseDeploymentStrategy.js.map +1 -0
  67. package/dist/strategies/DeploymentStrategy.d.ts +19 -0
  68. package/dist/strategies/DeploymentStrategy.d.ts.map +1 -0
  69. package/dist/strategies/DeploymentStrategy.js +3 -0
  70. package/dist/strategies/DeploymentStrategy.js.map +1 -0
  71. package/dist/strategies/LocalDeploymentStrategy.d.ts +4 -0
  72. package/dist/strategies/LocalDeploymentStrategy.d.ts.map +1 -0
  73. package/dist/strategies/LocalDeploymentStrategy.js +8 -0
  74. package/dist/strategies/LocalDeploymentStrategy.js.map +1 -0
  75. package/dist/strategies/OZDefenderDeploymentStrategy.d.ts +62 -0
  76. package/dist/strategies/OZDefenderDeploymentStrategy.d.ts.map +1 -0
  77. package/dist/strategies/OZDefenderDeploymentStrategy.js +757 -0
  78. package/dist/strategies/OZDefenderDeploymentStrategy.js.map +1 -0
  79. package/dist/strategies/RPCDeploymentStrategy.d.ts +139 -0
  80. package/dist/strategies/RPCDeploymentStrategy.d.ts.map +1 -0
  81. package/dist/strategies/RPCDeploymentStrategy.js +710 -0
  82. package/dist/strategies/RPCDeploymentStrategy.js.map +1 -0
  83. package/dist/strategies/index.d.ts +6 -0
  84. package/dist/strategies/index.d.ts.map +1 -0
  85. package/dist/strategies/index.js +12 -0
  86. package/dist/strategies/index.js.map +1 -0
  87. package/dist/types/config.d.ts +26 -0
  88. package/dist/types/config.d.ts.map +1 -0
  89. package/dist/types/config.js +3 -0
  90. package/dist/types/config.js.map +1 -0
  91. package/dist/types/defender.d.ts +22 -0
  92. package/dist/types/defender.d.ts.map +1 -0
  93. package/dist/types/defender.js +3 -0
  94. package/dist/types/defender.js.map +1 -0
  95. package/dist/types/deployments.d.ts +71 -0
  96. package/dist/types/deployments.d.ts.map +1 -0
  97. package/dist/types/deployments.js +20 -0
  98. package/dist/types/deployments.js.map +1 -0
  99. package/dist/types/index.d.ts +5 -0
  100. package/dist/types/index.d.ts.map +1 -0
  101. package/dist/types/index.js +21 -0
  102. package/dist/types/index.js.map +1 -0
  103. package/dist/types/rpc.d.ts +35 -0
  104. package/dist/types/rpc.d.ts.map +1 -0
  105. package/dist/types/rpc.js +3 -0
  106. package/dist/types/rpc.js.map +1 -0
  107. package/dist/utils/common.d.ts +20 -0
  108. package/dist/utils/common.d.ts.map +1 -0
  109. package/dist/utils/common.js +45 -0
  110. package/dist/utils/common.js.map +1 -0
  111. package/dist/utils/configurationResolver.d.ts +30 -0
  112. package/dist/utils/configurationResolver.d.ts.map +1 -0
  113. package/dist/utils/configurationResolver.js +151 -0
  114. package/dist/utils/configurationResolver.js.map +1 -0
  115. package/dist/utils/contractMapping.d.ts +29 -0
  116. package/dist/utils/contractMapping.d.ts.map +1 -0
  117. package/dist/utils/contractMapping.js +224 -0
  118. package/dist/utils/contractMapping.js.map +1 -0
  119. package/dist/utils/defenderClients.d.ts +5 -0
  120. package/dist/utils/defenderClients.d.ts.map +1 -0
  121. package/dist/utils/defenderClients.js +21 -0
  122. package/dist/utils/defenderClients.js.map +1 -0
  123. package/dist/utils/defenderStore.d.ts +14 -0
  124. package/dist/utils/defenderStore.d.ts.map +1 -0
  125. package/dist/utils/defenderStore.js +92 -0
  126. package/dist/utils/defenderStore.js.map +1 -0
  127. package/dist/utils/diamondAbiGenerator.d.ts +113 -0
  128. package/dist/utils/diamondAbiGenerator.d.ts.map +1 -0
  129. package/dist/utils/diamondAbiGenerator.js +415 -0
  130. package/dist/utils/diamondAbiGenerator.js.map +1 -0
  131. package/dist/utils/diffDeployedFacets.d.ts +26 -0
  132. package/dist/utils/diffDeployedFacets.d.ts.map +1 -0
  133. package/dist/utils/diffDeployedFacets.js +106 -0
  134. package/dist/utils/diffDeployedFacets.js.map +1 -0
  135. package/dist/utils/index.d.ts +16 -0
  136. package/dist/utils/index.d.ts.map +1 -0
  137. package/dist/utils/index.js +35 -0
  138. package/dist/utils/index.js.map +1 -0
  139. package/dist/utils/loupe.d.ts +44 -0
  140. package/dist/utils/loupe.d.ts.map +1 -0
  141. package/dist/utils/loupe.js +128 -0
  142. package/dist/utils/loupe.js.map +1 -0
  143. package/dist/utils/rpcStore.d.ts +36 -0
  144. package/dist/utils/rpcStore.d.ts.map +1 -0
  145. package/dist/utils/rpcStore.js +166 -0
  146. package/dist/utils/rpcStore.js.map +1 -0
  147. package/dist/utils/signer.d.ts +36 -0
  148. package/dist/utils/signer.d.ts.map +1 -0
  149. package/dist/utils/signer.js +91 -0
  150. package/dist/utils/signer.js.map +1 -0
  151. package/dist/utils/txlogging.d.ts +13 -0
  152. package/dist/utils/txlogging.d.ts.map +1 -0
  153. package/dist/utils/txlogging.js +87 -0
  154. package/dist/utils/txlogging.js.map +1 -0
  155. package/dist/utils/workspaceSetup.d.ts +32 -0
  156. package/dist/utils/workspaceSetup.d.ts.map +1 -0
  157. package/dist/utils/workspaceSetup.js +311 -0
  158. package/dist/utils/workspaceSetup.js.map +1 -0
  159. package/docs/DIAMOND_ABI_CONFIGURATION_SUMMARY.md +40 -0
  160. package/docs/DIAMOND_ABI_GENERATION.md +220 -0
  161. package/docs/DIAMOND_ABI_GENERATOR_EXAMPLES.md +1204 -0
  162. package/docs/DIAMOND_ABI_GENERATOR_IMPLEMENTATION.md +947 -0
  163. package/docs/DIAMOND_ABI_GENERATOR_QUICK_REFERENCE.md +336 -0
  164. package/docs/README-DEFENDER.md +394 -0
  165. package/docs/README_DIAMOND_ABI_GENERATOR.md +303 -0
  166. package/docs/ROADMAP.md +250 -0
  167. package/docs/assets/image.png +0 -0
  168. package/docs/defender-integration.md +451 -0
  169. package/docs/diamond_module-BaseStrategy_design-v2.uxf +247 -0
  170. package/docs/diamond_module-BaseStrategy_design.uxf +272 -0
  171. package/docs/monitoring-troubleshooting.md +556 -0
  172. package/docs/testing-guide.md +713 -0
  173. package/examples/Diamond_Config_and_Deployment_examples/diamonds/ProxyDiamond/callbacks/ERC20ProxyFacet.ts +31 -0
  174. package/examples/Diamond_Config_and_Deployment_examples/diamonds/ProxyDiamond/proxydiamond.config.json +27 -0
  175. package/examples/Local_Hardhat_Deployer_Script_example/LocalDiamondDeployer.ts +180 -0
  176. package/examples/OZ_Defender_Deployer_Script_example/OZDiamondDeployer.ts +107 -0
  177. package/examples/OZ_Defender_Deployer_Script_example/run-oz-deploy.ts +17 -0
  178. package/examples/Test_examples/ProxyDiamondDeployment.test.ts +202 -0
  179. package/examples/defender-deployment/.env.example +35 -0
  180. package/examples/defender-deployment/README.md +415 -0
  181. package/examples/defender-deployment/contracts/ExampleDiamond.sol +41 -0
  182. package/examples/defender-deployment/contracts/ExampleFacet1.sol +84 -0
  183. package/examples/defender-deployment/contracts/ExampleFacet2.sol +104 -0
  184. package/examples/defender-deployment/contracts/UpgradeFacet.sol +92 -0
  185. package/examples/defender-deployment/deploy-script.ts +170 -0
  186. package/examples/defender-deployment/diamond-config.json +36 -0
  187. package/examples/defender-deployment/upgrade-script.ts +237 -0
  188. package/examples/hardhat-diamonds-config.example.ts +41 -0
  189. package/package.json +228 -0
  190. package/src/core/CallbackManager.ts +70 -0
  191. package/src/core/DeploymentManager.ts +64 -0
  192. package/src/core/Diamond.ts +197 -0
  193. package/src/core/DiamondDeployer.ts +36 -0
  194. package/src/core/index.ts +4 -0
  195. package/src/index.ts +5 -0
  196. package/src/repositories/DBDeploymentRepository.ts +22 -0
  197. package/src/repositories/DeploymentRepository.ts +12 -0
  198. package/src/repositories/FileDeploymentRepository.ts +67 -0
  199. package/src/repositories/databaseHandler.ts +14 -0
  200. package/src/repositories/index.ts +4 -0
  201. package/src/repositories/jsonFileHandler.ts +252 -0
  202. package/src/repositories/prismaDBHandler.ts +10 -0
  203. package/src/schemas/DeploymentSchema.ts +71 -0
  204. package/src/schemas/index.ts +1 -0
  205. package/src/strategies/BaseDeploymentStrategy.ts +649 -0
  206. package/src/strategies/DeploymentStrategy.ts +25 -0
  207. package/src/strategies/LocalDeploymentStrategy.ts +5 -0
  208. package/src/strategies/OZDefenderDeploymentStrategy.ts +849 -0
  209. package/src/strategies/RPCDeploymentStrategy.ts +881 -0
  210. package/src/strategies/index.ts +5 -0
  211. package/src/types/config.ts +34 -0
  212. package/src/types/defender.ts +24 -0
  213. package/src/types/deployments.ts +102 -0
  214. package/src/types/index.ts +4 -0
  215. package/src/types/rpc.ts +37 -0
  216. package/src/utils/common.ts +54 -0
  217. package/src/utils/configurationResolver.ts +141 -0
  218. package/src/utils/contractMapping.ts +220 -0
  219. package/src/utils/defenderClients.ts +22 -0
  220. package/src/utils/defenderStore.ts +62 -0
  221. package/src/utils/diamondAbiGenerator.ts +523 -0
  222. package/src/utils/diffDeployedFacets.ts +131 -0
  223. package/src/utils/index.ts +15 -0
  224. package/src/utils/loupe.ts +159 -0
  225. package/src/utils/rpcStore.ts +152 -0
  226. package/src/utils/signer.ts +93 -0
  227. package/src/utils/txlogging.ts +97 -0
  228. package/src/utils/workspaceSetup.ts +315 -0
  229. package/test/README.md +136 -0
@@ -0,0 +1,131 @@
1
+ import { Fragment, Interface, JsonFragment } from "@ethersproject/abi";
2
+ import { Signer } from "@ethersproject/abstract-signer";
3
+ import chalk from "chalk";
4
+ import { JsonRpcProvider } from "ethers";
5
+ import { DeployedDiamondData } from "../schemas";
6
+ import { getDeployedFacets } from "./loupe";
7
+
8
+ export async function diffDeployedFacets(
9
+ deployedDiamondData: DeployedDiamondData,
10
+ signerOrProvider: Signer | JsonRpcProvider,
11
+ verboseGetDeployedFacets?: boolean,
12
+ ): Promise<boolean> {
13
+ const diamondAddress = deployedDiamondData.DiamondAddress!;
14
+ const onChainFacets = await getDeployedFacets(diamondAddress, signerOrProvider as any, undefined, verboseGetDeployedFacets);
15
+
16
+ const localFacets = deployedDiamondData.DeployedFacets ?? {};
17
+
18
+ const seen = new Set<string>();
19
+
20
+ let pass: boolean = true;
21
+
22
+ console.log(chalk.magentaBright("\n🔍 Diffing on-chain facets against deployment metadata:\n"));
23
+
24
+ for (const facet of onChainFacets) {
25
+ const match = Object.entries(localFacets).find(([_, meta]) => meta.address?.toLowerCase() === facet.facetAddress.toLowerCase());
26
+ if (!match) {
27
+ console.log(chalk.red(` ❌ On-chain facet ${facet.facetAddress} not found in deployment record.`));
28
+ continue;
29
+ }
30
+
31
+ const [name, meta] = match;
32
+ seen.add(name);
33
+
34
+ const expected = meta.funcSelectors ?? [];
35
+ const actual = facet.functionSelectors;
36
+
37
+ const added: string[] = actual.filter((sel: string) => !expected.includes(sel));
38
+ const removed = expected.filter(sel => !actual.includes(sel));
39
+
40
+ if (added.length ?? removed.length) {
41
+ console.log(chalk.yellow(` ⚠️ Mismatch in selectors for facet ${name} (${facet.facetAddress})`));
42
+ if (added.length) console.log(chalk.green(` + Added: ${added.join(", ")}`));
43
+ if (removed.length) console.log(chalk.red(` - Missing: ${removed.join(", ")}`));
44
+ } else {
45
+ console.log(chalk.green(` ✅ ${name} matches.`));
46
+ }
47
+ }
48
+
49
+ for (const localFacetName of Object.keys(localFacets)) {
50
+ if (pass && !seen.has(localFacetName)) {
51
+ console.log(chalk.red(` ❌ Deployed facet ${localFacetName} missing from on-chain state.`));
52
+ pass = false;
53
+ }
54
+ }
55
+ if (pass) {
56
+ console.log(chalk.bgGreenBright(" ✅ All facets exist in deplyoment metadata!"));
57
+ } else {
58
+ console.log(chalk.bgRed(" ❌ Some facets do not match!"));
59
+ }
60
+ return pass;
61
+ }
62
+
63
+ export function printFacetSelectorFunctions(abi: readonly (string | Fragment | JsonFragment)[], selectors: string[]) {
64
+ const iface = new Interface(abi);
65
+
66
+ console.log(chalk.cyan("\n🔎 Matching selectors to functions:"));
67
+ for (const selector of selectors) {
68
+ const fragment = Object.values(iface.functions).find(fn =>
69
+ iface.getSighash(fn) === selector
70
+ );
71
+ console.log(` ${selector} → ${fragment ? fragment.format() : chalk.gray("unknown")}`);
72
+ }
73
+ }
74
+
75
+ export async function isProtocolInitRegistered(
76
+ deployedDiamondData: DeployedDiamondData,
77
+ protocolInitFacet: string,
78
+ initializerSig: string
79
+ ): Promise<boolean> {
80
+ const facet = deployedDiamondData.DeployedFacets?.[protocolInitFacet];
81
+ console.log(`Checking if ${protocolInitFacet} is registered with ${initializerSig}...`);
82
+
83
+ if (!facet || !facet.funcSelectors) return false;
84
+ if (!initializerSig) {
85
+ console.warn(chalk.yellow(` ❌ No initializer signature provided for ${protocolInitFacet}.`));
86
+ return false;
87
+ }
88
+ const iface = new Interface([`function ${initializerSig}`]);
89
+ const selector = iface.getSighash(initializerSig);
90
+ console.log(` Found selector: ${selector}`);
91
+ return facet.funcSelectors.includes(selector);
92
+ }
93
+
94
+ /**
95
+ * Compares facet selectors from on-chain DiamondLoupe with local DeployedDiamondData
96
+ *
97
+ * @param deployedFacetData - local metadata (facetName -> { address, funcSelectors[] })
98
+ * @param onChainFacets - list of { facetAddress, functionSelectors[] } from DiamondLoupe
99
+ * @returns map of facetName -> { extraOnChain[], missingOnChain[], matched[] }
100
+ */
101
+ export function compareFacetSelectors(
102
+ deployedFacetData: Record<string, { address?: string; funcSelectors?: string[] }>,
103
+ onChainFacets: { facetAddress: string; functionSelectors: string[] }[]
104
+ ) {
105
+ const result: Record<string, {
106
+ extraOnChain: string[];
107
+ missingOnChain: string[];
108
+ matched: string[];
109
+ }> = {};
110
+
111
+ const addressToName = Object.entries(deployedFacetData).reduce((acc, [name, meta]) => {
112
+ if (meta.address) acc[meta.address.toLowerCase()] = name;
113
+ return acc;
114
+ }, {} as Record<string, string>);
115
+
116
+ for (const { facetAddress, functionSelectors } of onChainFacets) {
117
+ const lowerAddr = facetAddress.toLowerCase();
118
+ const name = addressToName[lowerAddr] ?? "unknown";
119
+
120
+ const expected = new Set(deployedFacetData[name]?.funcSelectors ?? []);
121
+ const actual = new Set(functionSelectors);
122
+
123
+ result[name] = {
124
+ extraOnChain: [...actual].filter(sel => !expected.has(sel)).sort(),
125
+ missingOnChain: [...expected].filter(sel => !actual.has(sel)).sort(),
126
+ matched: [...actual].filter(sel => expected.has(sel)).sort(),
127
+ };
128
+ }
129
+
130
+ return result;
131
+ }
@@ -0,0 +1,15 @@
1
+ export * from '../repositories/jsonFileHandler';
2
+ export { FileDeploymentRepository } from '../repositories/FileDeploymentRepository';
3
+ export { DeploymentRepository } from '../repositories/DeploymentRepository';
4
+ export * from './common';
5
+ export * from './signer';
6
+ export * from './txlogging';
7
+ export * from './diffDeployedFacets';
8
+ export * from './loupe';
9
+ export * from './defenderStore';
10
+ export * from './defenderClients';
11
+ export * from './contractMapping';
12
+ export * from './configurationResolver';
13
+ export * from './workspaceSetup';
14
+ export * from './diamondAbiGenerator';
15
+ export * from './rpcStore';
@@ -0,0 +1,159 @@
1
+ import '@nomicfoundation/hardhat-ethers';
2
+ import chalk from "chalk";
3
+ import { Contract, ContractTransactionResponse, Interface, InterfaceAbi, Log, LogDescription, Provider, Signer, TransactionReceipt } from "ethers";
4
+ import hre from "hardhat";
5
+ import { logTx } from "./txlogging";
6
+
7
+ /** ----------------------------------------------------------------
8
+ * Minimal ABI for DiamondLoupeFacet.facets()
9
+ * (tuple[] is encoded exactly the same as the struct array returned
10
+ * by the real contract, so we can decode it safely.)
11
+ * ----------------------------------------------------------------
12
+ */
13
+ const DIAMOND_LOUPE_ABI = new Interface([
14
+ "function facets() view returns (tuple(address facetAddress, bytes4[] functionSelectors)[])"
15
+ ]);
16
+
17
+ /** Optional convenience type for TS consumers (no TypeChain) */
18
+ export interface FacetStruct {
19
+ facetAddress: string;
20
+ functionSelectors: string[];
21
+ }
22
+
23
+ // -----------------------------------------------------------------------------
24
+ // logDiamondLoupe ­– decodes *events* that were emitted during a diamondCut()
25
+ // -----------------------------------------------------------------------------
26
+ /**
27
+ * logDiamondLoupe
28
+ * • pretty‑prints the transaction receipt
29
+ * • decodes the logs emitted by the DiamondLoupeFacet
30
+ * • decodes the logs emitted by the facets that were passed in
31
+ * (e.g. the facets that were added/removed)
32
+ * • prints the decoded events to the console
33
+ * • returns the transaction receipt
34
+ * @param tx The awaited transaction object (e.g. from `contract.fn()`)
35
+ * @param diamondLoupe Address of the diamond proxy
36
+ * @param facetABIs One or more `ethers.Interface` (or plain ABIs) used to decode
37
+ * the `receipt.logs`. Pass the primary contract interface first;
38
+ * additional ABIs (e.g. library or facet events) can follow.
39
+ * Pass an empty array if you don’t want to decode any facet events.
40
+ * (e.g. `[]`)
41
+ * (NB: this is not the same as the diamondLoupe address)
42
+ * @returns The transaction receipt
43
+ */
44
+ export async function logDiamondLoupe(
45
+ tx: ContractTransactionResponse,
46
+ diamondLoupe: string,
47
+ facetABIs: readonly (InterfaceAbi | Interface)[] = []
48
+ ): Promise<TransactionReceipt> {
49
+ const receipt = await logTx(tx, `DiamondLoupe (${diamondLoupe})`, facetABIs.map(abi => abi instanceof Interface ? abi : new Interface(abi)));
50
+
51
+ const iface = Array.isArray(facetABIs) && facetABIs.length
52
+ ? new Interface(
53
+ facetABIs.map(i => (i instanceof Interface ? i.fragments : i)).reduce((acc, val) => acc.concat(val), [])
54
+ )
55
+ : undefined;
56
+
57
+ console.log(chalk.cyan("\n📜 Decoded Loupe logs:"));
58
+ if (receipt && receipt.logs) {
59
+ receipt.logs.forEach((log: Log, idx: number) => {
60
+ if (!iface) {
61
+ console.log(chalk.dim(` Event[${idx}] – no ABI supplied`));
62
+ return;
63
+ }
64
+
65
+ let parsed: LogDescription | undefined;
66
+ try {
67
+ const result = iface.parseLog(log);
68
+ if (result !== null) {
69
+ parsed = result;
70
+ }
71
+ } catch {
72
+ /* swallow – this log wasn’t emitted by any of the supplied facets */
73
+ }
74
+
75
+ if (parsed) {
76
+ const argsPretty = parsed.args
77
+ .map((arg: unknown, i: number) => `${parsed?.fragment.inputs[i].name}: ${arg}`)
78
+ .join(", ");
79
+
80
+ console.log(
81
+ chalk.yellowBright(` Event[${idx}]`) +
82
+ chalk.bold(` ${parsed.name}`) +
83
+ ` → ${argsPretty}`
84
+ );
85
+ } else {
86
+ console.log(
87
+ chalk.dim(` Event[${idx}]`) + ` – unable to decode (topic0 = ${log.topics[0]})`
88
+ );
89
+ }
90
+ });
91
+
92
+ return receipt;
93
+ }
94
+
95
+ // Always return the receipt, even if logs are missing
96
+ if (!receipt) {
97
+ throw new Error("Transaction receipt is null");
98
+ }
99
+ return receipt;
100
+ }
101
+
102
+ /**
103
+ * Fetch the list of deployed facets from a diamond without relying on
104
+ * TypeChain’s IDiamondLoupe typings.
105
+ * getDeployedFacets – convenience wrapper around DiamondLoupe.facets()
106
+ * • fetches the facet list
107
+ * • pretty‑prints it
108
+ * • (optionally) calls logDiamondLoupe if you pass a receipt
109
+ *
110
+ * @param diamondAddress Address of the diamond proxy
111
+ * @param signerOrProvider Signer (for tx) or provider (for read‑only)
112
+ * @param receiptToDecode Optional transaction receipt to decode
113
+ * (e.g. from a diamondCut() transaction)
114
+ * @returns Array of FacetStruct objects
115
+ * (address + functionSelectors)
116
+ * (see DiamondLoupeFacet.sol)
117
+ */
118
+ export async function getDeployedFacets(
119
+ diamondAddress: string,
120
+ signerOrProvider: Signer | Provider = hre.ethers.provider,
121
+ receiptToDecode?: TransactionReceipt,
122
+ logDeployedFacets?: boolean, // default: assumed false
123
+ ): Promise<FacetStruct[]> {
124
+ // Generic ethers.Contract instance built only from the tiny ABI above
125
+ const loupe = new Contract(diamondAddress, DIAMOND_LOUPE_ABI, signerOrProvider);
126
+
127
+ // NB: cast is just to help TS‑users downstream; at runtime this is the raw array
128
+ const facets = (await loupe.facets()) as FacetStruct[];
129
+
130
+ if (logDeployedFacets === true) {
131
+ console.log(chalk.magentaBright("\n🔍 Currently deployed facets (via DiamondLoupe):"));
132
+ facets.forEach((f: { facetAddress: string; functionSelectors: string[] }, i: number) => {
133
+ console.log(chalk.blueBright(
134
+ ` [${i.toString().padStart(2, "0")}] ${chalk.bold(f.facetAddress)} – ${f.functionSelectors.length
135
+ } selector${f.functionSelectors.length === 1 ? "" : "s"}`
136
+ ));
137
+ f.functionSelectors.forEach((s: string, j: number) => {
138
+ console.log(` ${j.toString().padStart(2, "0")}: ${s}`);
139
+ });
140
+ console.log();
141
+ });
142
+ }
143
+
144
+ // If the caller supplied a receipt from a recent diamondCut, decode it too
145
+ if (receiptToDecode) {
146
+ await logDiamondLoupe(
147
+ // fabricate a ContractTransaction‑like object so logDiamondLoupe
148
+ // can stay reception‑agnostic
149
+ {
150
+ ...receiptToDecode,
151
+ wait: async () => receiptToDecode,
152
+ } as unknown as ContractTransactionResponse,
153
+ diamondAddress,
154
+ [loupe.interface]
155
+ );
156
+ }
157
+
158
+ return facets;
159
+ }
@@ -0,0 +1,152 @@
1
+ import { join } from 'path';
2
+ import * as fs from 'fs-extra';
3
+ import { RPCDeploymentRegistry, RPCStepStatus, RPCStepRecord } from '../types/rpc';
4
+
5
+ /**
6
+ * Step-by-step deployment store for RPC-based deployments
7
+ * Similar to DefenderDeploymentStore but for RPC deployments
8
+ */
9
+ export class RPCDeploymentStore {
10
+ private readonly filePath: string;
11
+ private readonly diamondName: string;
12
+ private readonly deploymentId: string;
13
+
14
+ constructor(diamondName: string, deploymentId: string, baseDir: string = 'diamonds') {
15
+ this.diamondName = diamondName;
16
+ this.deploymentId = deploymentId;
17
+ const registryDir = join(baseDir, diamondName, 'deployments', 'rpc');
18
+ fs.ensureDirSync(registryDir);
19
+ this.filePath = join(registryDir, `${deploymentId}.json`);
20
+ }
21
+
22
+ private loadRegistry(): RPCDeploymentRegistry {
23
+ if (!fs.existsSync(this.filePath)) {
24
+ return {
25
+ diamondName: this.diamondName,
26
+ deploymentId: this.deploymentId,
27
+ network: '',
28
+ chainId: 0,
29
+ rpcUrl: '',
30
+ deployerAddress: '',
31
+ steps: [],
32
+ startedAt: Date.now(),
33
+ lastUpdated: Date.now()
34
+ };
35
+ }
36
+ return fs.readJSONSync(this.filePath);
37
+ }
38
+
39
+ private saveRegistry(registry: RPCDeploymentRegistry) {
40
+ registry.lastUpdated = Date.now();
41
+ fs.writeJSONSync(this.filePath, registry, { spaces: 2 });
42
+ }
43
+
44
+ public initializeDeployment(network: string, chainId: number, rpcUrl: string, deployerAddress: string): void {
45
+ const registry = this.loadRegistry();
46
+ registry.network = network;
47
+ registry.chainId = chainId;
48
+ registry.rpcUrl = rpcUrl;
49
+ registry.deployerAddress = deployerAddress;
50
+ this.saveRegistry(registry);
51
+ }
52
+
53
+ public saveStep(step: RPCStepRecord): void {
54
+ const registry = this.loadRegistry();
55
+ const existing = registry.steps.find(s => s.stepName === step.stepName);
56
+ if (existing) {
57
+ Object.assign(existing, step);
58
+ } else {
59
+ registry.steps.push(step);
60
+ }
61
+ this.saveRegistry(registry);
62
+ }
63
+
64
+ public getStep(stepName: string): RPCStepRecord | undefined {
65
+ return this.loadRegistry().steps.find(s => s.stepName === stepName);
66
+ }
67
+
68
+ public updateStatus(stepName: string, status: RPCStepStatus, txHash?: string, contractAddress?: string, error?: string): void {
69
+ const registry = this.loadRegistry();
70
+ const step = registry.steps.find(s => s.stepName === stepName);
71
+ if (step) {
72
+ step.status = status;
73
+ step.timestamp = Date.now();
74
+ if (txHash) step.txHash = txHash;
75
+ if (contractAddress) step.contractAddress = contractAddress;
76
+ if (error) step.error = error;
77
+ this.saveRegistry(registry);
78
+ }
79
+ }
80
+
81
+ public list(): RPCStepRecord[] {
82
+ return this.loadRegistry().steps;
83
+ }
84
+
85
+ public getRegistry(): RPCDeploymentRegistry {
86
+ return this.loadRegistry();
87
+ }
88
+
89
+ public isStepCompleted(stepName: string): boolean {
90
+ const step = this.getStep(stepName);
91
+ return step?.status === 'completed';
92
+ }
93
+
94
+ public isStepFailed(stepName: string): boolean {
95
+ const step = this.getStep(stepName);
96
+ return step?.status === 'failed';
97
+ }
98
+
99
+ public getCompletedSteps(): RPCStepRecord[] {
100
+ return this.list().filter(s => s.status === 'completed');
101
+ }
102
+
103
+ public getFailedSteps(): RPCStepRecord[] {
104
+ return this.list().filter(s => s.status === 'failed');
105
+ }
106
+
107
+ public getPendingSteps(): RPCStepRecord[] {
108
+ return this.list().filter(s => s.status === 'pending' || s.status === 'in_progress');
109
+ }
110
+
111
+ public markDeploymentComplete(): void {
112
+ const registry = this.loadRegistry();
113
+ registry.completedAt = Date.now();
114
+ this.saveRegistry(registry);
115
+ }
116
+
117
+ public markDeploymentFailed(error: string): void {
118
+ const registry = this.loadRegistry();
119
+ registry.failedAt = Date.now();
120
+ registry.lastError = error;
121
+ this.saveRegistry(registry);
122
+ }
123
+
124
+ public clearFailedSteps(): void {
125
+ const registry = this.loadRegistry();
126
+ registry.steps = registry.steps.filter(s => s.status !== 'failed');
127
+ this.saveRegistry(registry);
128
+ }
129
+
130
+ public getDeploymentSummary(): {
131
+ total: number;
132
+ completed: number;
133
+ failed: number;
134
+ pending: number;
135
+ isComplete: boolean;
136
+ hasFailed: boolean;
137
+ } {
138
+ const steps = this.list();
139
+ const completed = steps.filter(s => s.status === 'completed').length;
140
+ const failed = steps.filter(s => s.status === 'failed').length;
141
+ const pending = steps.filter(s => s.status === 'pending' || s.status === 'in_progress').length;
142
+
143
+ return {
144
+ total: steps.length,
145
+ completed,
146
+ failed,
147
+ pending,
148
+ isComplete: failed === 0 && pending === 0 && completed > 0,
149
+ hasFailed: failed > 0
150
+ };
151
+ }
152
+ }
@@ -0,0 +1,93 @@
1
+ import hre from 'hardhat';
2
+ import { toWei } from './common';
3
+ import { JsonRpcProvider, Signer } from 'ethers';
4
+ import '@nomicfoundation/hardhat-ethers';
5
+ import { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider';
6
+
7
+ /**
8
+ * Impersonates a signer account. This is primarily used in Hardhat's testing environment
9
+ * to simulate actions from accounts that are not part of the default test accounts.
10
+ *
11
+ * @param signerAddress - The address of the account to impersonate.
12
+ * @returns The impersonated signer object.
13
+ */
14
+ export async function impersonateSigner(signerAddress: string, provider: JsonRpcProvider): Promise<Signer> {
15
+ // Request Hardhat to impersonate the account at the specified address
16
+ await provider.send("hardhat_impersonateAccount", [signerAddress]);
17
+ return provider.getSigner(signerAddress); // Return the impersonated signer
18
+ }
19
+
20
+ /**
21
+ * Sets the Ether balance for a specified address in the Hardhat testing environment.
22
+ * This is useful for ensuring test accounts have sufficient funds for transactions.
23
+ *
24
+ * @param address - The address to set the Ether balance for.
25
+ * @param amount - The desired balance as a `BigNumber`.
26
+ */
27
+ export async function setEtherBalance(address: string, amount: bigint, provider: JsonRpcProvider) {
28
+ await provider.send('hardhat_setBalance', [
29
+ address, // Address to modify the balance of
30
+ '0x' + amount.toString(16), // Amount to set, formatted as a hex string
31
+ ]);
32
+ }
33
+
34
+ /**
35
+ * Impersonates the deployer account and funds it to a balance that is rounded to the next highest 100 ETH.
36
+ *
37
+ * @param provider - The ethers provider instance.
38
+ * @param deployerAddress - The address of the deployer account.
39
+ * @param balance - The balance to set for the deployer account (in hex format).
40
+ */
41
+ export async function impersonateAndFundSigner(deployerAddress: string, provider: JsonRpcProvider | HardhatEthersProvider): Promise<Signer> {
42
+ try {
43
+ await provider.send('hardhat_impersonateAccount', [deployerAddress]);
44
+ const signer = provider.getSigner(deployerAddress);
45
+
46
+ // Fund the account
47
+ await provider.send('hardhat_setBalance', [deployerAddress, '0x56BC75E2D63100000']);
48
+ return signer;
49
+ } catch (error) {
50
+ if (error instanceof Error) {
51
+ console.error(`Impersonation and funding failed for ${deployerAddress}: ${error.message}`);
52
+ } else {
53
+ console.error(`Impersonation and funding failed for ${deployerAddress}: ${String(error)}`);
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+
59
+
60
+ /**
61
+ * Updates the owner of the contract at the specified root address for testing purposes.
62
+ * This involves transferring ownership from the current owner to the default signer in the Hardhat environment.
63
+ *
64
+ * @param rootAddress - The address of the root contract (e.g., GeniusOwnershipFacet).
65
+ * @returns The address of the old owner.
66
+ */
67
+ export const updateOwnerForTest = async (rootAddress: string, provider: JsonRpcProvider) => {
68
+ // Retrieve the current signer in the Hardhat environment
69
+ const curOwner = (await hre.ethers.getSigners())[0];
70
+
71
+ // Get a reference to the GeniusOwnershipFacet contract at the specified root address
72
+ const ownership = await hre.ethers.getContractAt('GeniusOwnershipFacet', rootAddress);
73
+
74
+ // Retrieve the current owner of the contract
75
+ const oldOwnerAddress = await ownership.owner();
76
+
77
+ // Impersonate the old owner
78
+ const oldOwner = await impersonateSigner(oldOwnerAddress, provider);
79
+
80
+ // If the old owner is not the current signer, transfer ownership to the current signer
81
+ if (oldOwnerAddress !== curOwner.address) {
82
+ debuglog(`Transferring ownership from ${oldOwnerAddress}`);
83
+
84
+ // Ensure the old owner has enough Ether to perform the ownership transfer
85
+ await setEtherBalance(oldOwnerAddress, toWei(10), provider);
86
+
87
+ await (ownership as any).connect(await oldOwner).transferOwnership(curOwner.address);
88
+ await (ownership as any).connect(oldOwner).transferOwnership(curOwner.address);
89
+ }
90
+
91
+ // Return the address of the old owner
92
+ return oldOwnerAddress;
93
+ };
@@ -0,0 +1,97 @@
1
+ // ──────────────────────────────────────────────────────────────────────────────
2
+ // txLogging.ts
3
+ // Extra helpers for inspecting diamond‑cut transactions
4
+ // ──────────────────────────────────────────────────────────────────────────────
5
+ import "@nomicfoundation/hardhat-ethers";
6
+ import chalk from "chalk";
7
+ import { ContractTransactionResponse, Interface, InterfaceAbi, Log, LogDescription, TransactionReceipt } from "ethers";
8
+ import hre from "hardhat";
9
+
10
+
11
+ /**
12
+ * Pretty‑prints a transaction receipt **and** decodes its logs.
13
+ *
14
+ * @param tx The awaited transaction object (e.g. from `contract.fn()`)
15
+ * @param description Optional label that will be shown in the console header.
16
+ * @param interfaces One or more `ethers.Interface` (or plain ABIs) used to decode
17
+ * the `receipt.logs`. Pass the primary contract interface first;
18
+ * additional ABIs (e.g. library or facet events) can follow.
19
+ */
20
+ export async function logTx(
21
+ tx: ContractTransactionResponse,
22
+ description = "",
23
+ interfaces: (Interface | InterfaceAbi)[] = [],
24
+ // verbose?: boolean
25
+ ): Promise<TransactionReceipt> {
26
+ // normalise incoming interfaces => ethers.Interface
27
+ const decoders: Interface[] = interfaces.map((i) =>
28
+ i instanceof Interface ? i : new Interface(i)
29
+ );
30
+
31
+ const receipt = await tx.wait();
32
+ if (!receipt) {
33
+ throw new Error("Transaction receipt is null");
34
+ }
35
+
36
+ /* --------------------------- basic tx statistics -------------------------- */
37
+ console.log(chalk.green(`\n⛓️ Transaction Details${description ? ` – ${description}` : ""}`));
38
+ console.table({
39
+ Hash: tx.hash,
40
+ Status: receipt.status === 1 ? "Success" : "Failed",
41
+ Block: receipt.blockNumber,
42
+ From: receipt.from,
43
+ To: receipt.to,
44
+ "Tx Index": (receipt as any).transactionIndex ?? receipt.index ?? "N/A",
45
+ "Gas Used": receipt.gasUsed.toString(),
46
+ "Cumulative Gas": receipt.cumulativeGasUsed.toString(),
47
+ "Gas Price": (receipt as any).gasPrice?.toString() ?? "N/A",
48
+ "Block Hash": receipt.blockHash,
49
+ "Confirmations": receipt.confirmations,
50
+ "Timestamp": receipt.blockNumber
51
+ ? new Date(((await tx.wait().then(() => hre.ethers.provider.getBlock(receipt.blockNumber))) ?? { timestamp: 0 }).timestamp * 1000).toLocaleString()
52
+ : "N/A",
53
+ "Created Contract": receipt.contractAddress ?? "N/A",
54
+ "Created By": receipt.from,
55
+
56
+ Events: receipt.logs.length,
57
+ });
58
+
59
+ /* ----------------------------- decode events ------------------------------ */
60
+ if (receipt.logs.length === 0) return receipt;
61
+ console.log(chalk.cyan("\n📜 Decoded events:"));
62
+ receipt.logs.forEach((log: Log, idx: number) => {
63
+ let parsed: LogDescription | undefined;
64
+ for (const iface of decoders) {
65
+ try {
66
+ const result = iface.parseLog(log);
67
+ if (result !== null) {
68
+ parsed = result;
69
+ break; // stop at first successful decoder
70
+ }
71
+ } catch (_) {
72
+ /* ignore and try next */
73
+ }
74
+ }
75
+
76
+ if (parsed) {
77
+ const argsPretty = parsed.args
78
+ .map((arg: unknown, i: number) => {
79
+ const key = parsed?.fragment.inputs[i].name;
80
+ return `${key ? key + ": " : ""}${arg}`;
81
+ })
82
+ .join(", ");
83
+
84
+ console.log(
85
+ chalk.yellowBright(` Event[${idx}]`) +
86
+ chalk.bold(` ${parsed.name}`) +
87
+ ` → ${argsPretty}`
88
+ );
89
+ } else {
90
+ console.log(
91
+ chalk.dim(` Event[${idx}]`) + ` – unable to decode (topic0 = ${log.topics[0]})`
92
+ );
93
+ }
94
+ });
95
+
96
+ return receipt;
97
+ }