Skip to content

Commit

Permalink
Support automatic verification for @openzeppelin/hardhat-upgrades (#176)
Browse files Browse the repository at this point in the history
This PR implements the support for manual and automatic verification of proxies, their implementations and all related contracts deployed with `@openzeppelin/hardhat-upgrades` plugin.
This PR also updates the `examples/contract-verification` folder with example usages of this feature.
  • Loading branch information
dule-git authored Feb 5, 2024
1 parent 81636c0 commit b96d3a8
Show file tree
Hide file tree
Showing 28 changed files with 8,337 additions and 1,356 deletions.
6 changes: 6 additions & 0 deletions .changeset/blue-melons-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tenderly/hardhat-tenderly": minor
"tenderly": minor
---

Implement manual and automatic verification of proxies deployed with `@openzeppelin/hardhat-upgrades`.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,4 @@ typings/
.idea
.vscode
**/.DS_Store
examples/contract-verification/.openzeppelin/
examples/contract-verification/.openzeppelin
78 changes: 78 additions & 0 deletions examples/contract-verification/contracts/VotingLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

contract VotingLogic is Initializable {
string private name;

// Struct to hold poll data
struct Poll {
string[] options; // Array of options for the poll
mapping(string => uint) votes; // Mapping of option to vote count
bool exists; // Flag to check if the poll exists
}

// Mapping from poll ID to Poll struct
mapping(bytes32 => Poll) public polls;

// Event emitted when a new poll is created
event PollCreated(bytes32 pollId);

// Event emitted when a vote is cast
event VoteCast(bytes32 pollId, string option);

function initialize() public initializer {
name = "VotingLogic-V9";
}

/**
* @dev Create a new poll with given options.
* @param pollId Unique identifier for the poll
* @param options Array of options for the poll
*/
function createPoll(bytes32 pollId, string[] memory options) public {
require(options.length > 1, "There must be at least two options.");
require(!polls[pollId].exists, "Poll already exists.");

Poll storage newPoll = polls[pollId];
for (uint i = 0; i < options.length; i++) {
newPoll.options.push(options[i]);
newPoll.votes[options[i]] = 0;
}
newPoll.exists = true;

emit PollCreated(pollId);
}

/**
* @dev Cast a vote in a specific poll.
* @param pollId Unique identifier for the poll
* @param option The option to vote for
*/
function vote(bytes32 pollId, string memory option) public {
require(polls[pollId].exists, "Poll does not exist.");
require(polls[pollId].votes[option] >= 0, "Invalid option.");

polls[pollId].votes[option]++;

emit VoteCast(pollId, option);
}

/**
* @dev Get the results of a poll.
* @param pollId Unique identifier for the poll
* @return An array of options and their respective vote counts
*/
function getResults(bytes32 pollId) public view returns (string[] memory, uint[] memory) {
require(polls[pollId].exists, "Poll does not exist.");

Poll storage poll = polls[pollId];
uint[] memory voteCounts = new uint[](poll.options.length);

for (uint i = 0; i < poll.options.length; i++) {
voteCounts[i] = poll.votes[poll.options[i]];
}

return (poll.options, voteCounts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "hardhat-deploy/solc_0.8/openzeppelin/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract VotingLogicUpgradeable is Initializable, UUPSUpgradeable, OwnableUpgradeable {
string private name;

// Struct to hold poll data
struct Poll {
string[] options; // Array of options for the poll
mapping(string => uint) votes; // Mapping of option to vote count
bool exists; // Flag to check if the poll exists
}

// Mapping from poll ID to Poll struct
mapping(bytes32 => Poll) public polls;

// Event emitted when a new poll is created
event PollCreated(bytes32 pollId);

// Event emitted when a vote is cast
event VoteCast(bytes32 pollId, string option);

function initialize() public initializer {
name = "VotingLogic-V10";
}

/**
* @dev Create a new poll with given options.
* @param pollId Unique identifier for the poll
* @param options Array of options for the poll
*/
function createPoll(bytes32 pollId, string[] memory options) public {
require(options.length > 1, "There must be at least two options.");
require(!polls[pollId].exists, "Poll already exists.");

Poll storage newPoll = polls[pollId];
for (uint i = 0; i < options.length; i++) {
newPoll.options.push(options[i]);
newPoll.votes[options[i]] = 0;
}
newPoll.exists = true;

emit PollCreated(pollId);
}

/**
* @dev Cast a vote in a specific poll.
* @param pollId Unique identifier for the poll
* @param option The option to vote for
*/
function vote(bytes32 pollId, string memory option) public {
require(polls[pollId].exists, "Poll does not exist.");
require(polls[pollId].votes[option] >= 0, "Invalid option.");

polls[pollId].votes[option]++;

emit VoteCast(pollId, option);
}

/**
* @dev Get the results of a poll.
* @param pollId Unique identifier for the poll
* @return An array of options and their respective vote counts
*/
function getResults(bytes32 pollId) public view returns (string[] memory, uint[] memory) {
require(polls[pollId].exists, "Poll does not exist.");

Poll storage poll = polls[pollId];
uint[] memory voteCounts = new uint[](poll.options.length);

for (uint i = 0; i < poll.options.length; i++) {
voteCounts[i] = poll.votes[poll.options[i]];
}

return (poll.options, voteCounts);
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }
}
31 changes: 14 additions & 17 deletions examples/contract-verification/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
import * as tdly from "@tenderly/hardhat-tenderly";
import "@nomicfoundation/hardhat-ethers";
import "@openzeppelin/hardhat-upgrades";
import "@nomicfoundation/hardhat-toolbox";

import { HardhatUserConfig } from "hardhat/config";
import * as dotenv from "dotenv";
import { HardhatUserConfig } from "hardhat/types/config";

const { TENDERLY_PRIVATE_VERIFICATION, TENDERLY_AUTOMATIC_VERIFICATION } =
process.env;

const privateVerification = TENDERLY_PRIVATE_VERIFICATION === "true";
const automaticVerifications = TENDERLY_AUTOMATIC_VERIFICATION === "true";

console.log(
"Using private verification? ",
privateVerification,
TENDERLY_PRIVATE_VERIFICATION,
);
console.log(
"Using automatic verification? ",
automaticVerifications,
TENDERLY_AUTOMATIC_VERIFICATION,
);

tdly.setup({ automaticVerifications });

dotenv.config();

console.log("Using private verification?", privateVerification);
console.log("Using automatic verification?", automaticVerifications);
console.log(
"Using automatic population of hardhat-verify `etherscan` configuration? ",
process.env.AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG === "true",
);

const config: HardhatUserConfig = {
solidity: "0.8.17",
solidity: "0.8.23",
networks: {
my_tenderly_fork_1: {
// or any other name
// or any other custom network name
url: `${process.env.TENDERLY_FORK_RPC_URL ?? ""}`,
},
my_tenderly_devnet_1: {
// or any other name
// or any other custom network name
url: `${process.env.TENDERLY_DEVNET_RPC_URL_1 ?? ""}`,
},
my_tenderly_devnet_2: {
// or any other name
// or any other custom network name
url: `${process.env.TENDERLY_DEVNET_RPC_URL_2 ?? ""}`,
},
sepolia: {
Expand Down
32 changes: 28 additions & 4 deletions examples/contract-verification/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
"public:calculator:automatic": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/calculator/automatic.ts",
"public:calculator:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-simple.ts",
"public:calculator:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-advanced.ts",
"public:proxy:automatic": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/proxy/automatic.ts",
"public:proxy:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/proxy/manual-simple.ts",
"private:greeter:automatic": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/greeter/automatic.ts",
"private:greeter:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/greeter/manual-simple.ts",
"private:greeter:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/greeter/manual-advanced.ts",
"private:calculator:automatic": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/calculator/automatic.ts",
"private:calculator:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-simple.ts",
"private:calculator:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-advanced.ts",
"private:proxy:automatic": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/proxy/automatic.ts",
"private:proxy:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=true TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/proxy/manual-simple.ts",
"fork:greeter:automatic": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/greeter/automatic.ts",
"fork:greeter:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/greeter/manual-simple.ts",
"fork:greeter:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/greeter/manual-advanced-fork.ts",
"fork:calculator:automatic": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/calculator/automatic.ts",
"fork:calculator:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-simple.ts",
"fork:calculator:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-advanced-fork.ts"
"fork:calculator:manual-advanced": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/calculator/manual-advanced-fork.ts",
"fork:proxy:manual-simple": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/proxy/manual-simple.ts",
"fork:proxy:automatic": "TENDERLY_PRIVATE_VERIFICATION=false TENDERLY_AUTOMATIC_VERIFICATION=true npx hardhat run scripts/proxy/automatic.ts"
},
"devDependencies": {
"@types/node": "^20.5.7",
Expand All @@ -41,10 +47,28 @@
"typescript": "^4.8.3"
},
"dependencies": {
"@tenderly/hardhat-tenderly": "^2.1.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"ethers": "^6.8.1",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@openzeppelin/contracts": "^5.0.1",
"@openzeppelin/contracts-upgradeable": "^5.0.1",
"@openzeppelin/hardhat-upgrades": "^3.0.1",
"@openzeppelin/upgrades-core": "^1.32.2",
"@tenderly/hardhat-tenderly": "^2.1.1",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"chai": "^4.2.0",
"dotenv": "^16.0.1",
"hardhat": "^2.19.0"
"ethers": "^6.8.1",
"global": "^4.4.0",
"global-tunnel-ng": "^2.7.1",
"hardhat": "^2.19.0",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.1",
"typechain": "^8.3.0"
}
}
24 changes: 24 additions & 0 deletions examples/contract-verification/scripts/proxy/automatic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
deployTransparentUpgradeableProxy,
deployUUPSProxy,
deployBeaconProxy,
} from "./deployment";

export async function main() {
// TransparentUpgradeableProxy
// The plugin will automatically verify the proxy, implementation and all the related contracts on deployment.
await deployTransparentUpgradeableProxy();

// UUPSProxy
// The plugin will automatically verify the proxy, implementation and all the related contracts on deployment.
await deployUUPSProxy();

// Beacon Proxy
// The plugin will automatically verify the proxy, implementation and all the related contracts on deployment.
await deployBeaconProxy();
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading

0 comments on commit b96d3a8

Please sign in to comment.