Exemples Hardhat Development Framework

Exemples de l'environnement de développement Ethereum Hardhat incluant les contrats intelligents, les tests, le déploiement, les scripts, les plugins et les flux de travail de développement

Key Facts

Category
Blockchain
Items
4
Format Families
text

Sample Overview

Exemples de l'environnement de développement Ethereum Hardhat incluant les contrats intelligents, les tests, le déploiement, les scripts, les plugins et les flux de travail de développement This sample set belongs to Blockchain and can be used to test related workflows inside Elysia Tools.

💻 Hardhat Hello World - Configuration de Base text

🟢 simple ⭐⭐

Configuration complète de projet Hardhat avec contrats intelligents, tests, scripts de déploiement et configuration

⏱️ 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");

💻 Tests Avancés Hardhat text

🟡 intermediate ⭐⭐⭐⭐

Patterns de tests avancés incluant les fixtures, les matchers personnalisés, la couverture, l'optimisation du gaz et les tests d'intégration

⏱️ 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 et Écosystème Hardhat text

🟡 intermediate ⭐⭐⭐⭐

Explorer les plugins Hardhat incluant les outils de sécurité, l'automatisation du déploiement, la vérification, les rapports de gaz et les utilitaires de développement

⏱️ 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");

💻 Contrats Améliorables Hardhat text

🔴 complex ⭐⭐⭐⭐⭐

Construire et déployer des contrats intelligents améliorables en utilisant les proxies OpenZeppelin, les patterns d'implémentation et les stratégies d'amélioration

⏱️ 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");