@diamondslab/diamonds-hardhat-foundry 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.
- package/CHANGELOG.md +66 -0
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/contracts/DiamondForgeHelpers.sol +96 -0
- package/contracts/DiamondFuzzBase.sol +128 -0
- package/package.json +78 -0
- package/src/foundry.ts +134 -0
- package/src/framework/DeploymentManager.ts +210 -0
- package/src/framework/ForgeFuzzingFramework.ts +194 -0
- package/src/framework/HelperGenerator.ts +246 -0
- package/src/index.ts +166 -0
- package/src/tasks/deploy.ts +110 -0
- package/src/tasks/generate-helpers.ts +101 -0
- package/src/tasks/init.ts +90 -0
- package/src/tasks/test.ts +108 -0
- package/src/templates/DiamondDeployment.sol.template +38 -0
- package/src/templates/ExampleFuzzTest.t.sol.template +109 -0
- package/src/templates/ExampleIntegrationTest.t.sol.template +79 -0
- package/src/templates/ExampleUnitTest.t.sol.template +59 -0
- package/src/types/config.ts +54 -0
- package/src/types/hardhat.ts +24 -0
- package/src/utils/foundry.ts +189 -0
- package/src/utils/logger.ts +66 -0
- package/src/utils/validation.ts +144 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.0;
|
|
3
|
+
|
|
4
|
+
import "forge-std/Test.sol";
|
|
5
|
+
import "forge-std/console.sol";
|
|
6
|
+
import "./DiamondForgeHelpers.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title DiamondFuzzBase
|
|
10
|
+
* @notice Base contract for Diamond fuzz testing with Forge
|
|
11
|
+
* @dev Provides setup methods and utilities for fuzz testing Diamond contracts
|
|
12
|
+
*/
|
|
13
|
+
abstract contract DiamondFuzzBase is Test {
|
|
14
|
+
using DiamondForgeHelpers for address;
|
|
15
|
+
|
|
16
|
+
// Diamond contract address (set by child contracts)
|
|
17
|
+
address public diamond;
|
|
18
|
+
|
|
19
|
+
// Facet addresses (set by child contracts)
|
|
20
|
+
mapping(string => address) public facets;
|
|
21
|
+
|
|
22
|
+
// Test accounts
|
|
23
|
+
address public deployer;
|
|
24
|
+
address public user1;
|
|
25
|
+
address public user2;
|
|
26
|
+
address public user3;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @notice Setup function called before each test
|
|
30
|
+
* @dev Override this in child contracts to set up Diamond and facets
|
|
31
|
+
*/
|
|
32
|
+
function setUp() public virtual {
|
|
33
|
+
// Set up test accounts
|
|
34
|
+
deployer = address(this);
|
|
35
|
+
user1 = makeAddr("user1");
|
|
36
|
+
user2 = makeAddr("user2");
|
|
37
|
+
user3 = makeAddr("user3");
|
|
38
|
+
|
|
39
|
+
// Fund test accounts
|
|
40
|
+
vm.deal(user1, 100 ether);
|
|
41
|
+
vm.deal(user2, 100 ether);
|
|
42
|
+
vm.deal(user3, 100 ether);
|
|
43
|
+
|
|
44
|
+
// Child contracts should override and call setDiamondAddress()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @notice Set the Diamond contract address
|
|
49
|
+
* @param _diamond Address of the deployed Diamond
|
|
50
|
+
*/
|
|
51
|
+
function setDiamondAddress(address _diamond) internal {
|
|
52
|
+
require(_diamond != address(0), "Diamond address cannot be zero");
|
|
53
|
+
diamond = _diamond;
|
|
54
|
+
DiamondForgeHelpers.assertValidDiamond(_diamond);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @notice Register a facet address
|
|
59
|
+
* @param name Name of the facet
|
|
60
|
+
* @param facetAddress Address of the facet
|
|
61
|
+
*/
|
|
62
|
+
function registerFacet(string memory name, address facetAddress) internal {
|
|
63
|
+
require(facetAddress != address(0), "Facet address cannot be zero");
|
|
64
|
+
facets[name] = facetAddress;
|
|
65
|
+
DiamondForgeHelpers.assertValidFacet(facetAddress, name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @notice Get a facet address by name
|
|
70
|
+
* @param name Name of the facet
|
|
71
|
+
* @return Address of the facet
|
|
72
|
+
*/
|
|
73
|
+
function getFacet(string memory name) internal view returns (address) {
|
|
74
|
+
address facetAddress = facets[name];
|
|
75
|
+
require(facetAddress != address(0), string(abi.encodePacked("Facet not found: ", name)));
|
|
76
|
+
return facetAddress;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @notice Assume valid Ethereum address for fuzzing
|
|
81
|
+
* @param addr Address to validate
|
|
82
|
+
*/
|
|
83
|
+
function assumeValidAddress(address addr) internal pure {
|
|
84
|
+
vm.assume(addr != address(0));
|
|
85
|
+
vm.assume(addr != address(0xdead));
|
|
86
|
+
vm.assume(uint160(addr) > 0xFF); // Avoid precompiles
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @notice Assume valid amount for fuzzing (not zero, not unreasonably large)
|
|
91
|
+
* @param amount Amount to validate
|
|
92
|
+
*/
|
|
93
|
+
function assumeValidAmount(uint256 amount) internal pure {
|
|
94
|
+
vm.assume(amount > 0);
|
|
95
|
+
vm.assume(amount < type(uint128).max); // Reasonable upper bound
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @notice Bound a fuzzed value to a specific range
|
|
100
|
+
* @param value The fuzzed value
|
|
101
|
+
* @param min Minimum value (inclusive)
|
|
102
|
+
* @param max Maximum value (inclusive)
|
|
103
|
+
* @return Bounded value
|
|
104
|
+
*/
|
|
105
|
+
function boundValue(uint256 value, uint256 min, uint256 max) internal pure returns (uint256) {
|
|
106
|
+
return bound(value, min, max);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @notice Log test context for debugging
|
|
111
|
+
* @param testName Name of the test
|
|
112
|
+
*/
|
|
113
|
+
function logTestContext(string memory testName) internal view {
|
|
114
|
+
console.log("=== Test:", testName, "===");
|
|
115
|
+
console.log("Diamond:", diamond);
|
|
116
|
+
console.log("Deployer:", deployer);
|
|
117
|
+
console.log("Block number:", block.number);
|
|
118
|
+
console.log("Block timestamp:", block.timestamp);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @notice Expect a revert with specific error message
|
|
123
|
+
* @param errorMessage Expected error message
|
|
124
|
+
*/
|
|
125
|
+
function expectRevertWithMessage(string memory errorMessage) internal {
|
|
126
|
+
vm.expectRevert(bytes(errorMessage));
|
|
127
|
+
}
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diamondslab/diamonds-hardhat-foundry",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Hardhat plugin that integrates Foundry testing with Diamond proxy contracts, providing deployment helpers and fuzzing support",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/diamondslab/diamonds-hardhat-foundry",
|
|
8
|
+
"directory": "packages/diamonds-hardhat-foundry"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/diamondslab/diamonds-hardhat-foundry",
|
|
11
|
+
"author": "Am0rfu5",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "dist/src/index.js",
|
|
14
|
+
"types": "dist/src/index.d.ts",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ethereum",
|
|
17
|
+
"smart-contracts",
|
|
18
|
+
"hardhat",
|
|
19
|
+
"hardhat-plugin",
|
|
20
|
+
"foundry",
|
|
21
|
+
"forge",
|
|
22
|
+
"diamondslab",
|
|
23
|
+
"diamonds",
|
|
24
|
+
"diamond",
|
|
25
|
+
"solidity",
|
|
26
|
+
"erc-2535",
|
|
27
|
+
"fuzzing",
|
|
28
|
+
"testing",
|
|
29
|
+
"diamond-proxy"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"lint": "yarn prettier --check && yarn eslint",
|
|
33
|
+
"lint:fix": "yarn prettier --write && yarn eslint --fix",
|
|
34
|
+
"eslint": "eslint 'src/**/*.ts'",
|
|
35
|
+
"prettier": "prettier \"**/*.{js,md,json}\"",
|
|
36
|
+
"pretest": "cd ../.. && yarn build",
|
|
37
|
+
"test": "mocha --recursive \"test/**/*.ts\" --exit",
|
|
38
|
+
"build": "tsc --build .",
|
|
39
|
+
"prepublishOnly": "yarn build",
|
|
40
|
+
"clean": "rimraf dist"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist/src/",
|
|
44
|
+
"src/",
|
|
45
|
+
"contracts/",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
"README.md"
|
|
48
|
+
],
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/chai": "^4.2.0",
|
|
51
|
+
"@types/debug": "^4.1.12",
|
|
52
|
+
"@types/mocha": ">=9.1.0",
|
|
53
|
+
"@types/node": "^20.0.0",
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "5.61.0",
|
|
55
|
+
"@typescript-eslint/parser": "5.61.0",
|
|
56
|
+
"chai": "^4.2.0",
|
|
57
|
+
"eslint": "^8.44.0",
|
|
58
|
+
"eslint-config-prettier": "8.3.0",
|
|
59
|
+
"eslint-plugin-import": "2.27.5",
|
|
60
|
+
"eslint-plugin-mocha": "10.4.1",
|
|
61
|
+
"eslint-plugin-prettier": "3.4.0",
|
|
62
|
+
"hardhat": "^2.26.0",
|
|
63
|
+
"mocha": "^10.0.0",
|
|
64
|
+
"prettier": "2.4.1",
|
|
65
|
+
"rimraf": "^3.0.2",
|
|
66
|
+
"ts-node": "^10.8.0",
|
|
67
|
+
"typescript": "~5.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@diamondslab/diamonds": "^1.2.1",
|
|
71
|
+
"@diamondslab/hardhat-diamonds": "^1.1.9",
|
|
72
|
+
"hardhat": "^2.26.0"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"picocolors": "^1.1.0"
|
|
76
|
+
},
|
|
77
|
+
"packageManager": "yarn@4.10.3"
|
|
78
|
+
}
|
package/src/foundry.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { exec as execCallback, execSync } from "child_process";
|
|
2
|
+
import { NomicLabsHardhatPluginError } from "hardhat/internal/core/errors";
|
|
3
|
+
import picocolors from "picocolors";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
|
|
6
|
+
const exec = promisify(execCallback);
|
|
7
|
+
|
|
8
|
+
type Remappings = Record<string, string>;
|
|
9
|
+
|
|
10
|
+
let cachedRemappings: Promise<Remappings> | undefined;
|
|
11
|
+
|
|
12
|
+
export class HardhatFoundryError extends NomicLabsHardhatPluginError {
|
|
13
|
+
constructor(message: string, parent?: Error) {
|
|
14
|
+
super("diamonds-hardhat-foundry", message, parent);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ForgeInstallError extends HardhatFoundryError {
|
|
19
|
+
constructor(dependency: string, parent: Error) {
|
|
20
|
+
super(
|
|
21
|
+
`Couldn't install '${dependency}', please install it manually.
|
|
22
|
+
|
|
23
|
+
${parent.message}
|
|
24
|
+
`,
|
|
25
|
+
parent
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getForgeConfig() {
|
|
31
|
+
return JSON.parse(runCmdSync("forge config --json"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseRemappings(remappingsTxt: string): Remappings {
|
|
35
|
+
const remappings: Remappings = {};
|
|
36
|
+
const remappingLines = remappingsTxt.split(/\r\n|\r|\n/);
|
|
37
|
+
for (const remappingLine of remappingLines) {
|
|
38
|
+
if (remappingLine.trim() === "") {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (remappingLine.includes(":")) {
|
|
43
|
+
throw new HardhatFoundryError(
|
|
44
|
+
`Invalid remapping '${remappingLine}', remapping contexts are not allowed`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!remappingLine.includes("=")) {
|
|
49
|
+
throw new HardhatFoundryError(
|
|
50
|
+
`Invalid remapping '${remappingLine}', remappings without a target are not allowed`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fromTo = remappingLine.split("=");
|
|
55
|
+
|
|
56
|
+
// if the remapping already exists, we ignore it because the first one wins
|
|
57
|
+
if (remappings[fromTo[0]] !== undefined) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
remappings[fromTo[0]] = fromTo[1];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return remappings;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getRemappings() {
|
|
68
|
+
// Get remappings only once
|
|
69
|
+
if (cachedRemappings === undefined) {
|
|
70
|
+
cachedRemappings = runCmd("forge remappings").then(parseRemappings);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return cachedRemappings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function installDependency(dependency: string) {
|
|
77
|
+
// Check if --no-commit flag is supported. Best way is checking the help text
|
|
78
|
+
const helpText = await runCmd("forge install --help");
|
|
79
|
+
const useNoCommitFlag = helpText.includes("--no-commit");
|
|
80
|
+
|
|
81
|
+
const cmd = `forge install ${
|
|
82
|
+
useNoCommitFlag ? "--no-commit" : ""
|
|
83
|
+
} ${dependency}`;
|
|
84
|
+
|
|
85
|
+
console.log(`Running '${picocolors.blue(cmd)}'`);
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await exec(cmd);
|
|
89
|
+
} catch (error: any) {
|
|
90
|
+
throw new ForgeInstallError(dependency, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runCmdSync(cmd: string): string {
|
|
95
|
+
try {
|
|
96
|
+
return execSync(cmd, { stdio: "pipe" }).toString();
|
|
97
|
+
} catch (error: any) {
|
|
98
|
+
const pluginError = buildForgeExecutionError(
|
|
99
|
+
error.status,
|
|
100
|
+
error.stderr.toString()
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
throw pluginError;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function runCmd(cmd: string): Promise<string> {
|
|
108
|
+
try {
|
|
109
|
+
const { stdout } = await exec(cmd);
|
|
110
|
+
return stdout;
|
|
111
|
+
} catch (error: any) {
|
|
112
|
+
throw buildForgeExecutionError(error.code, error.message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildForgeExecutionError(
|
|
117
|
+
exitCode: number | undefined,
|
|
118
|
+
message: string
|
|
119
|
+
) {
|
|
120
|
+
switch (exitCode) {
|
|
121
|
+
case 127:
|
|
122
|
+
return new HardhatFoundryError(
|
|
123
|
+
"Couldn't run `forge`. Please check that your foundry installation is correct."
|
|
124
|
+
);
|
|
125
|
+
case 134:
|
|
126
|
+
return new HardhatFoundryError(
|
|
127
|
+
"Running `forge` failed. Please check that your foundry.toml file is correct."
|
|
128
|
+
);
|
|
129
|
+
default:
|
|
130
|
+
return new HardhatFoundryError(
|
|
131
|
+
`Unexpected error while running \`forge\`: ${message}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Diamond } from "@diamondslab/diamonds";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { HardhatRuntimeEnvironment } from "hardhat/types";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { Logger } from "../utils/logger";
|
|
6
|
+
|
|
7
|
+
// Type for LocalDiamondDeployer (avoiding import issues)
|
|
8
|
+
type LocalDiamondDeployerConfig = {
|
|
9
|
+
diamondName: string;
|
|
10
|
+
networkName: string;
|
|
11
|
+
provider: any;
|
|
12
|
+
chainId: number;
|
|
13
|
+
writeDeployedDiamondData: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* DeploymentManager - Manages Diamond deployment lifecycle for Forge testing
|
|
18
|
+
*
|
|
19
|
+
* Note: This class dynamically requires LocalDiamondDeployer from the workspace
|
|
20
|
+
* to avoid module resolution issues in the published package.
|
|
21
|
+
*/
|
|
22
|
+
export class DeploymentManager {
|
|
23
|
+
constructor(private hre: HardhatRuntimeEnvironment) {}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get LocalDiamondDeployer class
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
private async getDeployerClass(): Promise<any> {
|
|
30
|
+
// LocalDiamondDeployer is in the workspace scripts, not exported from the module
|
|
31
|
+
// This will need to be available in user projects
|
|
32
|
+
const localDeployerPath = join(
|
|
33
|
+
this.hre.config.paths.root,
|
|
34
|
+
"scripts/setup/LocalDiamondDeployer"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const deployer = await import(localDeployerPath);
|
|
39
|
+
return deployer.LocalDiamondDeployer;
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"LocalDiamondDeployer not found. Make sure your project has scripts/setup/LocalDiamondDeployer.ts"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Deploy a Diamond using LocalDiamondDeployer
|
|
49
|
+
* @param diamondName - Name of the Diamond to deploy
|
|
50
|
+
* @param networkName - Target network (hardhat, localhost, anvil)
|
|
51
|
+
* @param force - Force redeployment even if exists
|
|
52
|
+
*/
|
|
53
|
+
async deploy(
|
|
54
|
+
diamondName: string = "ExampleDiamond",
|
|
55
|
+
networkName: string = "hardhat",
|
|
56
|
+
force: boolean = false
|
|
57
|
+
): Promise<Diamond> {
|
|
58
|
+
Logger.section(`Deploying ${diamondName} to ${networkName}`);
|
|
59
|
+
|
|
60
|
+
// Get provider and network info
|
|
61
|
+
const provider = this.hre.ethers.provider;
|
|
62
|
+
const network = await provider.getNetwork();
|
|
63
|
+
const chainId = Number(network.chainId);
|
|
64
|
+
|
|
65
|
+
// Check if deployment exists and handle force flag
|
|
66
|
+
if (!force && this.hasDeploymentRecord(diamondName, networkName, chainId)) {
|
|
67
|
+
Logger.info("Deployment record exists, using existing deployment");
|
|
68
|
+
Logger.info("Use --force to redeploy");
|
|
69
|
+
|
|
70
|
+
const LocalDiamondDeployer = await this.getDeployerClass();
|
|
71
|
+
const deployer = await LocalDiamondDeployer.getInstance({
|
|
72
|
+
diamondName,
|
|
73
|
+
networkName,
|
|
74
|
+
provider,
|
|
75
|
+
chainId,
|
|
76
|
+
writeDeployedDiamondData: true,
|
|
77
|
+
} as LocalDiamondDeployerConfig);
|
|
78
|
+
|
|
79
|
+
return await deployer.getDiamond();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Logger.step("Initializing LocalDiamondDeployer...");
|
|
83
|
+
|
|
84
|
+
const LocalDiamondDeployer = await this.getDeployerClass();
|
|
85
|
+
|
|
86
|
+
const deployer = await LocalDiamondDeployer.getInstance({
|
|
87
|
+
diamondName,
|
|
88
|
+
networkName,
|
|
89
|
+
provider,
|
|
90
|
+
chainId,
|
|
91
|
+
writeDeployedDiamondData: true,
|
|
92
|
+
} as LocalDiamondDeployerConfig);
|
|
93
|
+
|
|
94
|
+
await deployer.setVerbose(false); // Reduce noise, use our logger instead
|
|
95
|
+
|
|
96
|
+
Logger.step("Deploying Diamond contract...");
|
|
97
|
+
const diamond = await deployer.getDiamondDeployed();
|
|
98
|
+
|
|
99
|
+
const deployedData = diamond.getDeployedDiamondData();
|
|
100
|
+
|
|
101
|
+
Logger.success(`Diamond deployed at: ${deployedData.DiamondAddress}`);
|
|
102
|
+
Logger.info(`Deployer: ${deployedData.DeployerAddress}`);
|
|
103
|
+
Logger.info(`Facets deployed: ${Object.keys(deployedData.DeployedFacets || {}).length}`);
|
|
104
|
+
|
|
105
|
+
return diamond;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get existing deployment record
|
|
110
|
+
* @param diamondName - Name of the Diamond
|
|
111
|
+
* @param networkName - Network name
|
|
112
|
+
* @param chainId - Chain ID
|
|
113
|
+
*/
|
|
114
|
+
async getDeployment(
|
|
115
|
+
diamondName: string = "ExampleDiamond",
|
|
116
|
+
networkName: string = "hardhat",
|
|
117
|
+
chainId?: number
|
|
118
|
+
): Promise<Diamond | null> {
|
|
119
|
+
try {
|
|
120
|
+
const provider = this.hre.ethers.provider;
|
|
121
|
+
const network = await provider.getNetwork();
|
|
122
|
+
const actualChainId = chainId ?? Number(network.chainId);
|
|
123
|
+
|
|
124
|
+
if (!this.hasDeploymentRecord(diamondName, networkName, actualChainId)) {
|
|
125
|
+
Logger.warn("No deployment record found");
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const LocalDiamondDeployer = await this.getDeployerClass();
|
|
130
|
+
const deployer = await LocalDiamondDeployer.getInstance({
|
|
131
|
+
diamondName,
|
|
132
|
+
networkName,
|
|
133
|
+
provider,
|
|
134
|
+
chainId: actualChainId,
|
|
135
|
+
writeDeployedDiamondData: false,
|
|
136
|
+
} as LocalDiamondDeployerConfig);
|
|
137
|
+
|
|
138
|
+
return await deployer.getDiamond();
|
|
139
|
+
} catch (error) {
|
|
140
|
+
Logger.error(`Failed to get deployment: ${error}`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Ensure deployment exists, deploy if needed
|
|
147
|
+
* @param diamondName - Name of the Diamond
|
|
148
|
+
* @param networkName - Network name
|
|
149
|
+
* @param force - Force redeployment
|
|
150
|
+
*/
|
|
151
|
+
async ensureDeployment(
|
|
152
|
+
diamondName: string = "ExampleDiamond",
|
|
153
|
+
networkName: string = "hardhat",
|
|
154
|
+
force: boolean = false
|
|
155
|
+
): Promise<Diamond> {
|
|
156
|
+
const provider = this.hre.ethers.provider;
|
|
157
|
+
const network = await provider.getNetwork();
|
|
158
|
+
const chainId = Number(network.chainId);
|
|
159
|
+
|
|
160
|
+
// Check if deployment exists
|
|
161
|
+
const existing = await this.getDeployment(diamondName, networkName, chainId);
|
|
162
|
+
|
|
163
|
+
if (existing && !force) {
|
|
164
|
+
Logger.info("Using existing deployment");
|
|
165
|
+
return existing;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Deploy if not exists or force is true
|
|
169
|
+
return await this.deploy(diamondName, networkName, force);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if deployment record exists
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
private hasDeploymentRecord(
|
|
177
|
+
diamondName: string,
|
|
178
|
+
networkName: string,
|
|
179
|
+
chainId: number
|
|
180
|
+
): boolean {
|
|
181
|
+
const deploymentFileName = `${diamondName.toLowerCase()}-${networkName.toLowerCase()}-${chainId}.json`;
|
|
182
|
+
const deploymentPath = join(
|
|
183
|
+
this.hre.config.paths.root,
|
|
184
|
+
"diamonds",
|
|
185
|
+
diamondName,
|
|
186
|
+
"deployments",
|
|
187
|
+
deploymentFileName
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return existsSync(deploymentPath);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get deployment file path
|
|
195
|
+
*/
|
|
196
|
+
getDeploymentPath(
|
|
197
|
+
diamondName: string,
|
|
198
|
+
networkName: string,
|
|
199
|
+
chainId: number
|
|
200
|
+
): string {
|
|
201
|
+
const deploymentFileName = `${diamondName.toLowerCase()}-${networkName.toLowerCase()}-${chainId}.json`;
|
|
202
|
+
return join(
|
|
203
|
+
this.hre.config.paths.root,
|
|
204
|
+
"diamonds",
|
|
205
|
+
diamondName,
|
|
206
|
+
"deployments",
|
|
207
|
+
deploymentFileName
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|