> ## Documentation Index
> Fetch the complete documentation index at: https://docs.plasma.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 컨트랙트 검증하기

> 이 가이드는 Foundry, Hardhat 및 Ethers.js를 사용하여 Plasma에서 스마트 컨트랙트를 검증하는 방법을 보여줍니다. 검증을 통해 컨트랙트 소스 코드가 블록 익스플로러에서 공개적으로 접근 가능해지고 사용자에 대한 투명성이 보장됩니다.

## 사전 요구 사항

* Plasma 테스트넷에 배포된 컨트랙트. 아직 배포하지 않았다면 [컨트랙트 배포하기](./deploy-a-contract.mdx)를 참조하세요.
* 컨트랙트의 소스 코드와 컴파일 설정.
* 배포 시 사용된 생성자 인수(있는 경우).

Plasma 블록 익스플로러(`plasmascan.to`)는 Etherscan 호환 검증 API를 제공합니다. 아래 예시는 해당 API를 직접 사용하며, 검증 제출을 위해 제3자 계정이 필요하지 않습니다.

## 예시 컨트랙트

단순한 생성자 인수와 복잡한 생성자 인수 모두를 보여주기 위해 두 가지 예시 컨트랙트를 사용하겠습니다:

### 단순 컨트랙트

스마트 컨트랙트의 기본 개념을 보여주는 기본 데이터 저장 컨트랙트입니다.

```solidity theme={null}
// 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;
    }
}
```

### 복잡한 컨트랙트

구성 가능한 매개변수로 토큰 예금을 관리하는 보다 정교한 vault 시스템입니다.

```solidity theme={null}
// 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](https://getfoundry.sh)는 컨트랙트 검증을 위해 `forge verify-contract` 명령을 제공합니다.

1. `foundry.toml`에 검증 설정이 포함되어 있는지 확인합니다:

   ```toml theme={null}
   [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 컨트랙트의 경우:

   ```shell theme={null}
   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 컨트랙트의 경우:

   ```shell theme={null}
   # 먼저 생성자 인수를 인코딩합니다.
   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` 플래그를 사용하여 실시간 검증 상태를 표시합니다. 다음을 확인하세요:

   ```plaintext theme={null}
   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](https://hardhat.org/)은 `@nomicfoundation/hardhat-verify` 플러그인을 통해 컨트랙트 검증을 제공합니다.

1. 검증 설정을 포함하도록 `hardhat.config.js`를 업데이트합니다:

   ```javascript theme={null}
   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. 컨트랙트를 검증합니다. 단순 컨트랙트의 경우:

   ```shell theme={null}
   npx hardhat verify \
       --network plasmaTestnet \
       0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
       "Hello, Plasma!"
   ```

   여러 생성자 인수가 있는 컨트랙트의 경우, 검증 스크립트 `scripts/verify.js`를 생성합니다:

   ```javascript theme={null}
   const hre = require("hardhat");

   async function main() {
     const contractAddress = "0x9876543210987654321098765432109876543210";
     
     // TokenVault용 생성자 인수.
     const constructorArgs = [
       [
         "0x1234567890123456789012345678901234567890",
         "0x0987654321098765432109876543210987654321"
       ], // address[] _tokens
       "1000000000000000000", // uint256 _minDeposit (1e18, 18 decimals 가정)
       "10000000000000000000", // uint256 _maxDeposit (1e19, 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. 검증 스크립트를 실행합니다:

   ```shell theme={null}
   npx hardhat run scripts/verify.js --network plasmaTestnet
   ```

### 배포 시 자동 검증

배포 스크립트에 검증을 추가하여 배포 중에 컨트랙트를 자동으로 검증할 수도 있습니다:

```javascript theme={null}
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);
  
  // 검증 전에 몇 개의 블록 확인을 기다립니다.
  console.log("Waiting for block confirmations...");
  await simpleStorage.deploymentTransaction().wait(5);
  
  // 컨트랙트를 검증합니다.
  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](https://ethers.org/)는 검증을 위해 블록 익스플로러에 수동으로 API를 호출해야 합니다. 이 방식은 검증 프로세스에 대한 완전한 제어를 제공합니다.

1. 필요한 종속성을 설치합니다:

   ```shell theme={null}
   npm install ethers dotenv axios form-data
   ```

2. 검증 스크립트 `verify-ethers.js`를 생성합니다:

   ```javascript theme={null}
   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, // 참고: API는 이 철자를 사용합니다.
       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);
         
         // 검증 상태를 확인합니다.
         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)); // 10초 대기.
           }
         }
       } catch (error) {
         console.error('Error checking status:', error.message);
         break;
       }
     }

     if (attempts >= maxAttempts) {
       console.log('⚠️ Verification status check timed out. Please check manually.');
     }
   }

   // SimpleStorage 컨트랙트를 검증합니다.
   async function verifySimpleStorage() {
     const contractAddress = "0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F";
     const sourceCode = fs.readFileSync('SimpleStorage.sol', 'utf8');
     const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20506c61736d612100000000000000000000000000000000000000";
     
     await verifyContract(contractAddress, sourceCode, "SimpleStorage", constructorArgs);
   }

   // TokenVault 컨트랙트를 검증합니다.
   async function verifyTokenVault() {
     const contractAddress = "0x9876543210987654321098765432109876543210";
     const sourceCode = fs.readFileSync('TokenVault.sol', 'utf8');
     
     // 복잡한 생성자 인수 (ABI 인코딩).
     const constructorArgs = "0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000123456789012345678901234567890123456789000000000000000000000000098765432109876543210987654321098765432100";
     
     await verifyContract(contractAddress, sourceCode, "TokenVault", constructorArgs);
   }

   // 검증 실행 (필요한 것의 주석을 해제하세요).
   verifySimpleStorage();
   // verifyTokenVault();
   ```

3. 생성자 인수를 인코딩합니다. 복잡한 생성자 인수의 경우 ABI 인코딩이 필요합니다. Foundry의 `cast` 도구를 사용할 수 있습니다:

   ```shell theme={null}
   # 문자열 인수가 있는 SimpleStorage용.
   cast abi-encode "constructor(string)" "Hello, Plasma!"

   # 복잡한 인수가 있는 TokenVault용.
   cast abi-encode \
       "constructor(address[],uint256,uint256,bool)" \
       "[0x1234567890123456789012345678901234567890,0x0987654321098765432109876543210987654321]" \
       1000000000000000000 \
       10000000000000000000 \
       true
   ```

4. 마지막으로 검증을 실행합니다:

   ```shell theme={null}
   node verify-ethers.js
   ```

## 검증 상태 확인

검증 후 성공 여부를 확인할 수 있습니다:

### 블록 익스플로러에서

1. [Plasma 테스트넷 익스플로러](https://testnet.plasmascan.to/)를 방문합니다.
2. 컨트랙트 주소를 검색합니다.
3. “Contract” 탭 옆의 녹색 체크 표시를 확인합니다.
4. “Contract” 탭을 클릭하여 검증된 소스 코드를 봅니다.

### 프로그래밍 방식

1. 상태 확인 스크립트 `check-verification.js`를 생성합니다:

   ```javascript theme={null}
   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;
     }
   }

   // 컨트랙트를 확인합니다.
   checkIfVerified("0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F");
   ```

## 대체 방법

블록 익스플로러가 가장 일반적인 검증 방법이지만 다음을 사용할 수도 있습니다:

### Sourcify

[Sourcify](https://sourcify.dev/)는 분산형 컨트랙트 검증을 제공합니다:

```shell theme={null}
# Sourcify CLI 설치.
npm install -g @ethereum-sourcify/cli

# 컨트랙트 검증.
sourcify verify \
    --network 9746 \
    --address 0x742d35Cc6610C7532C8582d4C371Acb1D5f44D7F \
    --files contracts/SimpleStorage.sol
```

### Tenderly

[Tenderly](https://tenderly.co/)는 디버깅 플랫폼의 일부로 검증을 제공합니다. 자세한 내용은 [문서](https://docs.tenderly.co/contract-verification)를 참조하세요.

## 컨트랙트 평탄화

임포트 또는 라이브러리가 있는 컨트랙트의 경우 검증 전에 컨트랙트를 단일 파일로 평탄화해야 할 수 있습니다. 인기 있는 도구는 다음과 같습니다:

* [Foundry의 forge flatten](https://book.getfoundry.sh/reference/forge/forge-flatten)
* [Hardhat의 flatten 플러그인](https://github.com/NomicFoundation/hardhat/tree/main/packages/hardhat-core#flattening-contracts)
* [Truffle Flattener](https://github.com/nomiclabs/truffle-flattener)

외부 라이브러리가 있는 복잡한 프로젝트의 경우 고급 구성 옵션은 [Hardhat 검증 문서](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify)를 참조하세요.

## 문제 해결

### 생성자 인수 불일치

```plaintext theme={null}
Error: Invalid constructor arguments provided
```

생성자 인수가 배포 시 사용된 것과 정확히 일치하는지 다시 확인하세요. `cast abi-encode`를 사용하여 인코딩을 검증할 수 있습니다.

### 컴파일러 버전 불일치

```plaintext theme={null}
Error: Compilation failed
```

검증 요청의 컴파일러 버전이 컴파일 시 사용된 버전과 일치하는지 확인하세요.

### 최적화 설정 불일치

```plaintext theme={null}
Error: Bytecode doesn't match
```

최적화 설정(활성화/비활성화 및 runs 수)이 컴파일 설정과 일치하는지 확인하세요.

### 이미 검증됨

```plaintext theme={null}
Error: Contract source code already verified
```

이는 이전에 검증이 성공했음을 의미합니다. 블록 익스플로러에서 확인하세요.

### 속도 제한

```plaintext theme={null}
Error: Rate limit exceeded
```

몇 분 후에 다시 시도하세요. 더 높은 한도가 필요하다면 API 플랜 업그레이드를 고려하세요.
