Ir al contenido principal
Time to read: 1 min

Verify Smart Contracts Using Foundry Script on Rootstock


Location: docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md Sidebar Position: 106 Tags: [guides, developers, smart contracts, rsk, rootstock, foundry, verification, explorer]

Overview

Smart contract verification on Rootstock using Foundry provides developers with a streamlined, automated approach to make deployed contracts transparent and trustworthy. This comprehensive guide covers both command-line verification and automated script-based verification using the Rootstock Explorer API, ensuring your contracts are properly verified for community trust and interaction.

Prerequisites

Before proceeding with verification, ensure you have completed the foundational setup:

  • Foundry Installation & Configuration: Follow the complete setup guide at Configure Foundry for Rootstock
  • Deployed Smart Contract: Your contract must be successfully deployed on Rootstock. See Deploy Smart Contracts for deployment instructions
  • Contract Source Code: Access to the exact source code used for deployment
  • Constructor Arguments: Documentation of all constructor parameters used during deployment
  • Environment Setup: Properly configured .env file with necessary API keys and RPC URLs

Important: This guide assumes familiarity with Foundry basics. For foundational knowledge, review the Foundry Project Creation and Smart Contract Development guides.

Environment Configuration

Required Environment Variables

Create or update your .env file with the following variables:

# Network Configuration
RSK_TESTNET_RPC_URL=https://public-node.testnet.rsk.co
RSK_MAINNET_RPC_URL=https://public-node.rsk.co

# API Keys for Verification
ROOTSTOCK_EXPLORER_API_KEY=your_rootstock_explorer_api_key
BLOCKSCOUT_API_KEY=your_blockscout_api_key

# Security (Never commit these!)
PRIVATE_KEY=0x...
MNEMONIC="your twelve word mnemonic phrase here"

# Contract Deployment Addresses
CONTRACT_ADDRESS=0x...
CONSTRUCTOR_ARG_1=1000
CONSTRUCTOR_ARG_2="InitialTokenName"
CONSTRUCTOR_ARG_3=0x742d35Cc6634C0532925a3b8D85ac5b5c3b42F96

Foundry Configuration

Ensure your foundry.toml includes proper verification settings:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.24"
optimizer = true
optimizer_runs = 200
evm_version = "london"

[rpc_endpoints]
rskTestnet = "${RSK_TESTNET_RPC_URL}"
rskMainnet = "${RSK_MAINNET_RPC_URL}"

[etherscan]
rskTestnet = { key = "${ROOTSTOCK_EXPLORER_API_KEY}", url = "https://explorer.testnet.rootstock.io/api" }
rskMainnet = { key = "${ROOTSTOCK_EXPLORER_API_KEY}", url = "https://explorer.rootstock.io/api" }

Method 1: Direct Command-Line Verification

Basic Verification (No Constructor Arguments)

For contracts without constructor arguments:

# Rootstock Testnet
forge verify-contract \
--chain-id 31 \
--verifier-url https://explorer.testnet.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
<CONTRACT_ADDRESS> \
src/YourContract.sol:YourContract

# Rootstock Mainnet
forge verify-contract \
--chain-id 30 \
--verifier-url https://explorer.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
<CONTRACT_ADDRESS> \
src/YourContract.sol:YourContract

Advanced Verification with Constructor Arguments

For contracts with constructor parameters, proper encoding is crucial:

Single Parameter Examples

# uint256 parameter
forge verify-contract \
--chain-id 31 \
--verifier-url https://explorer.testnet.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
--constructor-args $(cast abi-encode "constructor(uint256)" 1000) \
<CONTRACT_ADDRESS> \
src/TokenContract.sol:TokenContract

# string parameter
forge verify-contract \
--chain-id 31 \
--verifier-url https://explorer.testnet.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
--constructor-args $(cast abi-encode "constructor(string)" "MyToken") \
<CONTRACT_ADDRESS> \
src/TokenContract.sol:TokenContract

# address parameter
forge verify-contract \
--chain-id 31 \
--verifier-url https://explorer.testnet.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
--constructor-args $(cast abi-encode "constructor(address)" "0x742d35Cc6634C0532925a3b8D85ac5b5c3b42F96") \
<CONTRACT_ADDRESS> \
src/TokenContract.sol:TokenContract

Multiple Parameters

# Multiple parameters of different types
forge verify-contract \
--chain-id 31 \
--verifier-url https://explorer.testnet.rootstock.io/api \
--etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \
--constructor-args $(cast abi-encode "constructor(uint256,string,address)" 1000 "MyToken" "0x742d35Cc6634C0532925a3b8D85ac5b5c3b42F96") \
<CONTRACT_ADDRESS> \
src/ComplexContract.sol:ComplexContract

Array and Struct Parameters

# Array parameter
cast abi-encode "constructor(uint256[])" "[1,2,3,4,5]"

# Struct parameter (encoded as tuple)
cast abi-encode "constructor((uint256,address,string))" "(1000,0x742d35Cc6634C0532925a3b8D85ac5b5c3b42F96,\"TokenName\")"

Method 2: Automated Verification Scripts

Basic Verification Script

Create script/Verify.s.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";

contract VerifyScript is Script {
function run() external {
// Environment variable validation
require(vm.envExists("CONTRACT_ADDRESS"), "CONTRACT_ADDRESS not set");

// Get contract details from environment
address contractAddress = vm.envAddress("CONTRACT_ADDRESS");
uint256 chainId = block.chainid;

console.log("=== Contract Verification Details ===");
console.log("Contract Address:", contractAddress);
console.log("Chain ID:", chainId);

// Determine explorer URL and network name
string memory explorerUrl;
string memory networkName;

if (chainId == 31) {
explorerUrl = "https://explorer.testnet.rootstock.io/api";
networkName = "Rootstock Testnet";
} else if (chainId == 30) {
explorerUrl = "https://explorer.rootstock.io/api";
networkName = "Rootstock Mainnet";
} else {
revert("Unsupported chain ID. Use 30 (Mainnet) or 31 (Testnet)");
}

console.log("Network:", networkName);
console.log("Explorer URL:", explorerUrl);
console.log("");

// Generate verification command
console.log("=== Verification Command ===");
console.log("Copy and run the following command:");
console.log("");

// Check for constructor arguments
if (vm.envExists("CONSTRUCTOR_ARG_1")) {
_generateVerificationWithArgs(chainId, explorerUrl, contractAddress);
} else {
_generateBasicVerification(chainId, explorerUrl, contractAddress);
}
}

function _generateBasicVerification(
uint256 chainId,
string memory explorerUrl,
address contractAddress
) private view {
console.log(
string(abi.encodePacked(
"forge verify-contract \\",
"\n --chain-id ", vm.toString(chainId), " \\",
"\n --verifier-url ", explorerUrl, " \\",
"\n --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \\",
"\n ", vm.toString(contractAddress), " \\",
"\n src/YourContract.sol:YourContract"
))
);
}

function _generateVerificationWithArgs(
uint256 chainId,
string memory explorerUrl,
address contractAddress
) private view {
uint256 arg1 = vm.envUint("CONSTRUCTOR_ARG_1");
string memory arg2 = vm.envExists("CONSTRUCTOR_ARG_2") ? vm.envString("CONSTRUCTOR_ARG_2") : "";
address arg3 = vm.envExists("CONSTRUCTOR_ARG_3") ? vm.envAddress("CONSTRUCTOR_ARG_3") : address(0);

console.log("Constructor Arguments Found:");
console.log(" Arg 1 (uint256):", arg1);
if (bytes(arg2).length > 0) console.log(" Arg 2 (string):", arg2);
if (arg3 != address(0)) console.log(" Arg 3 (address):", arg3);
console.log("");

// Generate appropriate cast command based on available arguments
string memory castCommand;
if (arg3 != address(0)) {
castCommand = string(abi.encodePacked(
"$(cast abi-encode \"constructor(uint256,string,address)\" ",
vm.toString(arg1), " \"", arg2, "\" \"", vm.toString(arg3), "\")"
));
} else if (bytes(arg2).length > 0) {
castCommand = string(abi.encodePacked(
"$(cast abi-encode \"constructor(uint256,string)\" ",
vm.toString(arg1), " \"", arg2, "\")"
));
} else {
castCommand = string(abi.encodePacked(
"$(cast abi-encode \"constructor(uint256)\" ",
vm.toString(arg1), ")"
));
}

console.log(
string(abi.encodePacked(
"forge verify-contract \\",
"\n --chain-id ", vm.toString(chainId), " \\",
"\n --verifier-url ", explorerUrl, " \\",
"\n --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \\",
"\n --constructor-args ", castCommand, " \\",
"\n ", vm.toString(contractAddress), " \\",
"\n src/YourContract.sol:YourContract"
))
);
}
}

Execute the Verification Script

# Set required environment variables
export CONTRACT_ADDRESS=0x1234567890123456789012345678901234567890
export CONSTRUCTOR_ARG_1=1000
export CONSTRUCTOR_ARG_2="InitialTokenName"

# Run the verification script
forge script script/Verify.s.sol --rpc-url $RSK_TESTNET_RPC_URL

Advanced Multi-Contract Verification Script

Create script/BatchVerify.s.sol for projects with multiple contracts:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";

contract BatchVerifyScript is Script {
struct ContractInfo {
address contractAddress;
string contractPath;
string contractName;
string constructorSignature;
string constructorArgs;
}

function run() external {
uint256 chainId = block.chainid;
string memory explorerUrl = chainId == 31
? "https://explorer.testnet.rootstock.io/api"
: "https://explorer.rootstock.io/api";

// Define contracts to verify
ContractInfo[] memory contracts = new ContractInfo[](3);

contracts[0] = ContractInfo({
contractAddress: vm.envAddress("TOKEN_CONTRACT"),
contractPath: "src/Token.sol",
contractName: "Token",
constructorSignature: "constructor(uint256,string)",
constructorArgs: "1000 \"MyToken\""
});

contracts[1] = ContractInfo({
contractAddress: vm.envAddress("VAULT_CONTRACT"),
contractPath: "src/Vault.sol",
contractName: "Vault",
constructorSignature: "constructor(address)",
constructorArgs: string(abi.encodePacked("\"", vm.toString(vm.envAddress("TOKEN_CONTRACT")), "\""))
});

contracts[2] = ContractInfo({
contractAddress: vm.envAddress("FACTORY_CONTRACT"),
contractPath: "src/Factory.sol",
contractName: "Factory",
constructorSignature: "",
constructorArgs: ""
});

console.log("=== Batch Verification Commands ===");
console.log("Chain ID:", chainId);
console.log("Explorer URL:", explorerUrl);
console.log("");

for (uint i = 0; i < contracts.length; i++) {
_generateVerificationCommand(contracts[i], chainId, explorerUrl);
console.log("");
}
}

function _generateVerificationCommand(
ContractInfo memory contractInfo,
uint256 chainId,
string memory explorerUrl
) private view {
console.log(string(abi.encodePacked("Contract: ", contractInfo.contractName)));
console.log(string(abi.encodePacked("Address: ", vm.toString(contractInfo.contractAddress))));

string memory baseCommand = string(abi.encodePacked(
"forge verify-contract \\",
"\n --chain-id ", vm.toString(chainId), " \\",
"\n --verifier-url ", explorerUrl, " \\",
"\n --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY"
));

if (bytes(contractInfo.constructorSignature).length > 0) {
string memory constructorCmd = string(abi.encodePacked(
" \\",
"\n --constructor-args $(cast abi-encode \"",
contractInfo.constructorSignature,
"\" ",
contractInfo.constructorArgs,
")"
));
baseCommand = string(abi.encodePacked(baseCommand, constructorCmd));
}

string memory finalCommand = string(abi.encodePacked(
baseCommand,
" \\",
"\n ", vm.toString(contractInfo.contractAddress), " \\",
"\n ", contractInfo.contractPath, ":", contractInfo.contractName
));

console.log(finalCommand);
}
}

Error Handling & Troubleshooting

Common Verification Issues

1. Bytecode Mismatch

Error: "Bytecode does not match"

Solutions:

  • Ensure identical compiler version and settings
  • Verify optimizer settings match deployment configuration
  • Check that source code is exactly as deployed
  • Confirm all imports and dependencies are identical
# Check compiler version
forge --version

# Verify compilation matches deployment
forge build --force

2. Constructor Argument Encoding

Error: "Constructor arguments verification failed"

Solutions:

  • Double-check argument types and order
  • Use proper encoding for complex types
  • Verify argument values match deployment
# Debug constructor encoding
cast abi-encode "constructor(uint256,string)" 1000 "TestToken" --verbose

3. Network Configuration Issues

Error: "Invalid chain ID" or "Network not supported"

Solutions:

  • Verify correct chain ID (30 for mainnet, 31 for testnet)
  • Check RPC URL configuration
  • Confirm API key validity

4. API Rate Limits

Error: "Rate limit exceeded"

Solutions:

  • Implement delays between verification attempts
  • Use different API keys for different environments
  • Consider using Blockscout as alternative: Verify with Foundry and Blockscout

Verification Status Monitoring

Create a status checking script script/CheckVerification.s.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";

contract CheckVerificationScript is Script {
function run() external {
address contractAddress = vm.envAddress("CONTRACT_ADDRESS");
uint256 chainId = block.chainid;

string memory explorerUrl;
if (chainId == 31) {
explorerUrl = "https://explorer.testnet.rootstock.io/address/";
} else if (chainId == 30) {
explorerUrl = "https://explorer.rootstock.io/address/";
}

console.log("=== Verification Status Check ===");
console.log("Contract Address:", contractAddress);
console.log("Explorer URL:", string(abi.encodePacked(explorerUrl, vm.toString(contractAddress))));
console.log("");
console.log("Visit the URL above to check verification status");
console.log("Look for 'Contract' tab to confirm source code is visible");
}
}

Integration with CI/CD

GitHub Actions Workflow

Create .github/workflows/verify-contracts.yml:

name: Deploy and Verify Contracts

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
FOUNDRY_PROFILE: ci

jobs:
verify-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install dependencies
run: forge install

- name: Compile contracts
run: forge build --sizes

- name: Run tests
run: forge test -vvv

- name: Deploy to testnet
if: github.ref == 'refs/heads/main'
run: |
forge script script/Deploy.s.sol \
--rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }} \
--private-key ${{ secrets.PRIVATE_KEY }} \
--broadcast \
--verify \
--etherscan-api-key ${{ secrets.ROOTSTOCK_EXPLORER_API_KEY }}

- name: Generate verification commands
if: github.ref == 'refs/heads/main'
run: |
forge script script/Verify.s.sol \
--rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }}
env:
CONTRACT_ADDRESS: ${{ env.DEPLOYED_CONTRACT_ADDRESS }}

Best Practices for Production

1. Comprehensive Testing Before Verification

# Complete testing pipeline
forge test --gas-report
forge coverage
forge snapshot

2. Consistent Development Environment

  • Lock compiler versions in foundry.toml
  • Document all optimization settings
  • Use identical configurations across environments

3. Automated Verification Pipeline

Integrate verification into deployment scripts:

// script/DeployAndVerify.s.sol
contract DeployAndVerifyScript is Script {
function run() external {
vm.startBroadcast();

// Deploy contract
YourContract deployedContract = new YourContract(constructorArgs);

vm.stopBroadcast();

// Log deployment details
console.log("Contract deployed at:", address(deployedContract));
console.log("Transaction hash:", vm.getDeployedCode(address(deployedContract)));

// Generate verification command
console.log("\nVerification Command:");
console.log("forge verify-contract --chain-id 31 --verifier-url https://explorer.testnet.rootstock.io/api --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY", address(deployedContract), "src/YourContract.sol:YourContract");
}
}

4. Security Considerations

  • Never commit private keys or API keys
  • Use environment variables for sensitive data
  • Implement proper access controls for verification scripts
  • Regular API key rotation

Additional Resources

Next Steps

After successfully verifying your smart contracts:

  1. Monitor Contract Performance: Set up monitoring for verified contracts
  2. Implement Automated Testing: Create continuous verification workflows
  3. Document Contract Interfaces: Generate comprehensive API documentation
  4. Community Engagement: Share verified contracts for community review and interaction
  5. Security Audits: Consider professional security audits for production contracts

Important: Always test verification on Rootstock Testnet before attempting mainnet verification to avoid API key rate limits and ensure proper configuration.

Última actualización en por Gbolahan Akande