Перейти к основному содержанию

Предварительные требования

  • Развёрнутый контракт в Plasma testnet. См. развёртывание контракта, если вы ещё не развернули его.
  • Исходный код контракта и настройки компиляции.
  • Аргументы конструктора, использованные при развёртывании (если есть).
Обозреватель блоков Plasma (plasmascan.to) предоставляет API верификации, совместимый с Etherscan. Примеры ниже используют этот API напрямую — для отправки верификации сторонняя учётная запись не требуется.

Примеры контрактов

Мы будем использовать два примера контрактов для демонстрации как простых, так и сложных аргументов конструктора:

Простой контракт

Базовый контракт хранения данных, демонстрирующий фундаментальные концепции смарт-контрактов.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract SimpleStorage {
    string private storedData;
    address public owner;
    
    constructor(string memory initialData) {
        storedData = initialData;
        owner = msg.sender;
    }
    
    function setData(string memory data) public {
        require(msg.sender == owner, "Only owner can set data");
        storedData = data;
    }
    
    function getData() public view returns (string memory) {
        return storedData;
    }
}

Сложный контракт

Более сложная система хранилища для управления токен-депозитами с настраиваемыми параметрами.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract TokenVault {
    struct VaultConfig {
        uint256 minDeposit;
        uint256 maxDeposit;
        bool isActive;
    }
    
    address[] public authorisedTokens;
    VaultConfig public config;
    mapping(address => uint256) public balances;
    
    constructor(
        address[] memory _tokens,
        uint256 _minDeposit,
        uint256 _maxDeposit,
        bool _isActive
    ) {
        authorisedTokens = _tokens;
        config = VaultConfig(_minDeposit, _maxDeposit, _isActive);
    }
    
    function deposit(address token, uint256 amount) external {
        require(config.isActive, "Vault is not active");
        require(amount >= config.minDeposit, "Amount below minimum");
        require(amount <= config.maxDeposit, "Amount above maximum");
        balances[msg.sender] += amount;
    }
}

Верификация с Foundry

Foundry предоставляет команду forge verify-contract для верификации контрактов.
  1. Убедитесь, что ваш foundry.toml включает настройки верификации:
    [profile.default]
    src = "src"
    out = "out"
    libs = ["lib"]
    solc_version = "0.8.28"
    optimizer = true
    optimizer_runs = 200
    
    [rpc_endpoints]
    plasma_testnet = "https://testnet-rpc.plasma.to"
    
  2. Верифицируйте контракт. Для контракта SimpleStorage со строковым аргументом конструктора:
    forge verify-contract \
      --chain-id 9746 \
      --num-of-optimizations 200 \
      --watch \
      --constructor-args $(cast abi-encode "constructor(string)" "Hello, Plasma!") \
      --compiler-version v0.8.28+commit.7893614a \
      0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
      src/SimpleStorage.sol:SimpleStorage
    
    Для контракта TokenVault с несколькими аргументами конструктора:
    # First, encode the constructor arguments.
    CONSTRUCTOR_ARGS=$(cast abi-encode \
        "constructor(address[],uint256,uint256,bool)" \
        "[0x1234567890123456789012345678901234567890,0x0987654321098765432109876543210987654321]" \
        1000000000000000000 \
        10000000000000000000 \
        true)
    
    forge verify-contract \
        --chain-id 9746 \
        --num-of-optimizations 200 \
        --watch \
        --constructor-args $CONSTRUCTOR_ARGS \
        --compiler-version v0.8.28+commit.7893614a \
        0x9876543210987654321098765432109876543210 \
        src/TokenVault.sol:TokenVault
    
  3. Проверьте статус верификации. Foundry покажет статус верификации в реальном времени с флагом --watch. Ищите:
    Submitting verification for [src/SimpleStorage.sol:SimpleStorage] 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F.
    Submitted contract for verification:
            Response: `OK`
            GUID: `abc123def456ghi789`
            URL: https://testnet.plasmascan.to/address/0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F
    Contract verification status:
    Response: `NOTOK`
    Details: `Pending in queue`
    Contract verification status:
    Response: `OK`
    Details: `Pass - Verified`
    Contract successfully verified
    

Верификация с Hardhat

Hardhat предлагает верификацию контрактов через плагин @nomicfoundation/hardhat-verify.
  1. Обновите hardhat.config.js, чтобы включить настройки верификации:
    require("@nomicfoundation/hardhat-toolbox");
    require("@nomicfoundation/hardhat-verify");
    require("dotenv").config();
    
    module.exports = {
      solidity: {
        version: "0.8.28",
        settings: {
          optimizer: {
            enabled: true,
            runs: 200
          }
        }
      },
      networks: {
        plasmaTestnet: {
          url: process.env.RPC_URL,
          chainId: 9746,
          accounts: [process.env.PRIVATE_KEY]
        }
      },
      etherscan: {
        customChains: [
          {
            network: "plasmaTestnet",
            chainId: 9746,
            urls: {
              apiURL: "https://testnet.plasmascan.to/api",
              browserURL: "https://testnet.plasmascan.to/"
            }
          }
        ]
      },
      sourcify: {
        enabled: false
      }
    };
    
  2. Верифицируйте контракт. Для простого контракта:
    npx hardhat verify \
        --network plasmaTestnet \
        0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
        "Hello, Plasma!"
    
    Для контрактов с несколькими аргументами конструктора создайте скрипт верификации scripts/verify.js:
    const hre = require("hardhat");
    
    async function main() {
      const contractAddress = "0x9876543210987654321098765432109876543210";
      
      // Constructor arguments for TokenVault.
      const constructorArgs = [
        [
          "0x1234567890123456789012345678901234567890",
          "0x0987654321098765432109876543210987654321"
        ], // address[] _tokens
        "1000000000000000000", // uint256 _minDeposit (1e18, assuming 18 decimals)
        "10000000000000000000", // uint256 _maxDeposit (1e19, assuming 18 decimals)
        true // bool _isActive
      ];
    
      try {
        await hre.run("verify:verify", {
          address: contractAddress,
          constructorArguments: constructorArgs,
        });
        console.log("Contract verified successfully!");
      } catch (error) {
        console.error("Verification failed:", error);
      }
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    
  3. Запустите скрипт верификации:
    npx hardhat run scripts/verify.js --network plasmaTestnet
    

Автоматическая верификация при развёртывании

Также можно верифицировать контракты автоматически при развёртывании, добавив верификацию в скрипт развёртывания:
const { ethers } = require("hardhat");

async function main() {
  const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
  const simpleStorage = await SimpleStorage.deploy("Hello, Plasma!");
  
  await simpleStorage.waitForDeployment();
  const contractAddress = await simpleStorage.getAddress();
  
  console.log("SimpleStorage deployed to:", contractAddress);
  
  // Wait for a few block confirmations before verifying.
  console.log("Waiting for block confirmations...");
  await simpleStorage.deploymentTransaction().wait(5);
  
  // Verify the contract.
  try {
    await hre.run("verify:verify", {
      address: contractAddress,
      constructorArguments: ["Hello, Plasma!"],
    });
    console.log("Contract verified successfully!");
  } catch (error) {
    console.log("Verification failed:", error.message);
  }
}

main();

Верификация с Ethers.js

Ethers.js требует ручных вызовов API обозревателя блоков для верификации. Этот подход даёт полный контроль над процессом верификации.
  1. Установите необходимые зависимости:
    npm install ethers dotenv axios form-data
    
  2. Создайте скрипт верификации verify-ethers.js:
    const axios = require('axios');
    const fs = require('fs');
    require('dotenv').config();
    
    async function verifyContract(contractAddress, sourceCode, contractName, constructorArgs = "") {
      const apiUrl = "https://testnet.plasmascan.to/api";
      
      const data = {
        module: 'contract',
        action: 'verifysourcecode',
        contractaddress: contractAddress,
        sourceCode: sourceCode,
        codeformat: 'solidity-single-file',
        contractname: contractName,
        compilerversion: 'v0.8.28+commit.7893614a',
        optimizationUsed: '1',
        runs: '200',
        constructorArguements: constructorArgs, // Note: API uses this spelling.
        evmversion: 'default',
        licenseType: '3' // MIT License
      };
    
      try {
        console.log('Submitting contract for verification...');
        const response = await axios.post(apiUrl, new URLSearchParams(data));
        
        if (response.data.status === '1') {
          const guid = response.data.result;
          console.log('Verification submitted successfully!');
          console.log('GUID:', guid);
          
          // Check verification status.
          await checkVerificationStatus(guid);
        } else {
          console.error('Verification submission failed:', response.data.result);
        }
      } catch (error) {
        console.error('Error submitting verification:', error.message);
      }
    }
    
    async function checkVerificationStatus(guid) {
      const apiUrl = "https://testnet.plasmascan.to/api";
      const maxAttempts = 30;
      let attempts = 0;
    
      while (attempts < maxAttempts) {
        try {
          const response = await axios.get(apiUrl, {
            params: {
              module: 'contract',
              action: 'checkverifystatus',
              guid: guid
            }
          });
    
          const status = response.data.status;
          const result = response.data.result;
    
          if (status === '1') {
            console.log('✅ Contract verified successfully!');
            console.log('Result:', result);
            break;
          } else if (result.includes('Fail')) {
            console.error('❌ Verification failed:', result);
            break;
          } else {
            console.log('⏳ Verification pending...');
            attempts++;
            
            if (attempts < maxAttempts) {
              await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds.
            }
          }
        } catch (error) {
          console.error('Error checking status:', error.message);
          break;
        }
      }
    
      if (attempts >= maxAttempts) {
        console.log('⚠️ Verification status check timed out. Please check manually.');
      }
    }
    
    // Verify SimpleStorage contract.
    async function verifySimpleStorage() {
      const contractAddress = "0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F";
      const sourceCode = fs.readFileSync('SimpleStorage.sol', 'utf8');
      const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20506c61736d612100000000000000000000000000000000000000";
      
      await verifyContract(contractAddress, sourceCode, "SimpleStorage", constructorArgs);
    }
    
    // Verify TokenVault contract.
    async function verifyTokenVault() {
      const contractAddress = "0x9876543210987654321098765432109876543210";
      const sourceCode = fs.readFileSync('TokenVault.sol', 'utf8');
      
      // Complex constructor arguments (ABI encoded).
      const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000123456789012345678901234567890123456789000000000000000000000000098765432109876543210987654321098765432100";
      
      await verifyContract(contractAddress, sourceCode, "TokenVault", constructorArgs);
    }
    
    // Run verification (uncomment the one you need).
    verifySimpleStorage();
    // verifyTokenVault();
    
  3. Закодируйте аргументы конструктора. Для сложных аргументов конструктора нужно ABI-закодировать их. Можно использовать инструмент cast от Foundry:
    # For SimpleStorage with string argument.
    cast abi-encode "constructor(string)" "Hello, Plasma!"
    
    # For TokenVault with complex arguments.
    cast abi-encode \
        "constructor(address[],uint256,uint256,bool)" \
        "[0x1234567890123456789012345678901234567890,0x0987654321098765432109876543210987654321]" \
        1000000000000000000 \
        10000000000000000000 \
        true
    
  4. Наконец, запустите верификацию:
    node verify-ethers.js
    

Проверка статуса верификации

После верификации можно проверить, прошла ли она успешно:

В обозревателе блоков

  1. Перейдите в обозреватель Plasma testnet.
  2. Найдите адрес вашего контракта.
  3. Поищите зелёную галочку рядом со вкладкой «Contract».
  4. Нажмите на вкладку «Contract», чтобы увидеть верифицированный исходный код.

Программно

  1. Создайте скрипт проверки статуса check-verification.js:
    const axios = require('axios');
    require('dotenv').config();
    
    async function checkIfVerified(contractAddress) {
      const apiUrl = "https://testnet.plasmascan.to/api";
      
      try {
        const response = await axios.get(apiUrl, {
          params: {
            module: 'contract',
            action: 'getsourcecode',
            address: contractAddress,
          }
        });
    
        const result = response.data.result[0];
        
        if (result.SourceCode && result.SourceCode !== '') {
          console.log('✅ Contract is verified!');
          console.log('Contract Name:', result.ContractName);
          console.log('Compiler Version:', result.CompilerVersion);
          console.log('Optimisation Used:', result.OptimizationUsed);
          return true;
        } else {
          console.log('❌ Contract is not verified');
          return false;
        }
      } catch (error) {
        console.error('Error checking verification status:', error.message);
        return false;
      }
    }
    
    // Check your contract.
    checkIfVerified("0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F");
    

Альтернативные методы

Хотя обозреватели блоков — самый распространённый метод верификации, можно также использовать:

Sourcify

Sourcify обеспечивает децентрализованную верификацию контрактов:
# Install Sourcify CLI.
npm install -g @ethereum-sourcify/cli

# Verify contract.
sourcify verify \
    --network 9746 \
    --address 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
    --files contracts/SimpleStorage.sol

Tenderly

Tenderly предлагает верификацию как часть своей платформы отладки. Подробности см. в их документации.

Сглаживание контрактов

Для контрактов с импортами или библиотеками может потребоваться объединить контракт в один файл перед верификацией. Популярные инструменты: Для сложных проектов с внешними библиотеками см. документацию по верификации Hardhat для расширенных опций конфигурации.

Устранение неполадок

Несоответствие аргументов конструктора

Error: Invalid constructor arguments provided
Дважды проверьте, что аргументы конструктора точно соответствуют использованным при развёртывании. Используйте cast abi-encode для проверки кодирования.

Несоответствие версии компилятора

Error: Compilation failed
Убедитесь, что версия компилятора в запросе на верификацию соответствует версии, использованной при компиляции.

Несоответствие настроек оптимизации

Error: Bytecode doesn't match
Проверьте, что настройки оптимизации (включено/выключено и количество runs) соответствуют настройкам компиляции.

Уже верифицирован

Error: Contract source code already verified
Это означает, что верификация уже прошла успешно ранее. Проверьте в обозревателе блоков, чтобы подтвердить.

Ограничение по частоте

Error: Rate limit exceeded
Подождите несколько минут перед повторной попыткой. Рассмотрите возможность повышения API-плана, если нужны более высокие лимиты.