Exemplos Hardhat Development Framework

Exemplos do ambiente de desenvolvimento Ethereum Hardhat incluindo contratos inteligentes, testes, deploy, scripts, plugins e workflows de desenvolvimento

💻 Hardhat Hello World - Configuração Básica javascript

🟢 simple ⭐⭐

Setup completo de projeto Hardhat com contratos inteligentes, testes, scripts de deploy e configuração

⏱️ 20 min 🏷️ hardhat, ethereum, smart contracts, development
Prerequisites: Node.js, npm, Basic understanding of Ethereum and Solidity
// Hardhat Hello World - Complete Project Setup
// Initialize: npx hardhat init

// ===== hardhat.config.js =====
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.19",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    hardhat: {
      chainId: 1337,
      // Accounts configuration for local testing
      accounts: {
        count: 10,
        accountsBalance: "10000000000000000000000", // 10000 ETH
      },
    },
    localhost: {
      url: "http://127.0.0.1:8545",
      chainId: 31337,
    },
    // Add testnet configurations
    sepolia: {
      url: process.env.SEPOLIA_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 11155111,
    },
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
  gasReporter: {
    enabled: process.env.REPORT_GAS !== undefined,
    currency: "USD",
  },
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts",
  },
  mocha: {
    timeout: 40000,
  },
};

// ===== contracts/Storage.sol =====
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract Storage {
    uint256 private number;
    string private message;
    address public owner;

    event DataStored(uint256 indexed newValue, string indexed newMessage, address indexed storedBy);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor(uint256 _initialNumber, string memory _initialMessage) {
        number = _initialNumber;
        message = _initialMessage;
        owner = msg.sender;
        emit DataStored(_initialNumber, _initialMessage, msg.sender);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Storage: caller is not the owner");
        _;
    }

    function store(uint256 _num, string memory _msg) public {
        require(_num >= 0, "Storage: number must be non-negative");
        require(bytes(_msg).length > 0, "Storage: message cannot be empty");

        number = _num;
        message = _msg;

        emit DataStored(_num, _msg, msg.sender);
    }

    function retrieve() public view returns (uint256, string memory) {
        return (number, message);
    }

    function increment() public {
        number += 1;
        emit DataStored(number, message, msg.sender);
    }

    function decrement() public {
        require(number > 0, "Storage: number cannot be negative");
        number -= 1;
        emit DataStored(number, message, msg.sender);
    }

    function transferOwnership(address _newOwner) public onlyOwner {
        require(_newOwner != address(0), "Storage: new owner is the zero address");
        address previousOwner = owner;
        owner = _newOwner;
        emit OwnershipTransferred(previousOwner, _newOwner);
    }
}

// ===== test/Storage.test.js =====
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Storage", function () {
  let storage;
  let owner;
  let addr1;
  let addr2;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();

    const Storage = await ethers.getContractFactory("Storage");
    storage = await Storage.deploy(42, "Hello, Hardhat!");
    await storage.deployed();
  });

  describe("Deployment", function () {
    it("Should set the right owner", async function () {
      expect(await storage.owner()).to.equal(owner.address);
    });

    it("Should set the initial number and message", async function () {
      const [number, message] = await storage.retrieve();
      expect(number).to.equal(42);
      expect(message).to.equal("Hello, Hardhat!");
    });

    it("Should emit DataStored event on deployment", async function () {
      // Event is tested in deployment, so we just verify initial state
      const [number, message] = await storage.retrieve();
      expect(number).to.equal(42);
      expect(message).to.equal("Hello, Hardhat!");
    });
  });

  describe("Store function", function () {
    it("Should store new number and message", async function () {
      await storage.store(100, "New message");

      const [number, message] = await storage.retrieve();
      expect(number).to.equal(100);
      expect(message).to.equal("New message");
    });

    it("Should emit DataStored event", async function () {
      await expect(storage.store(100, "New message"))
        .to.emit(storage, "DataStored")
        .withArgs(100, "New message", owner.address);
    });

    it("Should reject negative numbers", async function () {
      await expect(storage.store(-1, "Invalid"))
        .to.be.revertedWith("Storage: number must be non-negative");
    });

    it("Should reject empty messages", async function () {
      await expect(storage.store(100, ""))
        .to.be.revertedWith("Storage: message cannot be empty");
    });
  });

  describe("Increment and Decrement", function () {
    it("Should increment the number", async function () {
      await storage.increment();
      const [number] = await storage.retrieve();
      expect(number).to.equal(43);
    });

    it("Should decrement the number", async function () {
      await storage.decrement();
      const [number] = await storage.retrieve();
      expect(number).to.equal(41);
    });

    it("Should reject decrement below zero", async function () {
      // Deploy with zero initial value
      const Storage = await ethers.getContractFactory("Storage");
      const zeroStorage = await Storage.deploy(0, "Zero");
      await zeroStorage.deployed();

      await expect(zeroStorage.decrement())
        .to.be.revertedWith("Storage: number cannot be negative");
    });
  });

  describe("Ownership", function () {
    it("Should transfer ownership", async function () {
      await storage.transferOwnership(addr1.address);
      expect(await storage.owner()).to.equal(addr1.address);
    });

    it("Should emit OwnershipTransferred event", async function () {
      await expect(storage.transferOwnership(addr1.address))
        .to.emit(storage, "OwnershipTransferred")
        .withArgs(owner.address, addr1.address);
    });

    it("Should reject transfer to zero address", async function () {
      await expect(storage.transferOwnership(ethers.constants.AddressZero))
        .to.be.revertedWith("Storage: new owner is the zero address");
    });

    it("Should reject ownership transfer from non-owner", async function () {
      await expect(storage.connect(addr1).transferOwnership(addr2.address))
        .to.be.revertedWith("Storage: caller is not the owner");
    });
  });
});

// ===== scripts/deploy.js =====
const hre = require("hardhat");

async function main() {
  console.log("Starting deployment...");

  const [deployer] = await hre.ethers.getSigners();
  console.log("Deploying contracts with the account:", deployer.address);

  const balance = await deployer.getBalance();
  console.log("Account balance:", hre.ethers.utils.formatEther(balance));

  // Deploy Storage contract with initial values
  const initialNumber = 0;
  const initialMessage = "Welcome to Hardhat!";

  const Storage = await hre.ethers.getContractFactory("Storage");
  const storage = await Storage.deploy(initialNumber, initialMessage);

  await storage.deployed();

  console.log("Storage contract deployed to:", storage.address);
  console.log("Transaction hash:", storage.deployTransaction.hash);

  // Verify initial state
  const [number, message] = await storage.retrieve();
  console.log("Initial stored number:", number.toString());
  console.log("Initial stored message:", message);

  // Store some initial data
  const tx = await storage.store(100, "Contract deployed successfully!");
  await tx.wait();

  console.log("Initial data stored");

  // Save deployment info
  const deploymentInfo = {
    network: hre.network.name,
    contract: "Storage",
    address: storage.address,
    deployer: deployer.address,
    deployedAt: new Date().toISOString(),
    initialNumber: number.toString(),
    initialMessage: message,
  };

  // In a real project, save this to a file or database
  console.log("Deployment info:", JSON.stringify(deploymentInfo, null, 2));
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

// ===== scripts/interact.js =====
const hre = require("hardhat");

async function main() {
  console.log("Interacting with Storage contract...");

  // Get contract address from environment or use hardcoded for demo
  const contractAddress = process.env.CONTRACT_ADDRESS || "0x5FbDB2315678afecb367f032d93F642f64180aa3";

  const [signer] = await hre.ethers.getSigners();
  console.log("Interacting with account:", signer.address);

  // Connect to existing contract
  const Storage = await hre.ethers.getContractFactory("Storage");
  const storage = await Storage.attach(contractAddress);

  try {
    // Read current values
    const [number, message] = await storage.retrieve();
    console.log("\nCurrent stored values:");
    console.log("  Number:", number.toString());
    console.log("  Message:", message);

    // Store new values
    console.log("\nStoring new values...");
    const tx = await storage.store(42, "Hello from interaction script!");
    const receipt = await tx.wait();

    console.log("Transaction confirmed in block:", receipt.blockNumber);
    console.log("Gas used:", receipt.gasUsed.toString());

    // Read updated values
    const [newNumber, newMessage] = await storage.retrieve();
    console.log("\nUpdated stored values:");
    console.log("  Number:", newNumber.toString());
    console.log("  Message:", newMessage);

    // Test increment
    console.log("\nTesting increment...");
    await storage.increment();
    const [incNumber] = await storage.retrieve();
    console.log("Number after increment:", incNumber.toString());

    // Test decrement
    console.log("\nTesting decrement...");
    await storage.decrement();
    const [decNumber] = await storage.retrieve();
    console.log("Number after decrement:", decNumber.toString());

    console.log("\nInteraction completed successfully!");

  } catch (error) {
    console.error("Error during interaction:", error);
  }
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

// ===== package.json dependencies =====
{
  "dependencies": {
    "@nomicfoundation/hardhat-toolbox": "^3.0.0",
    "hardhat": "^2.17.0"
  },
  "devDependencies": {
    "@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
    "@nomicfoundation/hardhat-ethers": "^3.0.0",
    "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
    "@nomicfoundation/hardhat-verify": "^1.0.0",
    "@typechain/ethers-v6": "^0.4.0",
    "@typechain/hardhat": "^8.0.0",
    "chai": "^4.2.0",
    "ethers": "^6.4.0",
    "hardhat-gas-reporter": "^1.0.8",
    "solidity-coverage": "^0.8.1",
    "typechain": "^8.3.0"
  }
}

// ===== Environment variables (.env example) =====
PRIVATE_KEY=your_private_key_here
SEPOLIA_URL=https://sepolia.infura.io/v3/your_infura_project_id
ETHERSCAN_API_KEY=your_etherscan_api_key
REPORT_GAS=true

// ===== Useful Hardhat commands =====
/*
npx hardhat compile                    # Compile contracts
npx hardhat test                       # Run tests
npx hardhat test --grep "Storage"      # Run specific tests
npx hardhat node                       # Start local node
npx hardhat run scripts/deploy.js      # Run deployment script
npx hardhat run scripts/interact.js --network localhost
npx hardhat verify CONTRACT_ADDRESS    # Verify on Etherscan
npx hardhat coverage                  # Generate coverage report
npx hardhat clean                     # Clean cache and artifacts
*/

console.log("Hardhat Hello World project structure created!");
console.log("\nNext steps:");
console.log("1. Run 'npm install' to install dependencies");
console.log("2. Run 'npx hardhat compile' to compile contracts");
console.log("3. Run 'npx hardhat test' to run tests");
console.log("4. Run 'npx hardhat node' to start local development network");
console.log("5. Run 'npx hardhat run scripts/deploy.js --network localhost' to deploy");
console.log("6. Run 'npx hardhat run scripts/interact.js --network localhost' to interact");

💻 Testes Avançados Hardhat javascript

🟡 intermediate ⭐⭐⭐⭐

Patterns de teste avançados incluindo fixtures, matchers personalizados, cobertura, otimização de gás e testes de integração

⏱️ 30 min 🏷️ hardhat, testing, advanced
Prerequisites: Hardhat setup completed, Understanding of testing patterns, Solidity basics
// Hardhat Advanced Testing Patterns
// Comprehensive testing strategies for smart contracts

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-network-helpers");

// ===== Test Fixtures =====
// test/fixtures.js
async function deployContractFixture() {
  // Get signers
  const [owner, addr1, addr2, addr3] = await ethers.getSigners();

  // Deploy contract
  const ContractFactory = await ethers.getContractFactory("YourContract");
  const contract = await ContractFactory.deploy();
  await contract.deployed();

  // Fund other accounts
  await owner.sendTransaction({
    to: addr1.address,
    value: ethers.utils.parseEther("10"),
  });

  await owner.sendTransaction({
    to: addr2.address,
    value: ethers.utils.parseEther("5"),
  });

  return { contract, owner, addr1, addr2, addr3 };
}

// ===== Custom Chai Matchers =====
// test/helpers/chai.js
const chai = require("chai");
const { ethers } = require("hardhat");

chai.use(function (chai, utils) {
  // Matcher for checking if a transaction reverts with a specific message
  chai.Assertion.addMethod('revertedWithMessage', function (expectedMessage) {
    const promise = this._obj;

    return promise
      .then(() => {
        throw new Error('Expected transaction to revert');
      })
      .catch((error) => {
        if (error.message.includes('revert')) {
          const revertMessage = error.message.match(/revert(.*)/);
          const actualMessage = revertMessage ? revertMessage[1].trim() : '';

          this.assert(
            actualMessage.includes(expectedMessage),
            `Expected transaction to revert with message containing "${expectedMessage}"`,
            `Expected transaction to revert with message containing "${expectedMessage}", but got "${actualMessage}"`
          );
        } else {
          throw error;
        }
      });
  });

  // Matcher for checking gas usage
  chai.Assertion.addMethod('useGas', function (expectedGas) {
    const receipt = this._obj;
    const actualGas = receipt.gasUsed.toNumber();

    this.assert(
      actualGas <= expectedGas,
      `Expected transaction to use at most ${expectedGas} gas, but used ${actualGas}`,
      `Expected transaction to use at most ${expectedGas} gas, and used ${actualGas}`
    );
  });

  // Matcher for checking ether balance changes
  chai.Assertion.addMethod('changeEtherBalance', function (account, expectedChange) {
    const promise = this._obj;
    const provider = ethers.provider;

    return getBalance(account)
      .then((balanceBefore) => {
        return promise.then((tx) => {
          return tx.wait().then(() => {
            return getBalance(account);
          }).then((balanceAfter) => {
            const change = balanceAfter.sub(balanceBefore);

            this.assert(
              change.eq(expectedChange),
              `Expected ether balance change of ${ethers.utils.formatEther(expectedChange)} ETH`,
              `Expected ether balance change of ${ethers.utils.formatEther(expectedChange)} ETH, but got ${ethers.utils.formatEther(change)} ETH`
            );
          });
        });
      });

    async function getBalance(address) {
      return await provider.getBalance(address);
    }
  });
});

// ===== Advanced Test Example =====
// test/YourContract.advanced.test.js
describe("YourContract - Advanced Tests", function () {
  let contract, owner, addr1, addr2;

  beforeEach(async function () {
    ({ contract, owner, addr1, addr2 } = await deployContractFixture());
  });

  describe("Gas Optimization Tests", function () {
    it("Should track gas usage for different operations", async function () {
      const tx1 = await contract.someFunction();
      const receipt1 = await tx1.wait();
      console.log(`Gas used for someFunction(): ${receipt1.gasUsed}`);

      const tx2 = await contract.anotherFunction();
      const receipt2 = await tx2.wait();
      console.log(`Gas used for anotherFunction(): ${receipt2.gasUsed}`);

      // Gas assertions
      expect(receipt1).to.useGas(100000); // Adjust based on your contract
    });

    it("Should be more efficient with batch operations", async function () {
      // Individual operations
      const startGas = await ethers.provider.getBalance(owner.address);
      await contract.individualOperation1();
      await contract.individualOperation2();
      await contract.individualOperation3();
      const endGas = await ethers.provider.getBalance(owner.address);
      const individualGasCost = startGas.sub(endGas);

      // Batch operation
      const startGasBatch = await ethers.provider.getBalance(owner.address);
      await contract.batchOperation();
      const endGasBatch = await ethers.provider.getBalance(owner.address);
      const batchGasCost = startGasBatch.sub(endGasBatch);

      console.log(`Individual operations cost: ${ethers.utils.formatEther(individualGasCost)} ETH`);
      console.log(`Batch operation cost: ${ethers.utils.formatEther(batchGasCost)} ETH`);

      expect(batchGasCost.lt(individualGasCost)).to.be.true;
    });
  });

  describe("Time-Based Tests", function () {
    it("Should handle time-locked functions correctly", async function () {
      const lockTime = 7 * 24 * 60 * 60; // 7 days in seconds

      // Lock some tokens
      await contract.lockTokens(ethers.utils.parseEther("100"), lockTime);

      // Try to withdraw before lock period
      await expect(contract.withdrawLockedTokens())
        .to.be.revertedWith("Tokens are still time-locked");

      // Fast forward time
      await time.increase(lockTime);

      // Should succeed after lock period
      await expect(contract.withdrawLockedTokens())
        .to.emit(contract, "TokensWithdrawn");
    });

    it("Should calculate vesting schedules correctly", async function () {
      const totalAmount = ethers.utils.parseEther("1000");
      const vestingPeriod = 12 * 30 * 24 * 60 * 60; // 12 months

      await contract.setVestingSchedule(totalAmount, vestingPeriod);

      // Check vested amount after 6 months
      await time.increase(vestingPeriod / 2);
      const vestedAmount = await contract.getVestedAmount();

      expect(vestedAmount).to.equal(totalAmount.div(2));
    });
  });

  describe("State Transition Tests", function () {
    it("Should handle complex state transitions", async function () {
      // Initial state: Created
      expect(await contract.getState()).to.equal(0); // Assuming 0 = Created

      // Transition to Active
      await contract.activate();
      expect(await contract.getState()).to.equal(1); // Assuming 1 = Active

      // Transition to Paused
      await contract.pause();
      expect(await contract.getState()).to.equal(2); // Assuming 2 = Paused

      // Cannot perform certain actions while paused
      await expect(contract.someRestrictedAction())
        .to.be.revertedWith("Contract is paused");

      // Resume
      await contract.resume();
      expect(await contract.getState()).to.equal(1); // Back to Active
    });
  });

  describe("Edge Cases and Boundary Conditions", function () {
    it("Should handle maximum values correctly", async function () {
      const maxUint256 = ethers.constants.MaxUint256;

      // Test with maximum values
      await expect(contract.setSomeValue(maxUint256))
        .to.not.be.reverted;

      expect(await contract.getSomeValue()).to.equal(maxUint256);
    });

    it("Should handle zero address edge cases", async function () {
      await expect(contract.transfer(ethers.constants.AddressZero, 100))
        .to.be.revertedWith("Cannot transfer to zero address");
    });

    it("Should handle integer overflow/underflow", async function () {
      // Test with Solidity 0.8+ which has built-in overflow protection
      const balance = await contract.getBalance();

      if (balance.gt(0)) {
        await expect(contract.withdraw(balance.add(1)))
          .to.be.reverted; // Should revert due to underflow
      }
    });
  });

  describe("Event-Driven Tests", function () {
    it("Should emit correct events sequence", async function () {
      const tx = await contract.complexOperation();
      const receipt = await tx.wait();

      // Check for multiple events in correct order
      const events = receipt.events.filter(e => e.event === "StepCompleted");
      expect(events.length).to.equal(3); // Assuming 3 steps

      expect(events[0].args.step).to.equal(1);
      expect(events[1].args.step).to.equal(2);
      expect(events[2].args.step).to.equal(3);
    });
  });

  describe("Integration Tests", function () {
    it("Should work correctly with multiple contracts", async function () {
      // Deploy dependent contracts
      const Token = await ethers.getContractFactory("ERC20");
      const token = await Token.deploy("Test Token", "TEST");
      await token.deployed();

      const Marketplace = await ethers.getContractFactory("Marketplace");
      const marketplace = await Marketplace.deploy(token.address);
      await marketplace.deployed();

      // Setup
      await token.transfer(marketplace.address, ethers.utils.parseEther("1000"));

      // Test integration
      const listingPrice = ethers.utils.parseEther("10");
      await marketplace.createListing(listingPrice);

      const listings = await marketplace.getListings();
      expect(listings.length).to.equal(1);
      expect(listings[0].price).to.equal(listingPrice);
    });
  });

  describe("Fork Testing", function () {
    it("Should interact with mainnet contracts", async function () {
      // This test requires forking mainnet
      // Add to hardhat.config.js:
      // forking: {
      //   url: "https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY",
      //   blockNumber: 15000000, // Optional: specific block
      // }

      const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
      const weth = await ethers.getContractAt("IERC20", WETH_ADDRESS);

      const balance = await weth.balanceOf(owner.address);
      console.log(`WETH balance: ${ethers.utils.formatEther(balance)}`);

      expect(balance.gte(0)).to.be.true;
    });
  });
});

// ===== Property-Based Testing =====
// test/YourContract.property.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("YourContract - Property-Based Tests", function () {
  let contract, owner;

  beforeEach(async function () {
    [owner] = await ethers.getSigners();
    const Contract = await ethers.getContractFactory("YourContract");
    contract = await Contract.deploy();
    await contract.deployed();
  });

  it("Should maintain invariant: totalSupply = sum of all balances", async function () {
    // Transfer random amounts between accounts and check invariant
    const accounts = await ethers.getSigners();
    const initialSupply = await contract.totalSupply();

    for (let i = 0; i < 100; i++) {
      const fromIndex = Math.floor(Math.random() * 5);
      const toIndex = Math.floor(Math.random() * 5);
      const amount = ethers.BigNumber.from(Math.floor(Math.random() * 1000) + 1);

      if (fromIndex !== toIndex) {
        try {
          await contract.connect(accounts[fromIndex]).transfer(accounts[toIndex].address, amount);
        } catch (e) {
          // Ignore failed transfers (insufficient balance)
        }
      }

      // Check invariant
      const currentSupply = await contract.totalSupply();
      expect(currentSupply).to.equal(initialSupply);
    }
  });
});

// ===== Test Utilities =====
// test/utils/index.js
const { ethers } = require("hardhat");

// Helper functions for testing
const testHelpers = {
  // Get timestamp of latest block
  async getLatestBlockTimestamp() {
    const block = await ethers.provider.getBlock("latest");
    return block.timestamp;
  },

  // Calculate expected amount with fees
  calculateAmountWithFee(amount, feePercent) {
    return amount.mul(10000 - feePercent).div(10000);
  },

  // Generate random address
  randomAddress() {
    return ethers.Wallet.createRandom().address;
  },

  // Format wei to ether for display
  formatEther(amount) {
    return ethers.utils.formatEther(amount);
  },

  // Parse ether string to wei
  parseEther(amount) {
    return ethers.utils.parseEther(amount);
  },

  // Wait for specific number of blocks
  async waitBlocks(count) {
    for (let i = 0; i < count; i++) {
      await ethers.provider.send("evm_mine", []);
    }
  },

  // impersonate account (for fork testing)
  async impersonateAccount(address) {
    await ethers.provider.send("hardhat_impersonateAccount", [address]);
    return await ethers.getSigner(address);
  },

  // Set balance for account
  async setBalance(address, amount) {
    await ethers.provider.send("hardhat_setBalance", [
      address,
      amount.toHexString(),
    ]);
  },
};

module.exports = testHelpers;

// ===== Coverage Configuration =====
// hardhat.config.js (add to existing config)
module.exports = {
  // ... existing config ...

  solidity: {
    // ... existing solidity config ...
  },

  mocha: {
    timeout: 60000,
  },

  // Coverage configuration
  coverage: {
    IstanbulFolder: "coverage",
    onCleanUp: "coverage",
    reporter: ["html", "lcov", "text"],
    threshold: {
      lines: 80,
      functions: 80,
      branches: 80,
      statements: 80,
    },
    exclude: [
      "test/",
      "scripts/",
      "node_modules/",
    ],
  },
};

console.log("Advanced testing patterns configured!");
console.log("\nRun these commands:");
console.log("npx hardhat test                    # Run all tests");
console.log("npx hardhat test --coverage         # Run with coverage");
console.log("npx hardhat test --grep 'gas'       # Run gas optimization tests");
console.log("npx hardhat test --network fork     # Run fork tests");

💻 Plugins e Ecossistema Hardhat javascript

🟡 intermediate ⭐⭐⭐⭐

Explorar plugins Hardhat incluindo ferramentas de segurança, automação de deploy, verificação, relatórios de gás e utilitários de desenvolvimento

⏱️ 35 min 🏷️ hardhat, plugins, ecosystem, tools
Prerequisites: Hardhat experience, Understanding of Ethereum development tools, Node.js
// Hardhat Plugins and Ecosystem Guide
// Comprehensive plugin configuration and usage examples

// ===== Enhanced hardhat.config.js with Plugins =====
require("@nomicfoundation/hardhat-toolbox");
require("@nomicfoundation/hardhat-verify");
require("hardhat-deploy");
require("hardhat-deploy-ethers");
require("hardhat-gas-reporter");
require("solidity-coverage");
require("@openzeppelin/hardhat-upgrades");
require("hardhat-contract-sizer");
require("hardhat-spdx-license-identifier");
require("hardhat-preprocessor");
require("@tenderly/hardhat-tenderly");

const fs = require("fs");
const path = require("path");

// Task for contract verification
task("verify-all", "Verifies all deployed contracts")
  .addOptionalParam("network", "Network to verify on", "mainnet")
  .setAction(async (taskArgs, { run }) => {
    const deployments = fs.readFileSync(
      path.join(__dirname, "deployments", taskArgs.network, ".deployments Manifest.json"),
      "utf8"
    );
    const deploymentManifest = JSON.parse(deployments);

    for (const [contractName, deployment] of Object.entries(deploymentManifest.implementation)) {
      if (deployment.address && deployment.abi) {
        console.log(`Verifying ${contractName} at ${deployment.address}`);
        try {
          await run("verify:verify", {
            address: deployment.address,
            constructorArguments: deployment.constructorArgs || [],
          });
        } catch (error) {
          console.error(`Failed to verify ${contractName}:`, error.message);
        }
      }
    }
  });

// Task for gas optimization analysis
task("gas-analysis", "Analyzes gas usage across all contracts")
  .setAction(async (_, { run }) => {
    await run("compile");
    await run("test", { reporter: "gas-reporter" });
  });

// Task for security scan
task("security-scan", "Runs security analysis tools")
  .setAction(async (_, { run }) => {
    console.log("Running Slither analysis...");
    const { execSync } = require("child_process");

    try {
      execSync("slither . --filter-paths 'node_modules/' --json slither-report.json", { stdio: 'inherit' });
      console.log("Security scan completed. Check slither-report.json");
    } catch (error) {
      console.error("Security scan failed. Make sure Slither is installed.");
    }
  });

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    compilers: [
      {
        version: "0.8.19",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
          metadata: {
            bytecodeHash: "none",
          },
        },
      },
      {
        version: "0.6.12",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
      },
    ],
  },

  defaultNetwork: "hardhat",

  networks: {
    hardhat: {
      chainId: 31337,
      forking: {
        url: process.env.MAINNET_URL,
        blockNumber: 16000000, // Optional specific block
      },
    },

    localhost: {
      url: "http://127.0.0.1:8545",
      chainId: 31337,
    },

    // Testnet configurations
    sepolia: {
      url: process.env.SEPOLIA_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 11155111,
      gasPrice: 20000000000, // 20 gwei
    },

    goerli: {
      url: process.env.GOERLI_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 5,
    },

    // Mainnet
    mainnet: {
      url: process.env.MAINNET_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 1,
      gasPrice: "auto",
      gasMultiplier: 1.1,
    },
  },

  // Named accounts for deploy scripts
  namedAccounts: {
    deployer: {
      default: 0,
    },
    feeRecipient: {
      default: 1,
    },
  },

  // Gas reporter configuration
  gasReporter: {
    enabled: process.env.REPORT_GAS !== undefined,
    currency: "USD",
    gasPrice: 20,
    coinmarketcap: process.env.COINMARKETCAP_API_KEY,
    excludeContracts: ["test/", "mock/"],
  },

  // Contract sizer
  contractSizer: {
    alphaSort: true,
    disambiguatePaths: false,
    runOnCompile: true,
    strict: true,
  },

  // Etherscan verification
  etherscan: {
    apiKey: {
      mainnet: process.env.ETHERSCAN_API_KEY,
      sepolia: process.env.ETHERSCAN_API_KEY,
      goerli: process.env.ETHERSCAN_API_KEY,
    },
  },

  // Tenderly integration
  tenderly: {
    project: process.env.TENDERLY_PROJECT,
    username: process.env.TENDERLY_USERNAME,
    forkNetwork: "1", // Mainnet
  },

  // OpenZeppelin Upgrades
  openzeppelin: {
    version: "5.0.0",
  },

  // Preprocessor for imports
  preprocess: {
    eachLine: () => ({
      transform: (line) => {
        if (line.match(/^s*import ".*/./.*.sol";$/)) {
          // Handle relative imports differently
          return line;
        }
        return line;
      },
    }),
  },

  // Mocha configuration
  mocha: {
    timeout: 60000,
    reporter: "mocha-multi-reporters",
    reporterOptions: {
      configFile: "mocha-reporter-config.json",
    },
  },

  // Paths configuration
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts",
    deploy: "./deploy",
    deployments: "./deployments",
  },

  // SPDX license identifier
  spdxLicenseIdentifier: "MIT",
};

// ===== Deployment Scripts with hardhat-deploy =====
// deploy/00-deploy-mocks.js
module.exports = async ({ getNamedAccounts, deployments }) => {
  const { deploy } = deployments;
  const { deployer } = await getNamedAccounts();

  console.log("Deploying mock contracts with account:", deployer);

  // Deploy ERC20 mock
  await deploy("MockERC20", {
    from: deployer,
    args: ["Mock Token", "MOCK"],
    log: true,
    autoMine: true, // Speed up deployment on local network
  });

  // Deploy ERC721 mock
  await deploy("MockERC721", {
    from: deployer,
    args: ["Mock NFT", "MNFT"],
    log: true,
    autoMine: true,
  });
};

module.exports.tags = ["mocks", "test"];

// deploy/01-deploy-core.js
module.exports = async ({ getNamedAccounts, deployments }) => {
  const { deploy } = deployments;
  const { deployer } = await getNamedAccounts();

  console.log("Deploying core contracts with account:", deployer);

  // Get deployed mock contracts
  const mockERC20 = await deployments.get("MockERC20");

  // Deploy main contract
  await deploy("MainContract", {
    from: deployer,
    args: [mockERC20.address],
    log: true,
    waitConfirmations: 1, // Wait for 1 confirmation on testnets/mainnets
  });

  // Verify on Etherscan (skip on localhost)
  if (network.name !== "hardhat" && network.name !== "localhost") {
    try {
      await run("verify:verify", {
        address: (await deployments.get("MainContract")).address,
        constructorArguments: [mockERC20.address],
      });
    } catch (error) {
      console.error("Verification failed:", error.message);
    }
  }
};

module.exports.tags = ["core"];
module.exports.dependencies = ["mocks"];

// deploy/02-deploy-upgradeable.js
module.exports = async ({ getNamedAccounts, deployments }) => {
  const { deploy } = deployments;
  const { deployer } = await getNamedAccounts();

  console.log("Deploying upgradeable contracts with account:", deployer);

  // Deploy upgradeable contract
  await deploy("UpgradeableContract", {
    from: deployer,
    proxy: {
      proxyContract: "OpenZeppelinTransparentProxy",
      viaAdminContract: "DefaultProxyAdmin",
      execute: {
        init: {
          methodName: "initialize",
          args: ["Initial Value"],
        },
      },
    },
    log: true,
  });
};

module.exports.tags = ["upgradeable"];
module.exports.dependencies = ["core"];

// ===== Security Testing Setup =====
// test/security/slither.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Security Tests", function () {
  let contract, owner, attacker, user1, user2;

  beforeEach(async function () {
    [owner, attacker, user1, user2] = await ethers.getSigners();

    const Contract = await ethers.getContractFactory("YourContract");
    contract = await Contract.deploy();
    await contract.deployed();
  });

  // Reentrancy protection test
  it("Should be protected against reentrancy attacks", async function () {
    const MaliciousContract = await ethers.getContractFactory("ReentrancyAttacker");
    const attacker = await MaliciousContract.deploy(contract.address);
    await attacker.deployed();

    await owner.sendTransaction({
      to: contract.address,
      value: ethers.utils.parseEther("10"),
    });

    // Attempt reentrancy attack
    await expect(attacker.attack({ value: ethers.utils.parseEther("1") }))
      .to.be.revertedWith("ReentrancyGuard: reentrant call");
  });

  // Integer overflow/underflow test
  it("Should handle arithmetic safely", async function () {
    const maxUint256 = ethers.constants.MaxUint256;

    // Test overflow
    await contract.setBalance(maxUint256);
    await expect(contract.addToBalance(1))
      .to.be.reverted; // Should revert with Solidity 0.8+ overflow protection

    // Test underflow
    await contract.setBalance(0);
    await expect(contract.subtractFromBalance(1))
      .to.be.reverted; // Should revert with underflow protection
  });

  // Access control test
  it("Should enforce access controls properly", async function () {
    const restrictedFunction = async () => {
      return contract.restrictedFunction();
    };

    await expect(restrictedFunction()).to.be.revertedWith("Ownable: caller is not the owner");

    await expect(contract.connect(attacker).restrictedFunction())
      .to.be.revertedWith("Ownable: caller is not the owner");

    // Owner should be able to call
    await expect(contract.connect(owner).restrictedFunction())
      .to.not.be.reverted;
  });

  // Front-running protection test
  it("Should protect against front-running attacks", async function () {
    const value = ethers.utils.parseEther("1");

    // User submits transaction
    const userTx = contract.connect(user1).submitTransaction(value, {
      value: value,
    });

    // Attacker tries to front-run with higher gas price
    const attackerTx = contract.connect(attacker).submitTransaction(value, {
      value: value,
      gasPrice: ethers.utils.parseUnits("100", "gwei"),
    });

    // Both transactions should be processed according to gas price
    await expect(attackerTx).to.not.be.reverted;
    await expect(userTx).to.not.be.reverted;
  });
});

// ===== Gas Optimization Examples =====
// contracts/gas-optimized/Storage.sol
pragma solidity ^0.8.19;

contract GasOptimizedStorage {
    // Use packed structs to save gas
    struct UserInfo {
        uint128 balance;    // 128 bits is enough for most use cases
        uint64 lastUpdate;  // Unix timestamp fits in 64 bits
        uint32 tier;        // User tier fits in 32 bits
        bool isActive;      // 1 bit (but takes full byte)
    }

    mapping(address => UserInfo) public users;

    // Use events instead of storage for historical data
    event BalanceUpdated(address indexed user, uint256 newBalance, uint64 timestamp);

    // Use immutable for deployment-time constants
    uint256 public immutable MAX_BALANCE;
    uint256 public immutable FEE_RATE;

    constructor() {
        MAX_BALANCE = 1000000 * 10**18; // 1 million tokens
        FEE_RATE = 10; // 0.1%
    }

    // Optimized update function
    function updateBalance(address user, uint128 newBalance) external {
        UserInfo storage userInfo = users[user];

        // Use unchecked where overflow is not possible
        unchecked {
            userInfo.balance = newBalance;
            userInfo.lastUpdate = uint64(block.timestamp);
        }

        emit BalanceUpdated(user, newBalance, userInfo.lastUpdate);
    }

    // Batch operations to save gas
    function batchUpdateBalances(
        address[] calldata users_,
        uint128[] calldata balances_
    ) external {
        require(users_.length == balances_.length, "Array length mismatch");

        uint256 length = users_.length;
        for (uint256 i = 0; i < length;) {
            updateBalance(users_[i], balances_[i]);

            // Unchecked loop optimization
            unchecked { ++i; }
        }
    }
}

// ===== Development Scripts =====
// scripts/dev-setup.js
const hre = require("hardhat");
const { ethers } = hre;

async function main() {
  console.log("Setting up development environment...");

  const [deployer] = await hre.ethers.getSigners();
  console.log("Deployer account:", deployer.address);

  // Deploy all contracts
  await hre.run("deploy", {
    tags: "all",
    resetState: true,
  });

  // Fund test accounts
  const accounts = await hre.ethers.getSigners();
  for (let i = 1; i < 5; i++) {
    await deployer.sendTransaction({
      to: accounts[i].address,
      value: ethers.utils.parseEther("100"),
    });
  }

  console.log("Development environment setup complete!");
  console.log("Test accounts funded with 100 ETH each");
}

// scripts/fork-test.js
const hre = require("hardhat");

async function main() {
  console.log("Testing against forked mainnet...");

  // Get mainnet WETH contract
  const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
  const weth = await hre.ethers.getContractAt("IWETH", wethAddress);

  const [signer] = await hre.ethers.getSigners();
  const balance = await weth.balanceOf(signer.address);

  console.log(`WETH balance on fork: ${hre.ethers.utils.formatEther(balance)} WETH`);

  // Test interaction with mainnet contract
  if (balance.eq(0)) {
    const tx = await weth.deposit({
      value: hre.ethers.utils.parseEther("1"),
    });
    await tx.wait();

    console.log("Deposited 1 ETH for WETH");
  }
}

// ===== Plugin-specific Configuration Files =====
// .mocharc.json
{
  "require": "hardhat/register",
  "timeout": 60000,
  "reporter": "mocha-multi-reporters",
  "reporter-options": {
    "configFile": "mocha-reporter-config.json"
  }
}

// mocha-reporter-config.json
{
  "reporterEnabled": "spec,xunit,markdown",
  "specReporterOutput": "test-results.txt",
  "xunitReporterOutput": "test-results.xml",
  "markdownReporterOutput": "test-results.md"
}

// .solhint.json
{
  "extends": "solhint:recommended",
  "plugins": [],
  "rules": {
    "compiler-version": ["error", "^0.8.0"],
    "func-visibility": ["warn", { "ignoreConstructors": true }],
    "var-name-mixedcase": "error",
    "const-name-snakecase": "error",
    "contract-name-camelcase": "error",
    "event-name-camelcase": "error",
    "struct-name-camelcase": "error",
    "modifier-name-mixedcase": "error",
    "private-vars-leading-underscore": "error",
    "use-forbidden-name": "error"
  }
}

console.log("Hardhat plugins ecosystem configured!");
console.log("\nAvailable plugins and features:");
console.log("✅ @nomicfoundation/hardhat-toolbox - All-in-one development suite");
console.log("✅ hardhat-deploy - Declarative deployment system");
console.log("✅ hardhat-gas-reporter - Gas usage tracking");
console.log("✅ hardhat-contract-sizer - Contract size monitoring");
console.log("✅ @openzeppelin/hardhat-upgrades - Upgradeable contracts");
console.log("✅ solid-coverage - Code coverage analysis");
console.log("✅ @tenderly/hardhat-tenderly - Forking and debugging");

console.log("\nCustom tasks available:");
console.log("npx hardhat verify-all");
console.log("npx hardhat gas-analysis");
console.log("npx hardhat security-scan");

💻 Contratos Atualizáveis Hardhat javascript

🔴 complex ⭐⭐⭐⭐⭐

Construir e fazer deploy de contratos inteligentes atualizáveis usando proxies OpenZeppelin, patterns de implementação e estratégias de atualização

⏱️ 40 min 🏷️ hardhat, upgradeable, proxies, advanced
Prerequisites: Advanced Hardhat knowledge, Understanding of proxy patterns, Solidity expertise
// Hardhat Upgradeable Contracts Guide
// OpenZeppelin Proxy Contracts and Upgrade Patterns

// ===== Upgradeable Contract Examples =====
// contracts/upgradeable/BoxV1.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

contract BoxV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
    uint256 private _value;

    event ValueChanged(uint256 indexed newValue, address indexed changedBy);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        // Prevents initialization of implementation contract
        _disableInitializers();
    }

    function initialize(uint256 initialValue) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        _value = initialValue;
        emit ValueChanged(initialValue, msg.sender);
    }

    function store(uint256 newValue) public {
        require(newValue >= 0, "Box: value must be non-negative");
        _value = newValue;
        emit ValueChanged(newValue, msg.sender);
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }

    function version() public pure returns (uint256) {
        return 1;
    }

    // Required by UUPS upgradeability
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

// contracts/upgradeable/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

contract BoxV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
    uint256 private _value;
    string private _name;
    uint256 private _lastUpdated;

    event ValueChanged(uint256 indexed newValue, address indexed changedBy);
    event NameChanged(string indexed newName, address indexed changedBy);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(uint256 initialValue, string memory initialName) public initializer {
        __Ownable_init();
        __UUPSUpgradeable_init();
        _value = initialValue;
        _name = initialName;
        _lastUpdated = block.timestamp;
        emit ValueChanged(initialValue, msg.sender);
        emit NameChanged(initialName, msg.sender);
    }

    function store(uint256 newValue) public {
        require(newValue >= 0, "Box: value must be non-negative");
        _value = newValue;
        _lastUpdated = block.timestamp;
        emit ValueChanged(newValue, msg.sender);
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function setName(string memory newName) public onlyOwner {
        _name = newName;
        _lastUpdated = block.timestamp;
        emit NameChanged(newName, msg.sender);
    }

    function lastUpdated() public view returns (uint256) {
        return _lastUpdated;
    }

    function version() public pure returns (uint256) {
        return 2;
    }

    // New functionality in V2
    function increment() public {
        _value += 1;
        _lastUpdated = block.timestamp;
        emit ValueChanged(_value, msg.sender);
    }

    function decrement() public {
        require(_value > 0, "Box: value cannot be negative");
        _value -= 1;
        _lastUpdated = block.timestamp;
        emit ValueChanged(_value, msg.sender);
    }

    // Required by UUPS upgradeability
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

// ===== Deployment Scripts =====
// deploy/01-deploy-box-v1.js
module.exports = async ({ getNamedAccounts, deployments }) => {
  const { deploy, save } = deployments;
  const { deployer } = await getNamedAccounts();

  console.log("Deploying BoxV1 with account:", deployer);

  // Deploy BoxV1 as upgradeable proxy
  const boxDeployment = await deploy("BoxV1", {
    from: deployer,
    args: [42], // initialValue
    proxy: {
      proxyContract: "UUPSProxy",
      implementation: "BoxV1",
      methodName: "initialize",
    },
    log: true,
    waitConfirmations: 1,
  });

  console.log("BoxV1 deployed at:", boxDeployment.address);

  // Save deployment info
  await save("BoxProxy", {
    address: boxDeployment.address,
    abi: (await deployments.getArtifact("BoxV1")).abi,
    linkedData: {
      type: "UUPSProxy",
      implementation: "BoxV1",
    },
  });

  // Test the deployment
  const Box = await ethers.getContractFactory("BoxV1");
  const box = Box.attach(boxDeployment.address);

  const value = await box.retrieve();
  const version = await box.version();

  console.log("Initial value:", value.toString());
  console.log("Version:", version.toString());
  expect(value).to.equal(42);
  expect(version).to.equal(1);
};

module.exports.tags = ["BoxV1"];

// deploy/02-upgrade-box-v2.js
module.exports = async ({ getNamedAccounts, deployments }) => {
  const { execute, read, save } = deployments;
  const { deployer } = await getNamedAccounts();

  console.log("Upgrading Box to V2 with account:", deployer);

  // Get current proxy address
  const proxyAddress = (await deployments.get("BoxProxy")).address;
  console.log("Proxy address:", proxyAddress);

  // Upgrade to BoxV2
  const upgradeTx = await execute(
    "BoxV1", // Using BoxV1 as the proxy admin
    { from: deployer },
    "upgradeTo",
    (await deployments.getArtifact("BoxV2")).address
  );

  console.log("Upgrade transaction hash:", upgradeTx.hash);
  await upgradeTx.wait();

  // Update saved deployment info
  await save("BoxProxy", {
    address: proxyAddress,
    abi: (await deployments.getArtifact("BoxV2")).abi,
    linkedData: {
      type: "UUPSProxy",
      implementation: "BoxV2",
    },
  });

  // Test the upgrade
  const BoxV2 = await ethers.getContractFactory("BoxV2");
  const box = BoxV2.attach(proxyAddress);

  const value = await box.retrieve();
  const version = await box.version();

  console.log("Value after upgrade:", value.toString());
  console.log("Version after upgrade:", version.toString());
  expect(value).to.equal(42); // Value should be preserved
  expect(version).to.equal(2); // Version should be updated

  // Test new functionality
  await box.increment();
  const newValue = await box.retrieve();
  console.log("Value after increment:", newValue.toString());
  expect(newValue).to.equal(43);

  // Test setName function
  await box.setName("My Upgraded Box");
  const name = await box.name();
  console.log("Box name:", name);
  expect(name).to.equal("My Upgraded Box");
};

module.exports.tags = ["BoxV2"];
module.exports.dependencies = ["BoxV1"];

// ===== Testing Upgradeable Contracts =====
// test/upgradeable/Box.test.js
const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");

describe("Box Upgradeable Contract", function () {
  let box;
  let boxV2Factory;
  let owner, addr1, addr2;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();

    // Deploy initial version
    const Box = await ethers.getContractFactory("BoxV1");
    box = await upgrades.deployProxy(Box, [42], { initializer: "initialize" });
    await box.deployed();

    // Get V2 factory for upgrade tests
    boxV2Factory = await ethers.getContractFactory("BoxV2");
  });

  describe("Initial Deployment (V1)", function () {
    it("Should set the right initial value", async function () {
      expect(await box.retrieve()).to.equal(42);
    });

    it("Should return version 1", async function () {
      expect(await box.version()).to.equal(1);
    });

    it("Should allow storing new values", async function () {
      await box.store(100);
      expect(await box.retrieve()).to.equal(100);
    });

    it("Should reject negative values", async function () {
      await expect(box.store(-1)).to.be.revertedWith("Box: value must be non-negative");
    });
  });

  describe("Proxy Behavior", function () {
    it("Should have the correct owner", async function () {
      expect(await box.owner()).to.equal(owner.address);
    });

    it("Should emit events on value changes", async function () {
      await expect(box.store(100))
        .to.emit(box, "ValueChanged")
        .withArgs(100, owner.address);
    });

    it("Should prevent initialization twice", async function () {
      await expect(box.initialize(50))
        .to.be.revertedWith("Initializable: contract is already initialized");
    });
  });

  describe("Upgrade to V2", function () {
    let upgradedBox;

    beforeEach(async function () {
      // Upgrade to V2
      upgradedBox = await upgrades.upgradeProxy(box, boxV2Factory);
      // Note: upgradedBox and box point to the same proxy
    });

    it("Should preserve existing state", async function () {
      expect(await upgradedBox.retrieve()).to.equal(42);
      expect(await upgradedBox.owner()).to.equal(owner.address);
    });

    it("Should return new version", async function () {
      expect(await upgradedBox.version()).to.equal(2);
    });

    it("Should have new functionality", async function () {
      await upgradedBox.increment();
      expect(await upgradedBox.retrieve()).to.equal(43);

      await upgradedBox.decrement();
      expect(await upgradedBox.retrieve()).to.equal(42);
    });

    it("Should prevent invalid decrement", async function () {
      await upgradedBox.store(0);
      await expect(upgradedBox.decrement())
        .to.be.revertedWith("Box: value cannot be negative");
    });

    it("Should support name functionality", async function () {
      await upgradedBox.setName("Test Box");
      expect(await upgradedBox.name()).to.equal("Test Box");
    });

    it("Should emit name change events", async function () {
      await expect(upgradedBox.setName("New Name"))
        .to.emit(upgradedBox, "NameChanged")
        .withArgs("New Name", owner.address);
    });

    it("Should restrict name changes to owner", async function () {
      await expect(upgradedBox.connect(addr1).setName("Hacker Name"))
        .to.be.revertedWith("Ownable: caller is not the owner");
    });
  });

  describe("Storage Layout Compatibility", function () {
    it("Should maintain storage layout compatibility", async function () {
      // Store some data before upgrade
      await box.store(123);

      // Upgrade
      const upgradedBox = await upgrades.upgradeProxy(box, boxV2Factory);

      // Data should still be accessible
      expect(await upgradedBox.retrieve()).to.equal(123);

      // New storage should work
      await upgradedBox.setName("Storage Test");
      expect(await upgradedBox.name()).to.equal("Storage Test");
    });
  });

  describe("Access Control Through Upgrade", function () {
    it("Should maintain access control after upgrade", async function () {
      // Transfer ownership before upgrade
      await box.transferOwnership(addr1.address);
      expect(await box.owner()).to.equal(addr1.address);

      // Upgrade
      const upgradedBox = await upgrades.upgradeProxy(box, boxV2Factory);

      // Ownership should be preserved
      expect(await upgradedBox.owner()).to.equal(addr1.address);

      // New owner should be able to call new functions
      await expect(upgradedBox.connect(addr1).setName("Owner Can Change"))
        .to.not.be.reverted;

      // Old owner should not be able to call protected functions
      await expect(upgradedBox.setName("Old Owner Cannot Change"))
        .to.be.revertedWith("Ownable: caller is not the owner");
    });
  });
});

// ===== Advanced Upgrade Patterns =====
// contracts/upgradeable/StorageManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";

contract StorageManager is Initializable, UUPSUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    mapping(bytes32 => bytes32) private _storage;
    mapping(bytes32 => bool) private _permissions;
    mapping(address => uint256) private _userNonces;

    event StorageSet(bytes32 indexed key, bytes32 indexed value, address indexed setBy);
    event StorageDeleted(bytes32 indexed key, address indexed deletedBy);
    event PermissionSet(bytes32 indexed key, bool allowed, address indexed setBy);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address admin) public initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();
        __ReentrancyGuard_init();

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
        _grantRole(MANAGER_ROLE, admin);
    }

    function setStorage(bytes32 key, bytes32 value) external nonReverter onlyRole(MANAGER_ROLE) {
        require(_permissions[key] || msg.sender == getRoleMember(DEFAULT_ADMIN_ROLE, 0),
                "StorageManager: no permission for key");

        _storage[key] = value;
        emit StorageSet(key, value, msg.sender);
    }

    function getStorage(bytes32 key) external view returns (bytes32) {
        return _storage[key];
    }

    function deleteStorage(bytes32 key) external onlyRole(MANAGER_ROLE) {
        delete _storage[key];
        emit StorageDeleted(key, msg.sender);
    }

    function setPermission(bytes32 key, bool allowed) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _permissions[key] = allowed;
        emit PermissionSet(key, allowed, msg.sender);
    }

    function hasPermission(bytes32 key) external view returns (bool) {
        return _permissions[key];
    }

    // Batch operations for gas efficiency
    function batchSetStorage(bytes32[] calldata keys, bytes32[] calldata values)
        external nonReverter onlyRole(MANAGER_ROLE) {
        require(keys.length == values.length, "StorageManager: array length mismatch");

        for (uint256 i = 0; i < keys.length; i++) {
            require(_permissions[keys[i]] || msg.sender == getRoleMember(DEFAULT_ADMIN_ROLE, 0),
                    "StorageManager: no permission for key");

            _storage[keys[i]] = values[i];
            emit StorageSet(keys[i], values[i], msg.sender);
        }
    }

    function getUserNonce(address user) external view returns (uint256) {
        return _userNonces[user];
    }

    function version() public pure returns (uint256) {
        return 1;
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {}
}

// ===== Upgrade Validation Script =====
// scripts/validate-upgrade.js
const { ethers, upgrades } = require("hardhat");
const fs = require("fs");
const path = require("path");

async function validateUpgrade(proxyAddress, newImplementationFactory) {
  console.log("Validating upgrade safety...");

  try {
    // Check if upgrade is safe
    const BoxV1 = await ethers.getContractFactory("BoxV1");
    const box = BoxV1.attach(proxyAddress);

    // Validate upgrade
    const upgradeValidation = await upgrades.validateUpgrade(proxyAddress, newImplementationFactory);
    console.log("✅ Upgrade validation passed");

    // Check storage layout
    const storageLayout = await newImplementationFactory.getStorageLayout();
    console.log("Storage layout:", JSON.stringify(storageLayout, null, 2));

    // Simulate upgrade
    const upgradedBox = await upgrades.upgradeProxy(box, newImplementationFactory, {
      unsafeAllowStorage: false, // Set to true only if you're sure about storage compatibility
    });

    console.log("✅ Upgrade simulation successful");
    console.log("New implementation address:", upgradedBox.address);

    return true;
  } catch (error) {
    console.error("❌ Upgrade validation failed:", error.message);
    return false;
  }
}

// Automated upgrade testing
async function testUpgrade() {
  const proxyAddress = "YOUR_PROXY_ADDRESS_HERE"; // Get from deployments

  // Test V1 to V2 upgrade
  const BoxV2 = await ethers.getContractFactory("BoxV2");
  const isValid = await validateUpgrade(proxyAddress, BoxV2);

  if (isValid) {
    console.log("✅ Upgrade from V1 to V2 is safe");
  } else {
    console.log("❌ Upgrade from V1 to V2 is NOT safe");
  }
}

// ===== Production Upgrade Guide =====
const productionUpgradeChecklist = `
🔍 PRODUCTION UPGRADE CHECKLIST

1. PRE-UPGRADE PREPARATIONS:
   [ ] Comprehensive testing on testnets
   [ ] Security audit completed
   [ ] Storage layout compatibility verified
   [ ] Upgrade simulation successful
   [ ] Rollback plan prepared
   [ ] Community/teams notified

2. UPGRADE EXECUTION:
   [ ] Backup current state
   [ ] Deploy new implementation
   [ ] Validate upgrade function
   [ ] Execute upgrade with proper timelock
   [ ] Monitor upgrade transaction

3. POST-UPGRADE VERIFICATION:
   [ ] All functionality working
   [ ] Data integrity preserved
   [ ] Events emitted correctly
   [ ] Gas consumption as expected
   [ ] No unexpected side effects

4. MONITORING:
   [ ] Set up alerts for anomalies
   [ ] Monitor contract interactions
   [ ] Track gas usage patterns
   [ ] Watch for reverts or failures
   [ ] Log all upgrade activities

⚠️  UPGRADE SECURITY CONSIDERATIONS:
- Use multisig or DAO for upgrade authorization
- Implement timelock for upgrade delays
- Consider using upgrade guardians
- Document all upgrade reasons
- Maintain transparent communication
`;

console.log("Hardhat upgradeable contracts guide configured!");
console.log("\nKey concepts:");
console.log("✅ Proxy patterns (UUPS, Transparent, Beacon)");
console.log("✅ Storage layout compatibility");
console.log("✅ Initialization patterns");
console.log("✅ Access control through upgrades");
console.log("✅ Upgrade validation and testing");
console.log("✅ Production upgrade procedures");

console.log("\nCommands:");
console.log("npx hardhat deploy --tags BoxV1");
console.log("npx hardhat deploy --tags BoxV2");
console.log("npx hardhat test test/upgradeable/Box.test.js");