Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
48b59e9
chore(plugin): initial changes for hardhat 3
afa7789 Oct 16, 2025
37b710a
chore(plugin): second change on tsconfig.json to be able to avoid tse…
afa7789 Oct 16, 2025
70993d7
chore(dev): upgrading files to use hardhat3.
afa7789 Oct 20, 2025
9d6d41e
chore: lint fix
afa7789 Oct 20, 2025
2516303
chore(plugin): update imports to use .js extensions and adjust tsconf…
afa7789 Oct 21, 2025
7fe4ec6
chore(plugin): refactor plugin structure with hooks and update type e…
afa7789 Oct 21, 2025
bc0de37
chore(plugin): refactor hooks to improve user config resolution and e…
afa7789 Oct 22, 2025
13d2465
chore(plugin): enhance validation handling and namespaced storage lay…
afa7789 Oct 22, 2025
e579a89
chore(plugin): update dependencies handling and enhance type extensio…
afa7789 Oct 22, 2025
6e4c825
chore(plugin): lint fixes.
afa7789 Oct 22, 2025
3fde741
chore(plugin): update import statements to use ES module syntax for J…
afa7789 Oct 23, 2025
0709925
chore(plugin): update Hardhat configuration and fix plugin integratio…
afa7789 Oct 28, 2025
71c16f8
chore(logs): debugging
afa7789 Oct 28, 2025
f5266f5
chore(provider): adding one more log which should help.
afa7789 Oct 28, 2025
49726a7
chore(call): enhance logging for callOptionalSignature and add implem…
afa7789 Oct 28, 2025
fd2af91
chore(test): migrate to ES module syntax and update upgrades import
afa7789 Oct 28, 2025
0a71170
feat: refactor deploy and upgrade functions to accept NetworkConnection
afa7789 Oct 28, 2025
6858807
refactor(tests): migrate from CommonJS to ES modules in Hardhat tests
afa7789 Oct 28, 2025
1a7e042
chore(tests): update imports and initialization in Hardhat tests
afa7789 Oct 29, 2025
78f3699
chore: clean up TODO comments and remove unnecessary code in deploy, …
afa7789 Oct 29, 2025
db81b33
feat: add warning for locked validations cache during compilation
afa7789 Oct 29, 2025
33f86a3
refactor(tests): replace proxyquire with esmock for module mocking
afa7789 Oct 29, 2025
0f67ffe
refactor(tests): replace network provider calls with ethers.provider …
afa7789 Oct 29, 2025
fe45e60
refactor(tests): replace hre.ethers with ethers for consistency in co…
afa7789 Oct 29, 2025
c4f2a85
refactor(tests): migrate mockDeploy function to a new file and update…
afa7789 Oct 29, 2025
acb0b57
feat(tests): enhance test script to separate Solidity and JavaScript …
afa7789 Nov 3, 2025
148f07a
feat(tests): add WithConstructor and WithConstructorArray contracts, …
afa7789 Nov 4, 2025
524bf2e
refactor(tests): improve build info file handling and update test scr…
afa7789 Nov 6, 2025
7b43183
refactor(tests): enhance logging in Upgrades test and improve argumen…
afa7789 Nov 6, 2025
af94560
feat(tests): add new contracts for testing and update hardhat configu…
afa7789 Nov 6, 2025
b8216fc
feat(tests): update Solidity contracts for testing and inject AST int…
afa7789 Nov 7, 2025
35a4b68
feat(tests): add GreeterInitializable contract and update tests for c…
afa7789 Nov 7, 2025
011a216
chore(tests): the changes regarding to solidity ended up breaking th…
afa7789 Nov 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/core/src/call-optional-signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { keccak256 } from 'ethereumjs-util';
import { call, EthereumProvider } from './provider';

export async function callOptionalSignature(provider: EthereumProvider, address: string, signature: string) {

const data = '0x' + keccak256(Buffer.from(signature)).toString('hex').slice(0, 8);

try {
return await call(provider, address, data);
const result = await call(provider, address, data);
return result;
} catch (e: any) {
if (
e.message.includes('function selector was not recognized') ||
Expand Down
140 changes: 117 additions & 23 deletions packages/core/src/cli/validate/build-info-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,52 +129,137 @@ async function exists(dir: string) {

async function getJsonFiles(dir: string): Promise<string[]> {
const files = await fs.readdir(dir);
const jsonFiles = files.filter(file => file.endsWith('.json'));
const jsonFiles = files.filter(file => file.endsWith('.json') && !file.endsWith('.output.json'));
return jsonFiles.map(file => path.join(dir, file));
}

async function readBuildInfo(buildInfoFilePaths: string[], dirShortName: string) {
const buildInfoFiles: BuildInfoFile[] = [];

for (const buildInfoFilePath of buildInfoFilePaths) {
const buildInfoJson = await readJSON(buildInfoFilePath);
if (
buildInfoJson.input === undefined ||
buildInfoJson.output === undefined ||
buildInfoJson.solcVersion === undefined
) {
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} must contain Solidity compiler input, output, and solcVersion.`,
);
} else {
checkOutputSelection(buildInfoJson, buildInfoFilePath);
const buildInfo = await loadBuildInfo(buildInfoFilePath);

checkOutputSelection(buildInfo.input, buildInfoFilePath);

buildInfoFiles.push({
input: buildInfo.input,
output: buildInfo.output,
solcVersion: buildInfo.solcVersion,
dirShortName,
});
}
return buildInfoFiles;
}

async function loadBuildInfo(buildInfoFilePath: string): Promise<{
input: SolcInput;
output: SolcOutput;
solcVersion: string;
}> {
const buildInfoJson = await readJSON(buildInfoFilePath);

if (buildInfoJson.input !== undefined && buildInfoJson.output !== undefined && buildInfoJson.solcVersion !== undefined) {
return {
input: buildInfoJson.input,
output: buildInfoJson.output,
solcVersion: buildInfoJson.solcVersion,
};
}

const format = buildInfoJson._format as string | undefined;

if (typeof format === 'string' && (format.startsWith('hh3-sol-build-info') || format.startsWith('hh-sol-build-info'))) {
if (buildInfoJson.input !== undefined && buildInfoJson.solcVersion !== undefined) {
let inputData: SolcInput = buildInfoJson.input;
let outputData: SolcOutput | undefined = buildInfoJson.output;

if (outputData === undefined) {
const { dir, name } = path.parse(buildInfoFilePath);
const outputFilePath = path.join(dir, `${name}.output.json`);

buildInfoFiles.push({
input: buildInfoJson.input,
output: buildInfoJson.output,
try {
const outputJson = await readJSON(outputFilePath);
outputData = outputJson.output ?? outputJson;
} catch (error) {
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} does not contain output, and output file ${outputFilePath} could not be read.`,
);
}

if (outputData === undefined) {
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} does not contain output, and output file ${outputFilePath} is missing Solidity compiler output.`,
);
}
}

const userSourceNameMap: Record<string, string> | undefined = buildInfoJson.userSourceNameMap;
if (userSourceNameMap !== undefined) {
const canonicalToUser = Object.entries(userSourceNameMap).reduce<Record<string, string>>((acc, [userSource, canonicalSource]) => {
acc[canonicalSource] = userSource;
return acc;
}, {});

if (inputData.sources !== undefined) {
inputData = {
...inputData,
sources: remapKeys(inputData.sources, canonicalToUser),
};
}

if (outputData.sources !== undefined) {
outputData = {
...outputData,
sources: remapKeys(outputData.sources, canonicalToUser),
contracts: outputData.contracts !== undefined ? remapKeys(outputData.contracts, canonicalToUser) : outputData.contracts,
};
} else if (outputData.contracts !== undefined) {
outputData = {
...outputData,
contracts: remapKeys(outputData.contracts, canonicalToUser),
};
}
}

return {
input: inputData,
output: outputData,
solcVersion: buildInfoJson.solcVersion,
dirShortName: dirShortName,
});
};
}
}
return buildInfoFiles;

throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} must contain Solidity compiler input, output, and solcVersion. Got format: ${format ?? 'unknown'}.`,
);
}

/**
* Gives an error if there is empty output selection for any contract, or a contract does not have storage layout.
*/
function checkOutputSelection(buildInfoJson: any, buildInfoFilePath: string) {
const o = buildInfoJson.input.settings?.outputSelection;
function checkOutputSelection(input: SolcInput, buildInfoFilePath: string) {
const outputSelection = input.settings?.outputSelection;

return Object.keys(o).forEach((item: any) => {
if ((o[item][''] === undefined || o[item][''].length === 0) && o[item]['*'].length === 0) {
if (outputSelection === undefined) {
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} does not define compiler outputSelection.`,
() => STORAGE_LAYOUT_HELP,
);
}

Object.keys(outputSelection).forEach(item => {
const selection = outputSelection[item] ?? {};
const unnamed = Array.isArray(selection['']) ? selection[''] : [];
const wildcard = Array.isArray(selection['*']) ? selection['*'] : [];

if (unnamed.length === 0 && wildcard.length === 0) {
// No outputs at all for this contract e.g. if there were no changes since the last compile in Foundry.
// This is not supported for now, since it leads to AST nodes that reference node ids in other build-info files.
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} is not from a full compilation.`,
() => PARTIAL_COMPILE_HELP,
);
} else if (!o[item]['*'].includes('storageLayout')) {
} else if (!wildcard.includes('storageLayout')) {
throw new ValidateCommandError(
`Build info file ${buildInfoFilePath} does not contain storage layout for all contracts.`,
() => STORAGE_LAYOUT_HELP,
Expand All @@ -186,3 +271,12 @@ function checkOutputSelection(buildInfoJson: any, buildInfoFilePath: string) {
async function readJSON(path: string) {
return JSON.parse(await fs.readFile(path, 'utf8'));
}

function remapKeys<T>(original: Record<string, T>, canonicalToUser: Record<string, string>): Record<string, T> {
const remapped: Record<string, T> = {};
for (const [key, value] of Object.entries(original)) {
const mappedKey = canonicalToUser[key] ?? key;
remapped[mappedKey] = value;
}
return remapped;
}
29 changes: 19 additions & 10 deletions packages/core/src/impl-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,28 @@ export class InvalidBeacon extends UpgradesError {}
* @throws {InvalidBeacon} If the implementation() function could not be called or does not return an address.
*/
export async function getImplementationAddressFromBeacon(
provider: EthereumProvider,
provider: EthereumProvider, // v2 may differ from v3
beaconAddress: string,
): Promise<string> {
const impl = await callOptionalSignature(provider, beaconAddress, 'implementation()');
let parsedImplAddress;
if (impl !== undefined) {
parsedImplAddress = parseAddress(impl);
}
try {
const impl = await callOptionalSignature(provider, beaconAddress, 'implementation()');

if (parsedImplAddress === undefined) {
throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`);
} else {
return parsedImplAddress;
let parsedImplAddress;
if (impl !== undefined) {
try {
parsedImplAddress = parseAddress(impl);
} catch (parseErr) {
throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`);
}
}

if (parsedImplAddress === undefined) {
throw new InvalidBeacon(`Contract at ${beaconAddress} doesn't look like a beacon`);
} else {
return parsedImplAddress;
}
} catch (err) {
throw err;
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-hardhat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Changelog

## 3.10.0 (2025-XX-YY)
- Update hardhat, hardhat-ethers and hardhat-verify to support to hardhat v3

## 3.9.1 (2025-06-30)

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-hardhat/ava.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
workerThreads: false,
files: ['test/*.js'],
watchMode: {
Expand Down
21 changes: 0 additions & 21 deletions packages/plugin-hardhat/contracts/Constructor.sol

This file was deleted.

19 changes: 19 additions & 0 deletions packages/plugin-hardhat/contracts/GreeterInitializable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract GreeterInitializable is Initializable, OwnableUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

string public greeting;

function initialize(address initialOwner, string memory _greeting) public initializer {
__Ownable_init(initialOwner);
greeting = _greeting;
}
}
31 changes: 31 additions & 0 deletions packages/plugin-hardhat/contracts/GreeterProxiable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract GreeterProxiable is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

string public greeting;

function initialize(address initialOwner, string memory _greeting) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
greeting = _greeting;
}

function greet() public view returns (string memory) {
return greeting;
}

function setGreeting(string memory _greeting) public {
greeting = _greeting;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// These contracts are for testing only, they are not safe for use in production.

/// @custom:oz-upgrades-from GreeterProxiable
contract GreeterStorageConflictProxiable is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

// Storage conflict: uint before string (different order than GreeterProxiable)
uint public greets;
string public greeting;

function initialize(address initialOwner, string memory _greeting) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
greeting = _greeting;
}

function greet() public returns (string memory) {
greets = greets + 1;
return greeting;
}

function setGreeting(string memory _greeting) public {
greeting = _greeting;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

38 changes: 38 additions & 0 deletions packages/plugin-hardhat/contracts/GreeterV2Proxiable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// These contracts are for testing only, they are not safe for use in production.

/// @custom:oz-upgrades-from GreeterProxiable
contract GreeterV2Proxiable is Initializable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

string public greeting;

function initialize(address initialOwner, string memory _greeting) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
greeting = _greeting;
}

function greet() public view returns (string memory) {
return greeting;
}

function setGreeting(string memory _greeting) public {
greeting = _greeting;
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function resetGreeting() public {
greeting = "Hello World";
}
}
Loading
Loading