From 73a5c3f9e9b05f5d7e5de52d76b3fe8d4daa0497 Mon Sep 17 00:00:00 2001 From: Gbolahan Akande Date: Fri, 11 Jul 2025 17:57:01 +0100 Subject: [PATCH 1/3] feat: add verify smart contract using foundry script --- .../05-foundry/verify-smart-contracts.md | 446 ++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md diff --git a/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md b/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md new file mode 100644 index 00000000..132f0473 --- /dev/null +++ b/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md @@ -0,0 +1,446 @@ +--- +sidebar_label: Verify Smart Contract +sidebar_position: 106 +title: Verify Smart Contracts Using Foundry Script on Rootstock +description: "Learn how to verify smart contracts on Rootstock using Foundry scripts and the Rootstock Explorer" +tags: [guides, developers, smart contracts, rsk, rootstock, foundry, verification, explorer] +--- + +Smart contract verification on Rootstock using Foundry provides developers with a streamlined approach to make their deployed contracts transparent and trustworthy. This guide covers both command-line verification and automated script-based verification using the Rootstock Explorer API. + +## Overview + +Contract verification involves uploading and matching the contract's source code with the bytecode deployed on the blockchain. Once verified, users can read the contract's source code, understand its functionality, and interact with it more confidently through block explorers. + +## Prerequisites + +Before starting the verification process, ensure you have: + +- **Deployed smart contract** on Rootstock Testnet or Mainnet +- **Contract source code** and constructor arguments (if any) +- **Foundry installed** and configured for Rootstock +- **Rootstock Explorer API access** (optional but recommended) + +## Method 1: Using forge verify-contract Command + +### Environment Configuration + +First, set up your environment variables in a `.env` file: + +```bash +# .env file setup +ROOTSTOCK_EXPLORER_API_KEY=your_api_key_here +RSK_TESTNET_RPC_URL=https://public-node.testnet.rsk.co +RSK_MAINNET_RPC_URL=https://public-node.rsk.co +PRIVATE_KEY=your_private_key_here +``` + +:::warning[API Key Security] +Never commit your private keys or API keys to version control. Always use environment variables or secure key management systems. +::: + +### Basic Verification Commands + +**For Rootstock Testnet:** + +```bash +forge verify-contract \ + --chain-id 31 \ + --verifier-url https://explorer.testnet.rootstock.io/api \ + --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ + \ + src/YourContract.sol:YourContract +``` + +**For Rootstock Mainnet:** + +```bash +forge verify-contract \ + --chain-id 30 \ + --verifier-url https://explorer.rootstock.io/api \ + --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ + \ + src/YourContract.sol:YourContract +``` + +### Verification with Constructor Arguments + +If your contract has constructor arguments, you need to encode them properly: + +```bash +# For a contract with constructor arguments +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" "0x1234567890123456789012345678901234567890") \ + \ + src/YourContract.sol:YourContract +``` + +### Example: Verifying a Simple Counter Contract + +Let's verify a simple counter contract deployed on Rootstock Testnet: + +```bash +# Assuming your contract is at address 0x1234567890123456789012345678901234567890 +forge verify-contract \ + --chain-id 31 \ + --verifier-url https://explorer.testnet.rootstock.io/api \ + --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ + 0x1234567890123456789012345678901234567890 \ + src/Counter.sol:Counter +``` + +## Method 2: Automated Verification Script + +For more complex verification workflows, create a dedicated Foundry script that automates the verification process. + +### Create Verification Script + +Create a new file `script/Verify.s.sol`: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; + +contract VerifyScript is Script { + function run() external { + // Read deployment configuration + uint256 chainId = block.chainid; + address contractAddress = vm.envAddress("CONTRACT_ADDRESS"); + + console.log("Verifying contract on chain ID:", chainId); + console.log("Contract address:", contractAddress); + + // Determine the correct explorer URL based on chain ID + string memory explorerUrl; + if (chainId == 31) { + explorerUrl = "https://explorer.testnet.rootstock.io/api"; + console.log("Using Rootstock Testnet Explorer"); + } else if (chainId == 30) { + explorerUrl = "https://explorer.rootstock.io/api"; + console.log("Using Rootstock Mainnet Explorer"); + } else { + revert("Unsupported chain ID"); + } + + // If contract has constructor arguments, encode them here + // Example for a contract with uint256 constructor argument + uint256 constructorArg = vm.envUint("CONSTRUCTOR_ARG"); + + console.log("Constructor argument:", constructorArg); + console.log("Run the following command to verify:"); + console.log( + string(abi.encodePacked( + "forge verify-contract --chain-id ", + vm.toString(chainId), + " --verifier-url ", + explorerUrl, + " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY", + " --constructor-args $(cast abi-encode \"constructor(uint256)\" ", + vm.toString(constructorArg), + ") ", + vm.toString(contractAddress), + " src/YourContract.sol:YourContract" + )) + ); + } +} +``` + +### Run the Verification Script + +Execute the script to get the verification command: + +```bash +# Set environment variables +export CONTRACT_ADDRESS=0x1234567890123456789012345678901234567890 +export CONSTRUCTOR_ARG=1000 + +# Run the verification script +forge script script/Verify.s.sol --rpc-url $RSK_TESTNET_RPC_URL +``` + +### Advanced Verification Script with Multiple Contracts + +For projects with multiple contracts, create a batch verification script: + +```solidity +// script/BatchVerify.s.sol +// 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; + bytes 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_ADDRESS"), + contractPath: "src/Token.sol:Token", + contractName: "Token", + constructorArgs: abi.encode(1000000, "MyToken", "MTK") + }); + + contracts[1] = ContractInfo({ + contractAddress: vm.envAddress("STAKING_CONTRACT_ADDRESS"), + contractPath: "src/Staking.sol:Staking", + contractName: "Staking", + constructorArgs: abi.encode(vm.envAddress("TOKEN_CONTRACT_ADDRESS")) + }); + + contracts[2] = ContractInfo({ + contractAddress: vm.envAddress("GOVERNANCE_CONTRACT_ADDRESS"), + contractPath: "src/Governance.sol:Governance", + contractName: "Governance", + constructorArgs: abi.encode(vm.envAddress("TOKEN_CONTRACT_ADDRESS"), vm.envAddress("STAKING_CONTRACT_ADDRESS")) + }); + + // Generate verification commands for all contracts + for (uint256 i = 0; i < contracts.length; i++) { + console.log("\n=== Verifying", contracts[i].contractName, "==="); + console.log("Address:", contracts[i].contractAddress); + console.log("Command:"); + console.log( + string(abi.encodePacked( + "forge verify-contract --chain-id ", + vm.toString(chainId), + " --verifier-url ", + explorerUrl, + " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY", + " --constructor-args 0x", + _bytesToHex(contracts[i].constructorArgs), + " ", + vm.toString(contracts[i].contractAddress), + " ", + contracts[i].contractPath + )) + ); + } + } + + function _bytesToHex(bytes memory data) internal pure returns (string memory) { + bytes memory alphabet = "0123456789abcdef"; + bytes memory str = new bytes(2 + data.length * 2); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < data.length; i++) { + str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; + str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; + } + return string(str); + } +} +``` + +## Error Handling & Troubleshooting + +### Common Verification Issues + +#### 1. Mismatched Bytecode + +**Error Message:** +``` +Error: Contract verification failed: Bytecode does not match +``` + +**Solutions:** +- Ensure you're using the exact same compiler version and settings +- Check that all imported libraries and dependencies are identical +- Verify that constructor arguments are correctly encoded +- Confirm you're verifying the correct contract file and name + +#### 2. Incorrect Constructor Arguments + +**Error Message:** +``` +Error: Invalid constructor arguments +``` + +**Solutions:** +```bash +# Check constructor signature in your contract +grep -A 5 "constructor" src/YourContract.sol + +# Encode arguments properly +cast abi-encode "constructor(uint256,string,address)" 1000 "MyToken" "0x1234567890123456789012345678901234567890" + +# Verify encoding with cast +cast abi-decode "constructor(uint256,string,address)" 0x00000000000000000000000000000000000000000000000000000000000003e8... +``` + +#### 3. Network Configuration Issues + +**Error Message:** +``` +Error: Failed to connect to verification service +``` + +**Solutions:** +- Verify the correct chain ID and RPC URL +- Check internet connectivity +- Ensure the explorer API is accessible +- Try using alternative RPC endpoints + +#### 4. API Key Problems + +**Error Message:** +``` +Error: API key required or invalid +``` + +**Solutions:** +- Obtain a valid API key from the Rootstock Explorer +- Ensure the API key is properly set in environment variables +- Check API key permissions and rate limits + +### Verification Status Check + +After initiating verification, you can check the status through: + +**Rootstock Testnet Explorer:** +- Navigate to: https://explorer.testnet.rootstock.io/ +- Search for your contract address +- Check the "Contract" tab for verification status + +**Rootstock Mainnet Explorer:** +- Navigate to: https://explorer.rootstock.io/ +- Search for your contract address +- Check the "Contract" tab for verification status + +### Advanced Troubleshooting + +#### Verify Locally First + +Before submitting for verification, ensure your contract compiles correctly: + +```bash +# Clean and rebuild +forge clean +forge build + +# Check for compilation warnings +forge build --verbose + +# Verify contract size +forge build --sizes +``` + +#### Check Bytecode Match + +Compare local and deployed bytecode: + +```bash +# Get deployed bytecode +cast code --rpc-url $RSK_TESTNET_RPC_URL + +# Compare with local compilation +forge inspect YourContract bytecode +``` + +## Best Practices + +### 1. Consistent Development Environment + +- Use the same Solidity compiler version across all environments +- Lock dependency versions in your `foundry.toml` +- Document compiler settings and optimization parameters + +### 2. Automated Verification Pipeline + +Create a deployment script that includes verification: + +```solidity +// script/DeployAndVerify.s.sol +contract DeployAndVerifyScript is Script { + function run() external { + vm.startBroadcast(); + + // Deploy contract + YourContract contract = new YourContract(constructorArgs); + + vm.stopBroadcast(); + + // Log deployment info + console.log("Contract deployed at:", address(contract)); + console.log("Verification 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(contract), "src/YourContract.sol:YourContract"); + } +} +``` + +### 3. Documentation and Logging + +- Keep detailed logs of deployment and verification commands +- Document constructor arguments and their meanings +- Maintain a verification checklist for complex projects + +## Integration with CI/CD + +### GitHub Actions Example + +```yaml +name: Deploy and Verify +on: + push: + branches: [main] + +jobs: + deploy-and-verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Build contracts + run: forge build + + - name: Deploy to testnet + run: | + forge script script/Deploy.s.sol --rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }} --private-key ${{ secrets.PRIVATE_KEY }} --broadcast + + - name: Verify contracts + run: | + forge verify-contract --chain-id 31 --verifier-url https://explorer.testnet.rootstock.io/api --etherscan-api-key ${{ secrets.ROOTSTOCK_EXPLORER_API_KEY }} $CONTRACT_ADDRESS src/YourContract.sol:YourContract +``` + +## Resources + +- [Deploy Smart Contracts](/developers/smart-contracts/foundry/deploy-smart-contracts/) +- [Foundry Configuration Guide](/developers/smart-contracts/foundry/configure-foundry-rootstock/) +- [Rootstock Testnet Explorer](https://explorer.testnet.rootstock.io/) +- [Rootstock Mainnet Explorer](https://explorer.rootstock.io/) +- [Foundry Documentation](https://book.getfoundry.sh/) +- [Verify Smart Contract using Foundry and Blockscout](/developers/smart-contracts/verify-smart-contracts/foundry-blockscout/) + +## Next Steps + +After successfully verifying your smart contracts, consider: + +1. **Setting up monitoring** for your verified contracts +2. **Implementing automated testing** for verification workflows +3. **Creating documentation** for your verified contract interfaces +4. **Exploring advanced verification features** like proxy contract verification + +:::info[Credit] +This guide provides comprehensive coverage of smart contract verification on Rootstock using Foundry. For additional support or questions, join the [Rootstock Discord community](http://discord.gg/rootstock). +::: From 7e9d9980264b8313370492a0421d9fdd133e9bce Mon Sep 17 00:00:00 2001 From: Gbolahan Akande Date: Fri, 11 Jul 2025 17:59:44 +0100 Subject: [PATCH 2/3] feat: add verify smart contract on foundry with blockscout --- .../advanced-development-patterns.md | 550 ++++++++++++++++++ 1 file changed, 550 insertions(+) create mode 100644 docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md diff --git a/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md b/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md new file mode 100644 index 00000000..02279fb6 --- /dev/null +++ b/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md @@ -0,0 +1,550 @@ +--- +sidebar_label: Foundry Verification +sidebar_position: 3 +title: Verify Smart Contract using Foundry with Blockscout and Rootstock Explorer +description: "Learn how to verify smart contracts using Foundry with both Blockscout and Rootstock Explorer on Rootstock" +tags: [guides, developers, smart contracts, rsk, rootstock, foundry, verification, blockscout, explorer] +--- + +This guide demonstrates how to verify smart contracts on Rootstock using Foundry with both Blockscout and Rootstock Explorer, giving developers flexibility in choosing their preferred block explorer for contract verification. + +## Overview + +Contract verification is essential for transparency and trust in decentralized applications. Foundry provides seamless integration with multiple block explorers on Rootstock, including: + +- **Blockscout** - Open-source explorer with robust verification features +- **Rootstock Explorer** - Official Rootstock network explorer + +## Prerequisites + +Before verifying your contracts, ensure you have: + +- A deployed smart contract on Rootstock (Testnet or Mainnet) +- Foundry installed and configured +- Contract source code and constructor arguments (if applicable) +- Optional: API keys for enhanced verification features + +## Method 1: Verification with Blockscout + +Blockscout provides an open-source block explorer solution with excellent contract verification support. + +### Basic Blockscout Verification + +You need the contract address and the contract name (as defined in your Solidity file). Run the following command: + +**For Rootstock Testnet:** + +```bash +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +**For Rootstock Mainnet:** + +```bash +forge verify-contract \ + --chain-id 30 \ + --verifier blockscout \ + --verifier-url https://rootstock.blockscout.com/api \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +### Example Verification Output + +The response should look like this: + +```bash +Submitting verification for [src/YourContract.sol:YourContract] 0xYourContractAddress. +Submitted contract for verification: + Response: `OK` + GUID: `b390a97b95e4878626a6dbe5ef836ca1d1a0463a6806239d` + URL: https://rootstock-testnet.blockscout.com/address/0xYourContractAddress +Contract verification status: +Response: `OK` +Details: `Pending in queue` +Contract verification status: +Response: `OK` +Details: `Pass - Verified` +Contract successfully verified +All contracts were verified! +``` + +### Blockscout Verification with Constructor Arguments + +For contracts with constructor parameters: + +```bash +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + --constructor-args $(cast abi-encode "constructor(uint256,string)" 1000 "MyToken") \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +## Method 2: Verification with Rootstock Explorer + +The official Rootstock Explorer provides native verification support with additional features and integration. + +### Basic Rootstock Explorer Verification + +**For Rootstock Testnet:** + +```bash +forge verify-contract \ + --chain-id 31 \ + --verifier-url https://explorer.testnet.rootstock.io/api \ + --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +**For Rootstock Mainnet:** + +```bash +forge verify-contract \ + --chain-id 30 \ + --verifier-url https://explorer.rootstock.io/api \ + --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +### Rootstock Explorer Verification with Constructor Arguments + +```bash +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)" 1000 "MyToken") \ + "0xYourContractAddress" \ + src/YourContract.sol:YourContract +``` + +## Method 3: Automated Verification Script + +Create a versatile verification script that works with both explorers: + +```solidity +// script/UniversalVerify.s.sol +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; + +contract UniversalVerifyScript is Script { + enum Explorer { Blockscout, RootstockExplorer } + + function run() external { + uint256 chainId = block.chainid; + address contractAddress = vm.envAddress("CONTRACT_ADDRESS"); + Explorer explorer = Explorer(vm.envUint("EXPLORER_TYPE")); // 0 for Blockscout, 1 for Rootstock Explorer + + console.log("Verifying contract on chain ID:", chainId); + console.log("Contract address:", contractAddress); + + if (explorer == Explorer.Blockscout) { + _generateBlockscoutCommand(chainId, contractAddress); + } else { + _generateRootstockExplorerCommand(chainId, contractAddress); + } + } + + function _generateBlockscoutCommand(uint256 chainId, address contractAddress) internal view { + string memory explorerUrl = chainId == 31 ? + "https://rootstock-testnet.blockscout.com/api" : + "https://rootstock.blockscout.com/api"; + + console.log("=== Blockscout Verification ==="); + console.log("Command:"); + console.log( + string(abi.encodePacked( + "forge verify-contract --chain-id ", + vm.toString(chainId), + " --verifier blockscout --verifier-url ", + explorerUrl, + " ", + vm.toString(contractAddress), + " src/YourContract.sol:YourContract" + )) + ); + } + + function _generateRootstockExplorerCommand(uint256 chainId, address contractAddress) internal view { + string memory explorerUrl = chainId == 31 ? + "https://explorer.testnet.rootstock.io/api" : + "https://explorer.rootstock.io/api"; + + console.log("=== Rootstock Explorer Verification ==="); + console.log("Command:"); + console.log( + string(abi.encodePacked( + "forge verify-contract --chain-id ", + vm.toString(chainId), + " --verifier-url ", + explorerUrl, + " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY ", + vm.toString(contractAddress), + " src/YourContract.sol:YourContract" + )) + ); + } +} +``` + +### Using the Universal Verification Script + +```bash +# For Blockscout verification +export CONTRACT_ADDRESS=0xYourContractAddress +export EXPLORER_TYPE=0 +forge script script/UniversalVerify.s.sol --rpc-url $RSK_TESTNET_RPC_URL + +# For Rootstock Explorer verification +export EXPLORER_TYPE=1 +forge script script/UniversalVerify.s.sol --rpc-url $RSK_TESTNET_RPC_URL +``` + +## Comparison: Blockscout vs Rootstock Explorer + +| Feature | Blockscout | Rootstock Explorer | +|---------|------------|-------------------| +| **API Key Required** | No | Optional but recommended | +| **Verification Speed** | Fast | Fast | +| **Constructor Arg Support** | Yes | Yes | +| **Proxy Contract Support** | Yes | Yes | +| **Multi-file Contract Support** | Yes | Yes | +| **Source Code Display** | Excellent | Excellent | +| **Transaction History** | Complete | Complete | +| **Rate Limiting** | Minimal | Moderate | +| **Official Support** | Community | Rootstock Team | + +## Advanced Verification Scenarios + +### Verifying Proxy Contracts + +For proxy implementations, verify both the proxy and implementation contracts: + +```bash +# Verify the implementation contract first +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + "0xImplementationAddress" \ + src/Implementation.sol:Implementation + +# Then verify the proxy contract +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + --constructor-args $(cast abi-encode "constructor(address,bytes)" "0xImplementationAddress" "0x") \ + "0xProxyAddress" \ + src/Proxy.sol:Proxy +``` + +### Verifying Contracts with External Libraries + +For contracts using external libraries, ensure all dependencies are properly linked: + +```bash +# Deploy library first (if not already deployed) +forge script script/DeployLibrary.s.sol --rpc-url $RSK_TESTNET_RPC_URL --broadcast + +# Verify library +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + "0xLibraryAddress" \ + src/MyLibrary.sol:MyLibrary + +# Verify main contract with library links +forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + --libraries src/MyLibrary.sol:MyLibrary:0xLibraryAddress \ + "0xMainContractAddress" \ + src/MainContract.sol:MainContract +``` + +## Troubleshooting Common Issues + +### Issue 1: Compilation Version Mismatch + +**Error:** `Bytecode does not match the expected one` + +**Solution:** +```bash +# Check your foundry.toml for compiler version +cat foundry.toml + +# Ensure it matches your contract pragma +grep "pragma solidity" src/YourContract.sol + +# Rebuild with specific version if needed +forge build --use 0.8.19 +``` + +### Issue 2: Constructor Arguments Encoding + +**Error:** `Invalid constructor arguments` + +**Solution:** +```bash +# Check constructor signature +forge inspect YourContract constructor + +# Test argument encoding +cast abi-encode "constructor(uint256,string,address)" 1000 "TestToken" "0x1234567890123456789012345678901234567890" + +# Verify encoding +cast abi-decode "constructor(uint256,string,address)" 0x000... +``` + +### Issue 3: Multiple File Dependencies + +**Error:** `Source code compilation failed` + +**Solution:** +```bash +# Ensure all imports are resolvable +forge build --verbose + +# Check for missing remappings +cat remappings.txt + +# Verify all dependencies are included +tree src/ +``` + +### Issue 4: Network Connectivity Issues + +**Error:** `Failed to submit verification request` + +**Solutions:** +- Check internet connectivity +- Verify explorer URL accessibility +- Try alternative RPC endpoints +- Check for rate limiting + +```bash +# Test explorer connectivity +curl -I https://rootstock-testnet.blockscout.com/api + +# Test with different RPC +export RSK_TESTNET_RPC_URL=https://public-node.testnet.rsk.co +``` + +## Best Practices + +### 1. Pre-verification Checklist + +Before submitting for verification: + +```bash +# Clean build +forge clean && forge build + +# Check for warnings +forge build --verbose 2>&1 | grep -i warning + +# Verify contract size limits +forge build --sizes + +# Test deployment locally +forge script script/Deploy.s.sol --fork-url $RSK_TESTNET_RPC_URL +``` + +### 2. Verification Automation + +Create a comprehensive verification workflow: + +```bash +#!/bin/bash +# verify.sh - Automated verification script + +set -e + +CONTRACT_ADDRESS=$1 +CONTRACT_NAME=$2 +EXPLORER_TYPE=${3:-"blockscout"} # default to blockscout + +if [ -z "$CONTRACT_ADDRESS" ] || [ -z "$CONTRACT_NAME" ]; then + echo "Usage: ./verify.sh [explorer_type]" + exit 1 +fi + +echo "Verifying $CONTRACT_NAME at $CONTRACT_ADDRESS using $EXPLORER_TYPE" + +if [ "$EXPLORER_TYPE" = "blockscout" ]; then + forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + "$CONTRACT_ADDRESS" \ + "src/$CONTRACT_NAME.sol:$CONTRACT_NAME" +else + forge verify-contract \ + --chain-id 31 \ + --verifier-url https://explorer.testnet.rootstock.io/api \ + --etherscan-api-key "$ROOTSTOCK_EXPLORER_API_KEY" \ + "$CONTRACT_ADDRESS" \ + "src/$CONTRACT_NAME.sol:$CONTRACT_NAME" +fi + +echo "Verification submitted successfully!" +``` + +### 3. Environment Management + +Use environment-specific configuration: + +```toml +# foundry.toml +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc_version = "0.8.19" +optimizer = true +optimizer_runs = 200 + +[profile.testnet] +chain_id = 31 +eth_rpc_url = "${RSK_TESTNET_RPC_URL}" + +[profile.mainnet] +chain_id = 30 +eth_rpc_url = "${RSK_MAINNET_RPC_URL}" +``` + +## Verification Status Monitoring + +### Check Verification Status + +After submitting verification, monitor the status: + +**Blockscout:** +- Visit: https://rootstock-testnet.blockscout.com/address/[CONTRACT_ADDRESS] +- Look for the "Contract" tab +- Check verification status and source code display + +**Rootstock Explorer:** +- Visit: https://explorer.testnet.rootstock.io/address/[CONTRACT_ADDRESS] +- Navigate to the "Contract" section +- Verify source code is visible and properly formatted + +### Automated Status Checking + +Create a script to monitor verification status: + +```bash +#!/bin/bash +# check_verification.sh + +CONTRACT_ADDRESS=$1 +EXPLORER_TYPE=${2:-"blockscout"} + +if [ "$EXPLORER_TYPE" = "blockscout" ]; then + URL="https://rootstock-testnet.blockscout.com/api/v2/smart-contracts/$CONTRACT_ADDRESS" +else + URL="https://explorer.testnet.rootstock.io/api/contracts/$CONTRACT_ADDRESS/source" +fi + +echo "Checking verification status for $CONTRACT_ADDRESS" +curl -s "$URL" | jq '.verified_at // .source_code // "Not verified yet"' +``` + +## Integration with Development Workflow + +### GitHub Actions Integration + +```yaml +name: Deploy and Verify Contracts +on: + push: + branches: [main] + paths: ['contracts/**', 'script/**'] + +jobs: + deploy-and-verify: + runs-on: ubuntu-latest + strategy: + matrix: + explorer: [blockscout, rootstock] + + steps: + - uses: actions/checkout@v3 + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Build contracts + run: forge build --sizes + + - name: Run tests + run: forge test -vvv + + - name: Deploy contracts + run: | + forge script script/Deploy.s.sol \ + --rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }} \ + --private-key ${{ secrets.PRIVATE_KEY }} \ + --broadcast + + - name: Verify with Blockscout + if: matrix.explorer == 'blockscout' + run: | + forge verify-contract \ + --chain-id 31 \ + --verifier blockscout \ + --verifier-url https://rootstock-testnet.blockscout.com/api \ + ${{ env.CONTRACT_ADDRESS }} \ + src/MyContract.sol:MyContract + + - name: Verify with Rootstock Explorer + if: matrix.explorer == 'rootstock' + run: | + forge verify-contract \ + --chain-id 31 \ + --verifier-url https://explorer.testnet.rootstock.io/api \ + --etherscan-api-key ${{ secrets.ROOTSTOCK_EXPLORER_API_KEY }} \ + ${{ env.CONTRACT_ADDRESS }} \ + src/MyContract.sol:MyContract +``` + +## Resources + +- [Foundry Book - Contract Verification](https://book.getfoundry.sh/reference/forge/forge-verify-contract) +- [Deploy Smart Contracts with Foundry](/developers/smart-contracts/foundry/deploy-smart-contracts/) +- [Rootstock Testnet Blockscout](https://rootstock-testnet.blockscout.com/) +- [Rootstock Mainnet Blockscout](https://rootstock.blockscout.com/) +- [Rootstock Testnet Explorer](https://explorer.testnet.rootstock.io/) +- [Rootstock Mainnet Explorer](https://explorer.rootstock.io/) +- [Verify a Smart Contract using the Hardhat Verification Plugin](/developers/smart-contracts/verify-smart-contracts/hardhat-verify-plugin/) + +## Next Steps + +After successful verification: + +1. **Test contract interactions** through the explorer interfaces +2. **Set up monitoring** for your verified contracts +3. **Document verification procedures** for your team +4. **Consider proxy pattern verification** for upgradeable contracts +5. **Implement automated verification** in your CI/CD pipeline + +:::info[Credit] + +This enhanced guide provides comprehensive coverage of smart contract verification on Rootstock using Foundry with multiple explorer options. This documentation was created to address GitHub issue #308 and provides developers with flexible verification options for the Rootstock ecosystem. + +::: From 88296357daacd8abf9b2252882a6d370ad645a4e Mon Sep 17 00:00:00 2001 From: Gbolahan Akande Date: Fri, 11 Jul 2025 18:03:59 +0100 Subject: [PATCH 3/3] feat: updated with comprehensive Foundry verification docs --- .../advanced-development-patterns.md | 1323 +++++++++++------ .../05-foundry/verify-smart-contracts.md | 657 +++++--- 2 files changed, 1271 insertions(+), 709 deletions(-) diff --git a/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md b/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md index 02279fb6..28413269 100644 --- a/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md +++ b/docs/02-developers/05-smart-contracts/05-foundry/advanced-development-patterns.md @@ -1,550 +1,951 @@ +# Advanced Smart Contract Development and Deployment with Foundry on Rootstock + --- -sidebar_label: Foundry Verification -sidebar_position: 3 -title: Verify Smart Contract using Foundry with Blockscout and Rootstock Explorer -description: "Learn how to verify smart contracts using Foundry with both Blockscout and Rootstock Explorer on Rootstock" -tags: [guides, developers, smart contracts, rsk, rootstock, foundry, verification, blockscout, explorer] +**Location**: `docs/02-developers/05-smart-contracts/05-foundry/advanced-smart-contracts.md` +**Sidebar Position**: 107 +**Tags**: [guides, developers, smart contracts, rsk, rootstock, foundry, deployment, verification, advanced] --- -This guide demonstrates how to verify smart contracts on Rootstock using Foundry with both Blockscout and Rootstock Explorer, giving developers flexibility in choosing their preferred block explorer for contract verification. - ## Overview -Contract verification is essential for transparency and trust in decentralized applications. Foundry provides seamless integration with multiple block explorers on Rootstock, including: - -- **Blockscout** - Open-source explorer with robust verification features -- **Rootstock Explorer** - Official Rootstock network explorer +This comprehensive guide covers advanced smart contract development patterns, deployment strategies, and verification workflows specifically for Rootstock using Foundry. Building upon the foundational concepts covered in the [Smart Contract Basics](/developers/smart-contracts/foundry/smart-contracts/) guide, this documentation provides production-ready examples and best practices for complex contract architectures. ## Prerequisites -Before verifying your contracts, ensure you have: +Before proceeding with advanced smart contract development, ensure you have mastered the foundational concepts: -- A deployed smart contract on Rootstock (Testnet or Mainnet) -- Foundry installed and configured -- Contract source code and constructor arguments (if applicable) -- Optional: API keys for enhanced verification features +- **Foundry Fundamentals**: Complete understanding of [Foundry Project Creation](/developers/smart-contracts/foundry/create-foundry-project/) and [Foundry Configuration](/developers/smart-contracts/foundry/configure-foundry-rootstock/) +- **Basic Smart Contract Development**: Proficiency with concepts covered in [Smart Contract Development](/developers/smart-contracts/foundry/smart-contracts/) +- **Testing Knowledge**: Experience with [Testing Smart Contracts](/developers/smart-contracts/foundry/test-smart-contracts/) +- **Deployment Experience**: Successful completion of [Deploy Smart Contracts](/developers/smart-contracts/foundry/deploy-smart-contracts/) -## Method 1: Verification with Blockscout +> **Note**: This guide assumes intermediate to advanced Solidity knowledge. For basic Solidity concepts, refer to the [Solidity Documentation](https://docs.soliditylang.org/). -Blockscout provides an open-source block explorer solution with excellent contract verification support. +## Advanced Contract Architecture Patterns -### Basic Blockscout Verification +### 1. Upgradeable Contract Pattern -You need the contract address and the contract name (as defined in your Solidity file). Run the following command: +Modern DeFi applications require upgradeability for bug fixes and feature additions. This example demonstrates OpenZeppelin's proxy pattern implementation: -**For Rootstock Testnet:** +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +/** + * @title AdvancedTokenV1 + * @dev Upgradeable ERC20 token with advanced features + * @notice This contract demonstrates production-ready patterns for Rootstock + */ +contract AdvancedTokenV1 is + Initializable, + ERC20Upgradeable, + OwnableUpgradeable, + UUPSUpgradeable +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } -```bash -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract -``` + /** + * @dev Initializes the contract + * @param _name Token name + * @param _symbol Token symbol + * @param _initialSupply Initial token supply + * @param _owner Contract owner address + */ + function initialize( + string memory _name, + string memory _symbol, + uint256 _initialSupply, + address _owner + ) public initializer { + __ERC20_init(_name, _symbol); + __Ownable_init(_owner); + __UUPSUpgradeable_init(); + + _mint(_owner, _initialSupply * 10**decimals()); + } -**For Rootstock Mainnet:** + /** + * @dev Mint new tokens (only owner) + * @param to Recipient address + * @param amount Amount to mint + */ + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } -```bash -forge verify-contract \ - --chain-id 30 \ - --verifier blockscout \ - --verifier-url https://rootstock.blockscout.com/api \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract + /** + * @dev Required override for UUPS upgrades + */ + function _authorizeUpgrade(address newImplementation) + internal + onlyOwner + override + {} + + /** + * @dev Returns the version of the contract + */ + function version() public pure returns (string memory) { + return "1.0.0"; + } +} ``` -### Example Verification Output - -The response should look like this: - -```bash -Submitting verification for [src/YourContract.sol:YourContract] 0xYourContractAddress. -Submitted contract for verification: - Response: `OK` - GUID: `b390a97b95e4878626a6dbe5ef836ca1d1a0463a6806239d` - URL: https://rootstock-testnet.blockscout.com/address/0xYourContractAddress -Contract verification status: -Response: `OK` -Details: `Pending in queue` -Contract verification status: -Response: `OK` -Details: `Pass - Verified` -Contract successfully verified -All contracts were verified! -``` +### 2. Multi-Contract System with Factory Pattern -### Blockscout Verification with Constructor Arguments +Complex DApps often require multiple interacting contracts. This pattern demonstrates a factory-vault system: -For contracts with constructor parameters: +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title VaultFactory + * @dev Factory contract for creating and managing token vaults + */ +contract VaultFactory is AccessControl, ReentrancyGuard { + using SafeERC20 for IERC20; + + bytes32 public constant VAULT_CREATOR_ROLE = keccak256("VAULT_CREATOR_ROLE"); + + struct VaultInfo { + address vaultAddress; + address tokenAddress; + address creator; + uint256 createdAt; + bool isActive; + } + + mapping(address => VaultInfo) public vaults; + mapping(address => address[]) public userVaults; + address[] public allVaults; + + event VaultCreated( + address indexed vaultAddress, + address indexed tokenAddress, + address indexed creator, + uint256 timestamp + ); + + event VaultDeactivated(address indexed vaultAddress, uint256 timestamp); -```bash -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - --constructor-args $(cast abi-encode "constructor(uint256,string)" 1000 "MyToken") \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract -``` + constructor(address _admin) { + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + _grantRole(VAULT_CREATOR_ROLE, _admin); + } + + /** + * @dev Creates a new vault for specified token + * @param _tokenAddress Address of the ERC20 token + * @param _rewardRate Daily reward rate (basis points) + * @return vaultAddress Address of the created vault + */ + function createVault( + address _tokenAddress, + uint256 _rewardRate + ) external onlyRole(VAULT_CREATOR_ROLE) nonReentrant returns (address vaultAddress) { + require(_tokenAddress != address(0), "Invalid token address"); + require(_rewardRate > 0 && _rewardRate <= 10000, "Invalid reward rate"); + + // Deploy new vault + StakingVault vault = new StakingVault( + _tokenAddress, + _rewardRate, + msg.sender, + address(this) + ); + + vaultAddress = address(vault); + + // Store vault information + vaults[vaultAddress] = VaultInfo({ + vaultAddress: vaultAddress, + tokenAddress: _tokenAddress, + creator: msg.sender, + createdAt: block.timestamp, + isActive: true + }); + + userVaults[msg.sender].push(vaultAddress); + allVaults.push(vaultAddress); + + emit VaultCreated(vaultAddress, _tokenAddress, msg.sender, block.timestamp); + + return vaultAddress; + } -## Method 2: Verification with Rootstock Explorer + /** + * @dev Deactivates a vault + * @param _vaultAddress Address of the vault to deactivate + */ + function deactivateVault(address _vaultAddress) + external + onlyRole(DEFAULT_ADMIN_ROLE) + { + require(vaults[_vaultAddress].isActive, "Vault already inactive"); + vaults[_vaultAddress].isActive = false; + + emit VaultDeactivated(_vaultAddress, block.timestamp); + } -The official Rootstock Explorer provides native verification support with additional features and integration. + /** + * @dev Returns all vaults created by a user + * @param _user User address + * @return Array of vault addresses + */ + function getUserVaults(address _user) external view returns (address[] memory) { + return userVaults[_user]; + } -### Basic Rootstock Explorer Verification + /** + * @dev Returns total number of vaults + */ + function getTotalVaults() external view returns (uint256) { + return allVaults.length; + } +} -**For Rootstock Testnet:** +/** + * @title StakingVault + * @dev Individual staking vault for token rewards + */ +contract StakingVault is ReentrancyGuard { + using SafeERC20 for IERC20; -```bash -forge verify-contract \ - --chain-id 31 \ - --verifier-url https://explorer.testnet.rootstock.io/api \ - --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract -``` + IERC20 public immutable stakingToken; + address public immutable factory; + address public owner; + + uint256 public rewardRate; // Daily reward rate in basis points + uint256 public totalStaked; + uint256 public lastUpdateTime; + uint256 public rewardPerTokenStored; + + mapping(address => uint256) public userStaked; + mapping(address => uint256) public userRewardPerTokenPaid; + mapping(address => uint256) public rewards; + + event Staked(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + event RewardPaid(address indexed user, uint256 reward); -**For Rootstock Mainnet:** + modifier onlyOwner() { + require(msg.sender == owner, "Not authorized"); + _; + } -```bash -forge verify-contract \ - --chain-id 30 \ - --verifier-url https://explorer.rootstock.io/api \ - --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract -``` + modifier updateReward(address account) { + rewardPerTokenStored = rewardPerToken(); + lastUpdateTime = block.timestamp; + + if (account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = rewardPerTokenStored; + } + _; + } + + constructor( + address _stakingToken, + uint256 _rewardRate, + address _owner, + address _factory + ) { + stakingToken = IERC20(_stakingToken); + rewardRate = _rewardRate; + owner = _owner; + factory = _factory; + lastUpdateTime = block.timestamp; + } + + /** + * @dev Calculates reward per token + */ + function rewardPerToken() public view returns (uint256) { + if (totalStaked == 0) { + return rewardPerTokenStored; + } + + return rewardPerTokenStored + + (((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / + (86400 * 10000 * totalStaked)); + } + + /** + * @dev Calculates earned rewards for an account + */ + function earned(address account) public view returns (uint256) { + return ((userStaked[account] * + (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18) + + rewards[account]; + } + + /** + * @dev Stake tokens + * @param amount Amount to stake + */ + function stake(uint256 amount) + external + nonReentrant + updateReward(msg.sender) + { + require(amount > 0, "Cannot stake 0"); + + totalStaked += amount; + userStaked[msg.sender] += amount; + + stakingToken.safeTransferFrom(msg.sender, address(this), amount); + + emit Staked(msg.sender, amount); + } + + /** + * @dev Withdraw staked tokens + * @param amount Amount to withdraw + */ + function withdraw(uint256 amount) + external + nonReentrant + updateReward(msg.sender) + { + require(amount > 0, "Cannot withdraw 0"); + require(userStaked[msg.sender] >= amount, "Insufficient staked amount"); + + totalStaked -= amount; + userStaked[msg.sender] -= amount; + + stakingToken.safeTransfer(msg.sender, amount); + + emit Withdrawn(msg.sender, amount); + } -### Rootstock Explorer Verification with Constructor Arguments + /** + * @dev Claim earned rewards + */ + function getReward() external nonReentrant updateReward(msg.sender) { + uint256 reward = rewards[msg.sender]; + if (reward > 0) { + rewards[msg.sender] = 0; + // In production, implement reward token distribution + emit RewardPaid(msg.sender, reward); + } + } -```bash -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)" 1000 "MyToken") \ - "0xYourContractAddress" \ - src/YourContract.sol:YourContract + /** + * @dev Emergency withdrawal (owner only) + */ + function emergencyWithdraw() external onlyOwner { + uint256 balance = stakingToken.balanceOf(address(this)); + stakingToken.safeTransfer(owner, balance); + } +} ``` -## Method 3: Automated Verification Script +## Advanced Deployment Scripts + +### 1. Comprehensive Deployment Script with Verification -Create a versatile verification script that works with both explorers: +Create `script/DeployAdvanced.s.sol`: ```solidity -// script/UniversalVerify.s.sol // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; - -contract UniversalVerifyScript is Script { - enum Explorer { Blockscout, RootstockExplorer } +import {AdvancedTokenV1} from "../src/AdvancedTokenV1.sol"; +import {VaultFactory} from "../src/VaultFactory.sol"; +import {StakingVault} from "../src/StakingVault.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +/** + * @title Advanced Deployment Script + * @dev Deploys complete DeFi system with upgradeable tokens and vault factory + */ +contract DeployAdvancedScript is Script { + // Deployment configuration + struct DeploymentConfig { + string tokenName; + string tokenSymbol; + uint256 initialSupply; + address owner; + uint256 rewardRate; + } + + // Deployment results + struct DeploymentResult { + address proxyToken; + address implementationToken; + address vaultFactory; + address stakingVault; + uint256 chainId; + uint256 deploymentTimestamp; + } + + DeploymentResult public deploymentResult; function run() external { - uint256 chainId = block.chainid; - address contractAddress = vm.envAddress("CONTRACT_ADDRESS"); - Explorer explorer = Explorer(vm.envUint("EXPLORER_TYPE")); // 0 for Blockscout, 1 for Rootstock Explorer + // Load configuration from environment + DeploymentConfig memory config = _loadConfiguration(); - console.log("Verifying contract on chain ID:", chainId); - console.log("Contract address:", contractAddress); + // Validate configuration + _validateConfiguration(config); - if (explorer == Explorer.Blockscout) { - _generateBlockscoutCommand(chainId, contractAddress); - } else { - _generateRootstockExplorerCommand(chainId, contractAddress); - } + console.log("=== Starting Advanced Deployment ==="); + console.log("Chain ID:", block.chainid); + console.log("Deployer:", msg.sender); + console.log(""); + + vm.startBroadcast(); + + // Deploy upgradeable token + (address proxyToken, address implementationToken) = _deployUpgradeableToken(config); + + // Deploy vault factory + address vaultFactory = _deployVaultFactory(config.owner); + + // Create a vault for the token + address stakingVault = _createStakingVault(vaultFactory, proxyToken, config.rewardRate); + + vm.stopBroadcast(); + + // Store deployment results + deploymentResult = DeploymentResult({ + proxyToken: proxyToken, + implementationToken: implementationToken, + vaultFactory: vaultFactory, + stakingVault: stakingVault, + chainId: block.chainid, + deploymentTimestamp: block.timestamp + }); + + // Generate verification commands + _generateVerificationCommands(); + + // Save deployment addresses + _saveDeploymentAddresses(); + + console.log("=== Deployment Complete ==="); } - function _generateBlockscoutCommand(uint256 chainId, address contractAddress) internal view { - string memory explorerUrl = chainId == 31 ? - "https://rootstock-testnet.blockscout.com/api" : - "https://rootstock.blockscout.com/api"; - - console.log("=== Blockscout Verification ==="); - console.log("Command:"); - console.log( - string(abi.encodePacked( - "forge verify-contract --chain-id ", - vm.toString(chainId), - " --verifier blockscout --verifier-url ", - explorerUrl, - " ", - vm.toString(contractAddress), - " src/YourContract.sol:YourContract" - )) - ); + function _loadConfiguration() internal view returns (DeploymentConfig memory) { + return DeploymentConfig({ + tokenName: vm.envOr("TOKEN_NAME", string("Advanced Token")), + tokenSymbol: vm.envOr("TOKEN_SYMBOL", string("ADV")), + initialSupply: vm.envOr("INITIAL_SUPPLY", uint256(1000000)), + owner: vm.envOr("OWNER_ADDRESS", msg.sender), + rewardRate: vm.envOr("REWARD_RATE", uint256(500)) // 5% daily + }); } - function _generateRootstockExplorerCommand(uint256 chainId, address contractAddress) internal view { - string memory explorerUrl = chainId == 31 ? - "https://explorer.testnet.rootstock.io/api" : - "https://explorer.rootstock.io/api"; - - console.log("=== Rootstock Explorer Verification ==="); - console.log("Command:"); - console.log( - string(abi.encodePacked( - "forge verify-contract --chain-id ", - vm.toString(chainId), - " --verifier-url ", - explorerUrl, - " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY ", - vm.toString(contractAddress), - " src/YourContract.sol:YourContract" - )) + function _validateConfiguration(DeploymentConfig memory config) internal pure { + require(bytes(config.tokenName).length > 0, "Token name required"); + require(bytes(config.tokenSymbol).length > 0, "Token symbol required"); + require(config.initialSupply > 0, "Initial supply must be positive"); + require(config.owner != address(0), "Owner address required"); + require(config.rewardRate > 0 && config.rewardRate <= 10000, "Invalid reward rate"); + } + + function _deployUpgradeableToken(DeploymentConfig memory config) + internal + returns (address proxy, address implementation) + { + console.log("Deploying upgradeable token..."); + + // Deploy implementation + implementation = address(new AdvancedTokenV1()); + console.log("Token implementation:", implementation); + + // Prepare initializer data + bytes memory initData = abi.encodeWithSelector( + AdvancedTokenV1.initialize.selector, + config.tokenName, + config.tokenSymbol, + config.initialSupply, + config.owner ); + + // Deploy proxy + proxy = address(new ERC1967Proxy(implementation, initData)); + console.log("Token proxy:", proxy); + console.log("Token name:", config.tokenName); + console.log("Token symbol:", config.tokenSymbol); + console.log("Initial supply:", config.initialSupply); + console.log(""); + + return (proxy, implementation); + } + + function _deployVaultFactory(address owner) internal returns (address factory) { + console.log("Deploying vault factory..."); + + factory = address(new VaultFactory(owner)); + console.log("Vault factory:", factory); + console.log("Factory owner:", owner); + console.log(""); + + return factory; + } + + function _createStakingVault( + address factory, + address token, + uint256 rewardRate + ) internal returns (address vault) { + console.log("Creating staking vault..."); + + VaultFactory factoryContract = VaultFactory(factory); + vault = factoryContract.createVault(token, rewardRate); + + console.log("Staking vault:", vault); + console.log("Staking token:", token); + console.log("Reward rate:", rewardRate, "basis points"); + console.log(""); + + return vault; + } + + function _generateVerificationCommands() internal view { + console.log("=== Verification Commands ==="); + + string memory baseCommand = string(abi.encodePacked( + "forge verify-contract \\", + "\n --chain-id ", vm.toString(block.chainid), " \\", + "\n --verifier-url ", _getExplorerUrl(), " \\", + "\n --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY" + )); + + // Token implementation verification + console.log("1. Token Implementation:"); + console.log(string(abi.encodePacked( + baseCommand, " \\", + "\n ", vm.toString(deploymentResult.implementationToken), " \\", + "\n src/AdvancedTokenV1.sol:AdvancedTokenV1" + ))); + console.log(""); + + // Vault factory verification + console.log("2. Vault Factory:"); + console.log(string(abi.encodePacked( + baseCommand, + "\n --constructor-args $(cast abi-encode \"constructor(address)\" \"", + vm.toString(vm.envOr("OWNER_ADDRESS", msg.sender)), "\") \\", + "\n ", vm.toString(deploymentResult.vaultFactory), " \\", + "\n src/VaultFactory.sol:VaultFactory" + ))); + console.log(""); + + // Note: Proxy and vault contracts require special handling + console.log("Note: Proxy and StakingVault contracts require manual verification"); + console.log("Refer to the verification guide for proxy contract verification"); + console.log(""); + } + + function _getExplorerUrl() internal view returns (string memory) { + if (block.chainid == 31) { + return "https://explorer.testnet.rootstock.io/api"; + } else if (block.chainid == 30) { + return "https://explorer.rootstock.io/api"; + } else { + revert("Unsupported chain ID"); + } + } + + function _saveDeploymentAddresses() internal { + string memory deploymentData = string(abi.encodePacked( + "# Deployment Addresses\n", + "CHAIN_ID=", vm.toString(deploymentResult.chainId), "\n", + "DEPLOYMENT_TIMESTAMP=", vm.toString(deploymentResult.deploymentTimestamp), "\n", + "TOKEN_PROXY=", vm.toString(deploymentResult.proxyToken), "\n", + "TOKEN_IMPLEMENTATION=", vm.toString(deploymentResult.implementationToken), "\n", + "VAULT_FACTORY=", vm.toString(deploymentResult.vaultFactory), "\n", + "STAKING_VAULT=", vm.toString(deploymentResult.stakingVault), "\n" + )); + + vm.writeFile("deployment.env", deploymentData); + console.log("Deployment addresses saved to deployment.env"); } } ``` -### Using the Universal Verification Script - -```bash -# For Blockscout verification -export CONTRACT_ADDRESS=0xYourContractAddress -export EXPLORER_TYPE=0 -forge script script/UniversalVerify.s.sol --rpc-url $RSK_TESTNET_RPC_URL - -# For Rootstock Explorer verification -export EXPLORER_TYPE=1 -forge script script/UniversalVerify.s.sol --rpc-url $RSK_TESTNET_RPC_URL -``` - -## Comparison: Blockscout vs Rootstock Explorer - -| Feature | Blockscout | Rootstock Explorer | -|---------|------------|-------------------| -| **API Key Required** | No | Optional but recommended | -| **Verification Speed** | Fast | Fast | -| **Constructor Arg Support** | Yes | Yes | -| **Proxy Contract Support** | Yes | Yes | -| **Multi-file Contract Support** | Yes | Yes | -| **Source Code Display** | Excellent | Excellent | -| **Transaction History** | Complete | Complete | -| **Rate Limiting** | Minimal | Moderate | -| **Official Support** | Community | Rootstock Team | - -## Advanced Verification Scenarios - -### Verifying Proxy Contracts - -For proxy implementations, verify both the proxy and implementation contracts: - -```bash -# Verify the implementation contract first -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - "0xImplementationAddress" \ - src/Implementation.sol:Implementation - -# Then verify the proxy contract -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - --constructor-args $(cast abi-encode "constructor(address,bytes)" "0xImplementationAddress" "0x") \ - "0xProxyAddress" \ - src/Proxy.sol:Proxy -``` - -### Verifying Contracts with External Libraries - -For contracts using external libraries, ensure all dependencies are properly linked: - -```bash -# Deploy library first (if not already deployed) -forge script script/DeployLibrary.s.sol --rpc-url $RSK_TESTNET_RPC_URL --broadcast - -# Verify library -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - "0xLibraryAddress" \ - src/MyLibrary.sol:MyLibrary - -# Verify main contract with library links -forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - --libraries src/MyLibrary.sol:MyLibrary:0xLibraryAddress \ - "0xMainContractAddress" \ - src/MainContract.sol:MainContract -``` - -## Troubleshooting Common Issues - -### Issue 1: Compilation Version Mismatch - -**Error:** `Bytecode does not match the expected one` - -**Solution:** -```bash -# Check your foundry.toml for compiler version -cat foundry.toml - -# Ensure it matches your contract pragma -grep "pragma solidity" src/YourContract.sol - -# Rebuild with specific version if needed -forge build --use 0.8.19 -``` - -### Issue 2: Constructor Arguments Encoding - -**Error:** `Invalid constructor arguments` - -**Solution:** -```bash -# Check constructor signature -forge inspect YourContract constructor - -# Test argument encoding -cast abi-encode "constructor(uint256,string,address)" 1000 "TestToken" "0x1234567890123456789012345678901234567890" - -# Verify encoding -cast abi-decode "constructor(uint256,string,address)" 0x000... -``` +### 2. Upgrade Deployment Script -### Issue 3: Multiple File Dependencies +Create `script/UpgradeToken.s.sol`: -**Error:** `Source code compilation failed` - -**Solution:** -```bash -# Ensure all imports are resolvable -forge build --verbose - -# Check for missing remappings -cat remappings.txt - -# Verify all dependencies are included -tree src/ -``` - -### Issue 4: Network Connectivity Issues - -**Error:** `Failed to submit verification request` - -**Solutions:** -- Check internet connectivity -- Verify explorer URL accessibility -- Try alternative RPC endpoints -- Check for rate limiting - -```bash -# Test explorer connectivity -curl -I https://rootstock-testnet.blockscout.com/api +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; -# Test with different RPC -export RSK_TESTNET_RPC_URL=https://public-node.testnet.rsk.co +import {Script, console} from "forge-std/Script.sol"; +import {AdvancedTokenV1} from "../src/AdvancedTokenV1.sol"; +import {AdvancedTokenV2} from "../src/AdvancedTokenV2.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +/** + * @title Token Upgrade Script + * @dev Upgrades the token implementation while preserving state + */ +contract UpgradeTokenScript is Script { + function run() external { + address proxyAddress = vm.envAddress("TOKEN_PROXY"); + require(proxyAddress != address(0), "TOKEN_PROXY not set"); + + console.log("=== Token Upgrade Process ==="); + console.log("Proxy address:", proxyAddress); + console.log("Current deployer:", msg.sender); + + vm.startBroadcast(); + + // Deploy new implementation + AdvancedTokenV2 newImplementation = new AdvancedTokenV2(); + console.log("New implementation deployed:", address(newImplementation)); + + // Upgrade proxy to new implementation + AdvancedTokenV1 proxy = AdvancedTokenV1(proxyAddress); + proxy.upgradeToAndCall(address(newImplementation), ""); + + vm.stopBroadcast(); + + console.log("Upgrade completed successfully"); + + // Verification command for new implementation + console.log("\nVerification command:"); + console.log(string(abi.encodePacked( + "forge verify-contract --chain-id ", vm.toString(block.chainid), + " --verifier-url ", _getExplorerUrl(), + " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY ", + vm.toString(address(newImplementation)), + " src/AdvancedTokenV2.sol:AdvancedTokenV2" + ))); + } + + function _getExplorerUrl() internal view returns (string memory) { + return block.chainid == 31 + ? "https://explorer.testnet.rootstock.io/api" + : "https://explorer.rootstock.io/api"; + } +} ``` -## Best Practices +## Testing Advanced Contracts -### 1. Pre-verification Checklist +### Comprehensive Test Suite -Before submitting for verification: +Create `test/AdvancedContracts.t.sol`: -```bash -# Clean build -forge clean && forge build - -# Check for warnings -forge build --verbose 2>&1 | grep -i warning +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; -# Verify contract size limits -forge build --sizes +import {Test, console} from "forge-std/Test.sol"; +import {AdvancedTokenV1} from "../src/AdvancedTokenV1.sol"; +import {VaultFactory} from "../src/VaultFactory.sol"; +import {StakingVault} from "../src/StakingVault.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -# Test deployment locally -forge script script/Deploy.s.sol --fork-url $RSK_TESTNET_RPC_URL +contract AdvancedContractsTest is Test { + AdvancedTokenV1 public token; + VaultFactory public factory; + StakingVault public vault; + + address public owner = address(0x1); + address public user1 = address(0x2); + address public user2 = address(0x3); + + uint256 public constant INITIAL_SUPPLY = 1000000e18; + uint256 public constant REWARD_RATE = 500; // 5% daily + + event Transfer(address indexed from, address indexed to, uint256 value); + event VaultCreated(address indexed vaultAddress, address indexed tokenAddress, address indexed creator, uint256 timestamp); + + function setUp() public { + vm.startPrank(owner); + + // Deploy token implementation + AdvancedTokenV1 implementation = new AdvancedTokenV1(); + + // Deploy proxy + bytes memory initData = abi.encodeWithSelector( + AdvancedTokenV1.initialize.selector, + "Test Token", + "TEST", + INITIAL_SUPPLY / 1e18, + owner + ); + ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), initData); + token = AdvancedTokenV1(address(proxy)); + + // Deploy factory + factory = new VaultFactory(owner); + + // Grant vault creator role to owner + factory.grantRole(factory.VAULT_CREATOR_ROLE(), owner); + + vm.stopPrank(); + } + + function testTokenInitialization() public { + assertEq(token.name(), "Test Token"); + assertEq(token.symbol(), "TEST"); + assertEq(token.totalSupply(), INITIAL_SUPPLY); + assertEq(token.balanceOf(owner), INITIAL_SUPPLY); + assertEq(token.owner(), owner); + } + + function testVaultCreation() public { + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + + vault = StakingVault(vaultAddress); + + assertEq(address(vault.stakingToken()), address(token)); + assertEq(vault.rewardRate(), REWARD_RATE); + assertEq(vault.owner(), owner); + + // Check factory records + (address storedVault, address storedToken, address creator, uint256 createdAt, bool isActive) = + factory.vaults(vaultAddress); + + assertEq(storedVault, vaultAddress); + assertEq(storedToken, address(token)); + assertEq(creator, owner); + assertGt(createdAt, 0); + assertTrue(isActive); + } + + function testStaking() public { + // Create vault first + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + vault = StakingVault(vaultAddress); + + // Transfer tokens to user1 + uint256 stakeAmount = 1000e18; + vm.prank(owner); + token.transfer(user1, stakeAmount); + + // User1 stakes tokens + vm.startPrank(user1); + token.approve(address(vault), stakeAmount); + vault.stake(stakeAmount); + vm.stopPrank(); + + assertEq(vault.userStaked(user1), stakeAmount); + assertEq(vault.totalStaked(), stakeAmount); + assertEq(token.balanceOf(address(vault)), stakeAmount); + } + + function testRewardCalculation() public { + // Create vault and stake + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + vault = StakingVault(vaultAddress); + + uint256 stakeAmount = 1000e18; + vm.prank(owner); + token.transfer(user1, stakeAmount); + + vm.startPrank(user1); + token.approve(address(vault), stakeAmount); + vault.stake(stakeAmount); + vm.stopPrank(); + + // Fast forward 1 day + vm.warp(block.timestamp + 86400); + + uint256 expectedReward = (stakeAmount * REWARD_RATE) / 10000; + uint256 actualReward = vault.earned(user1); + + // Allow for small rounding differences + assertApproxEqAbs(actualReward, expectedReward, 1e15); + } + + function testWithdrawal() public { + // Setup staking + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + vault = StakingVault(vaultAddress); + + uint256 stakeAmount = 1000e18; + vm.prank(owner); + token.transfer(user1, stakeAmount); + + vm.startPrank(user1); + token.approve(address(vault), stakeAmount); + vault.stake(stakeAmount); + + // Withdraw half + uint256 withdrawAmount = stakeAmount / 2; + vault.withdraw(withdrawAmount); + vm.stopPrank(); + + assertEq(vault.userStaked(user1), stakeAmount - withdrawAmount); + assertEq(vault.totalStaked(), stakeAmount - withdrawAmount); + assertEq(token.balanceOf(user1), withdrawAmount); + } + + function testUnauthorizedAccess() public { + // Test vault creation by non-authorized user + vm.prank(user1); + vm.expectRevert(); + factory.createVault(address(token), REWARD_RATE); + + // Test token minting by non-owner + vm.prank(user1); + vm.expectRevert(); + token.mint(user1, 1000e18); + } + + function testFactoryDeactivation() public { + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + + vm.prank(owner); + factory.deactivateVault(vaultAddress); + + (, , , , bool isActive) = factory.vaults(vaultAddress); + assertFalse(isActive); + } + + function testMultipleVaults() public { + // Create multiple vaults + address[] memory vaults = new address[](3); + + vm.startPrank(owner); + for (uint i = 0; i < 3; i++) { + vaults[i] = factory.createVault(address(token), REWARD_RATE + (i * 100)); + } + vm.stopPrank(); + + assertEq(factory.getTotalVaults(), 3); + + address[] memory userVaults = factory.getUserVaults(owner); + assertEq(userVaults.length, 3); + + for (uint i = 0; i < 3; i++) { + assertEq(userVaults[i], vaults[i]); + } + } + + function testFuzzStaking(uint256 amount) public { + // Bound the amount to reasonable values + amount = bound(amount, 1e18, INITIAL_SUPPLY / 2); + + vm.prank(owner); + address vaultAddress = factory.createVault(address(token), REWARD_RATE); + vault = StakingVault(vaultAddress); + + vm.prank(owner); + token.transfer(user1, amount); + + vm.startPrank(user1); + token.approve(address(vault), amount); + vault.stake(amount); + vm.stopPrank(); + + assertEq(vault.userStaked(user1), amount); + assertEq(vault.totalStaked(), amount); + } +} ``` -### 2. Verification Automation +## Integration with Verification System -Create a comprehensive verification workflow: +This advanced smart contract system integrates seamlessly with the verification workflow documented in [Verify Smart Contracts Using Foundry Script](/developers/smart-contracts/foundry/verify-smart-contracts/). Key integration points include: -```bash -#!/bin/bash -# verify.sh - Automated verification script +### 1. Automated Verification Integration -set -e +The deployment scripts generate verification commands automatically, as demonstrated in the `_generateVerificationCommands()` function. This ensures that complex multi-contract systems are properly verified. -CONTRACT_ADDRESS=$1 -CONTRACT_NAME=$2 -EXPLORER_TYPE=${3:-"blockscout"} # default to blockscout +### 2. Constructor Argument Handling -if [ -z "$CONTRACT_ADDRESS" ] || [ -z "$CONTRACT_NAME" ]; then - echo "Usage: ./verify.sh [explorer_type]" - exit 1 -fi +The advanced contracts demonstrate proper constructor argument encoding for verification: +- Simple parameters (address, uint256) +- Complex parameters (initialization data for proxy contracts) +- Multiple parameter combinations -echo "Verifying $CONTRACT_NAME at $CONTRACT_ADDRESS using $EXPLORER_TYPE" +### 3. Proxy Contract Verification -if [ "$EXPLORER_TYPE" = "blockscout" ]; then - forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - "$CONTRACT_ADDRESS" \ - "src/$CONTRACT_NAME.sol:$CONTRACT_NAME" -else - forge verify-contract \ - --chain-id 31 \ - --verifier-url https://explorer.testnet.rootstock.io/api \ - --etherscan-api-key "$ROOTSTOCK_EXPLORER_API_KEY" \ - "$CONTRACT_ADDRESS" \ - "src/$CONTRACT_NAME.sol:$CONTRACT_NAME" -fi +Upgradeable contracts require special handling for verification. The proxy contract verification process is documented in the [verification guide](/developers/smart-contracts/foundry/verify-smart-contracts/) with specific examples for UUPS proxies. -echo "Verification submitted successfully!" -``` - -### 3. Environment Management - -Use environment-specific configuration: - -```toml -# foundry.toml -[profile.default] -src = "src" -out = "out" -libs = ["lib"] -solc_version = "0.8.19" -optimizer = true -optimizer_runs = 200 - -[profile.testnet] -chain_id = 31 -eth_rpc_url = "${RSK_TESTNET_RPC_URL}" - -[profile.mainnet] -chain_id = 30 -eth_rpc_url = "${RSK_MAINNET_RPC_URL}" -``` +## Production Deployment Checklist -## Verification Status Monitoring +Before deploying to Rootstock mainnet, complete this comprehensive checklist: -### Check Verification Status +### 1. Security Audit +- [ ] Professional security audit completed +- [ ] All critical and high-severity issues resolved +- [ ] Access control mechanisms verified +- [ ] Reentrancy protection confirmed -After submitting verification, monitor the status: +### 2. Testing Coverage +- [ ] Unit tests coverage > 95% +- [ ] Integration tests for all contract interactions +- [ ] Fuzz testing for critical functions +- [ ] Gas optimization analysis completed -**Blockscout:** -- Visit: https://rootstock-testnet.blockscout.com/address/[CONTRACT_ADDRESS] -- Look for the "Contract" tab -- Check verification status and source code display +### 3. Documentation +- [ ] Contract interfaces documented +- [ ] Deployment process documented +- [ ] Upgrade procedures defined (for upgradeable contracts) +- [ ] Emergency procedures established -**Rootstock Explorer:** -- Visit: https://explorer.testnet.rootstock.io/address/[CONTRACT_ADDRESS] -- Navigate to the "Contract" section -- Verify source code is visible and properly formatted +### 4. Configuration Validation +- [ ] Constructor parameters verified +- [ ] Access control roles properly assigned +- [ ] Network-specific configurations confirmed +- [ ] API keys and environment variables secured -### Automated Status Checking +### 5. Deployment Process +- [ ] Testnet deployment successful +- [ ] Contract verification completed +- [ ] Interaction testing performed +- [ ] Monitoring systems configured -Create a script to monitor verification status: +### 6. Post-Deployment +- [ ] Contract ownership transferred (if applicable) +- [ ] Community notification completed +- [ ] Documentation published +- [ ] Monitoring alerts configured -```bash -#!/bin/bash -# check_verification.sh +## Additional Resources -CONTRACT_ADDRESS=$1 -EXPLORER_TYPE=${2:-"blockscout"} +### Smart Contract Development +- **OpenZeppelin Contracts**: [OpenZeppelin Documentation](https://docs.openzeppelin.com/contracts/) +- **Solidity Best Practices**: [Solidity Documentation](https://docs.soliditylang.org/en/latest/security-considerations.html) +- **Gas Optimization**: [Foundry Gas Optimization Guide](https://book.getfoundry.sh/tutorials/solidity-scripting) -if [ "$EXPLORER_TYPE" = "blockscout" ]; then - URL="https://rootstock-testnet.blockscout.com/api/v2/smart-contracts/$CONTRACT_ADDRESS" -else - URL="https://explorer.testnet.rootstock.io/api/contracts/$CONTRACT_ADDRESS/source" -fi +### Testing and Security +- **Foundry Testing**: [Test Smart Contracts](/developers/smart-contracts/foundry/test-smart-contracts/) +- **Security Tools**: [Slither](https://github.com/crytic/slither), [Mythril](https://mythril.ai/) +- **Audit Resources**: [Smart Contract Security Best Practices](https://consensys.github.io/smart-contract-best-practices/) -echo "Checking verification status for $CONTRACT_ADDRESS" -curl -s "$URL" | jq '.verified_at // .source_code // "Not verified yet"' -``` - -## Integration with Development Workflow - -### GitHub Actions Integration - -```yaml -name: Deploy and Verify Contracts -on: - push: - branches: [main] - paths: ['contracts/**', 'script/**'] - -jobs: - deploy-and-verify: - runs-on: ubuntu-latest - strategy: - matrix: - explorer: [blockscout, rootstock] - - steps: - - uses: actions/checkout@v3 - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Build contracts - run: forge build --sizes - - - name: Run tests - run: forge test -vvv - - - name: Deploy contracts - run: | - forge script script/Deploy.s.sol \ - --rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }} \ - --private-key ${{ secrets.PRIVATE_KEY }} \ - --broadcast - - - name: Verify with Blockscout - if: matrix.explorer == 'blockscout' - run: | - forge verify-contract \ - --chain-id 31 \ - --verifier blockscout \ - --verifier-url https://rootstock-testnet.blockscout.com/api \ - ${{ env.CONTRACT_ADDRESS }} \ - src/MyContract.sol:MyContract - - - name: Verify with Rootstock Explorer - if: matrix.explorer == 'rootstock' - run: | - forge verify-contract \ - --chain-id 31 \ - --verifier-url https://explorer.testnet.rootstock.io/api \ - --etherscan-api-key ${{ secrets.ROOTSTOCK_EXPLORER_API_KEY }} \ - ${{ env.CONTRACT_ADDRESS }} \ - src/MyContract.sol:MyContract -``` - -## Resources - -- [Foundry Book - Contract Verification](https://book.getfoundry.sh/reference/forge/forge-verify-contract) -- [Deploy Smart Contracts with Foundry](/developers/smart-contracts/foundry/deploy-smart-contracts/) -- [Rootstock Testnet Blockscout](https://rootstock-testnet.blockscout.com/) -- [Rootstock Mainnet Blockscout](https://rootstock.blockscout.com/) -- [Rootstock Testnet Explorer](https://explorer.testnet.rootstock.io/) -- [Rootstock Mainnet Explorer](https://explorer.rootstock.io/) -- [Verify a Smart Contract using the Hardhat Verification Plugin](/developers/smart-contracts/verify-smart-contracts/hardhat-verify-plugin/) +### Rootstock Specific +- **Deployment Guide**: [Deploy Smart Contracts](/developers/smart-contracts/foundry/deploy-smart-contracts/) +- **Verification Guide**: [Verify Smart Contracts Using Foundry Script](/developers/smart-contracts/foundry/verify-smart-contracts/) +- **Troubleshooting**: [Foundry Troubleshooting](/developers/smart-contracts/foundry/troubleshooting/) +- **Rootstock Explorer**: [Testnet](https://explorer.testnet.rootstock.io/) | [Mainnet](https://explorer.rootstock.io/) ## Next Steps -After successful verification: - -1. **Test contract interactions** through the explorer interfaces -2. **Set up monitoring** for your verified contracts -3. **Document verification procedures** for your team -4. **Consider proxy pattern verification** for upgradeable contracts -5. **Implement automated verification** in your CI/CD pipeline - -:::info[Credit] +After mastering advanced smart contract development: -This enhanced guide provides comprehensive coverage of smart contract verification on Rootstock using Foundry with multiple explorer options. This documentation was created to address GitHub issue #308 and provides developers with flexible verification options for the Rootstock ecosystem. +1. **Explore DeFi Protocols**: Study existing DeFi implementations on Rootstock +2. **Contribute to Ecosystem**: Consider contributing to open-source Rootstock tools +3. **Build Complex Applications**: Develop multi-contract systems with advanced features +4. **Optimize for Gas**: Implement gas optimization strategies specific to Rootstock +5. **Community Engagement**: Share your contracts and contribute to developer discussions -::: +**Important**: Always prioritize security and proper testing in production environments. Consider professional audits for contracts handling significant value. diff --git a/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md b/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md index 132f0473..4bdac73c 100644 --- a/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md +++ b/docs/02-developers/05-smart-contracts/05-foundry/verify-smart-contracts.md @@ -1,60 +1,92 @@ +# Verify Smart Contracts Using Foundry Script on Rootstock + --- -sidebar_label: Verify Smart Contract -sidebar_position: 106 -title: Verify Smart Contracts Using Foundry Script on Rootstock -description: "Learn how to verify smart contracts on Rootstock using Foundry scripts and the Rootstock Explorer" -tags: [guides, developers, smart contracts, rsk, rootstock, foundry, verification, explorer] +**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] --- -Smart contract verification on Rootstock using Foundry provides developers with a streamlined approach to make their deployed contracts transparent and trustworthy. This guide covers both command-line verification and automated script-based verification using the Rootstock Explorer API. - ## Overview -Contract verification involves uploading and matching the contract's source code with the bytecode deployed on the blockchain. Once verified, users can read the contract's source code, understand its functionality, and interact with it more confidently through block explorers. +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 starting the verification process, ensure you have: +Before proceeding with verification, ensure you have completed the foundational setup: + +- **Foundry Installation & Configuration**: Follow the complete setup guide at [Configure Foundry for Rootstock](/developers/smart-contracts/foundry/configure-foundry-rootstock/) +- **Deployed Smart Contract**: Your contract must be successfully deployed on Rootstock. See [Deploy Smart Contracts](/developers/smart-contracts/foundry/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 -- **Deployed smart contract** on Rootstock Testnet or Mainnet -- **Contract source code** and constructor arguments (if any) -- **Foundry installed** and configured for Rootstock -- **Rootstock Explorer API access** (optional but recommended) +> **Important**: This guide assumes familiarity with Foundry basics. For foundational knowledge, review the [Foundry Project Creation](/developers/smart-contracts/foundry/create-foundry-project/) and [Smart Contract Development](/developers/smart-contracts/foundry/smart-contracts/) guides. -## Method 1: Using forge verify-contract Command +## Environment Configuration -### Environment Configuration +### Required Environment Variables -First, set up your environment variables in a `.env` file: +Create or update your `.env` file with the following variables: ```bash -# .env file setup -ROOTSTOCK_EXPLORER_API_KEY=your_api_key_here +# Network Configuration RSK_TESTNET_RPC_URL=https://public-node.testnet.rsk.co RSK_MAINNET_RPC_URL=https://public-node.rsk.co -PRIVATE_KEY=your_private_key_here + +# 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 ``` -:::warning[API Key Security] -Never commit your private keys or API keys to version control. Always use environment variables or secure key management systems. -::: +### Foundry Configuration + +Ensure your `foundry.toml` includes proper verification settings: -### Basic Verification Commands +```toml +[profile.default] +src = "src" +out = "out" +libs = ["lib"] +solc_version = "0.8.24" +optimizer = true +optimizer_runs = 200 +evm_version = "london" -**For Rootstock Testnet:** +[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: ```bash +# Rootstock Testnet forge verify-contract \ --chain-id 31 \ --verifier-url https://explorer.testnet.rootstock.io/api \ --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY \ \ src/YourContract.sol:YourContract -``` -**For Rootstock Mainnet:** - -```bash +# Rootstock Mainnet forge verify-contract \ --chain-id 30 \ --verifier-url https://explorer.rootstock.io/api \ @@ -63,42 +95,69 @@ forge verify-contract \ src/YourContract.sol:YourContract ``` -### Verification with Constructor Arguments +### Advanced Verification with Constructor Arguments -If your contract has constructor arguments, you need to encode them properly: +For contracts with constructor parameters, proper encoding is crucial: + +#### Single Parameter Examples ```bash -# For a contract with constructor arguments +# 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,string,address)" 1000 "MyToken" "0x1234567890123456789012345678901234567890") \ + --constructor-args $(cast abi-encode "constructor(uint256)" 1000) \ \ - src/YourContract.sol:YourContract -``` + 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") \ + \ + src/TokenContract.sol:TokenContract -### Example: Verifying a Simple Counter Contract +# 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") \ + \ + src/TokenContract.sol:TokenContract +``` -Let's verify a simple counter contract deployed on Rootstock Testnet: +#### Multiple Parameters ```bash -# Assuming your contract is at address 0x1234567890123456789012345678901234567890 +# 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 \ - 0x1234567890123456789012345678901234567890 \ - src/Counter.sol:Counter + --constructor-args $(cast abi-encode "constructor(uint256,string,address)" 1000 "MyToken" "0x742d35Cc6634C0532925a3b8D85ac5b5c3b42F96") \ + \ + src/ComplexContract.sol:ComplexContract ``` -## Method 2: Automated Verification Script +#### Array and Struct Parameters + +```bash +# 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\")" +``` -For more complex verification workflows, create a dedicated Foundry script that automates the verification process. +## Method 2: Automated Verification Scripts -### Create Verification Script +### Basic Verification Script -Create a new file `script/Verify.s.sol`: +Create `script/Verify.s.sol`: ```solidity // SPDX-License-Identifier: UNLICENSED @@ -108,68 +167,131 @@ import {Script, console} from "forge-std/Script.sol"; contract VerifyScript is Script { function run() external { - // Read deployment configuration - uint256 chainId = block.chainid; + // 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("Verifying contract on chain ID:", chainId); - console.log("Contract address:", contractAddress); + console.log("=== Contract Verification Details ==="); + console.log("Contract Address:", contractAddress); + console.log("Chain ID:", chainId); - // Determine the correct explorer URL based on chain ID + // Determine explorer URL and network name string memory explorerUrl; + string memory networkName; + if (chainId == 31) { explorerUrl = "https://explorer.testnet.rootstock.io/api"; - console.log("Using Rootstock Testnet Explorer"); + networkName = "Rootstock Testnet"; } else if (chainId == 30) { explorerUrl = "https://explorer.rootstock.io/api"; - console.log("Using Rootstock Mainnet Explorer"); + networkName = "Rootstock Mainnet"; } else { - revert("Unsupported chain ID"); + revert("Unsupported chain ID. Use 30 (Mainnet) or 31 (Testnet)"); } - // If contract has constructor arguments, encode them here - // Example for a contract with uint256 constructor argument - uint256 constructorArg = vm.envUint("CONSTRUCTOR_ARG"); + 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(""); - console.log("Constructor argument:", constructorArg); - console.log("Run the following command to verify:"); + // 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 --chain-id ", - vm.toString(chainId), - " --verifier-url ", - explorerUrl, - " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY", - " --constructor-args $(cast abi-encode \"constructor(uint256)\" ", - vm.toString(constructorArg), - ") ", - vm.toString(contractAddress), - " src/YourContract.sol:YourContract" + "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" )) ); } } ``` -### Run the Verification Script - -Execute the script to get the verification command: +### Execute the Verification Script ```bash -# Set environment variables +# Set required environment variables export CONTRACT_ADDRESS=0x1234567890123456789012345678901234567890 -export CONSTRUCTOR_ARG=1000 +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 Verification Script with Multiple Contracts +### Advanced Multi-Contract Verification Script -For projects with multiple contracts, create a batch verification script: +Create `script/BatchVerify.s.sol` for projects with multiple contracts: ```solidity -// script/BatchVerify.s.sol // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; @@ -180,72 +302,89 @@ contract BatchVerifyScript is Script { address contractAddress; string contractPath; string contractName; - bytes constructorArgs; + 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"; + 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_ADDRESS"), - contractPath: "src/Token.sol:Token", + contractAddress: vm.envAddress("TOKEN_CONTRACT"), + contractPath: "src/Token.sol", contractName: "Token", - constructorArgs: abi.encode(1000000, "MyToken", "MTK") + constructorSignature: "constructor(uint256,string)", + constructorArgs: "1000 \"MyToken\"" }); contracts[1] = ContractInfo({ - contractAddress: vm.envAddress("STAKING_CONTRACT_ADDRESS"), - contractPath: "src/Staking.sol:Staking", - contractName: "Staking", - constructorArgs: abi.encode(vm.envAddress("TOKEN_CONTRACT_ADDRESS")) + 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("GOVERNANCE_CONTRACT_ADDRESS"), - contractPath: "src/Governance.sol:Governance", - contractName: "Governance", - constructorArgs: abi.encode(vm.envAddress("TOKEN_CONTRACT_ADDRESS"), vm.envAddress("STAKING_CONTRACT_ADDRESS")) + contractAddress: vm.envAddress("FACTORY_CONTRACT"), + contractPath: "src/Factory.sol", + contractName: "Factory", + constructorSignature: "", + constructorArgs: "" }); - // Generate verification commands for all contracts - for (uint256 i = 0; i < contracts.length; i++) { - console.log("\n=== Verifying", contracts[i].contractName, "==="); - console.log("Address:", contracts[i].contractAddress); - console.log("Command:"); - console.log( - string(abi.encodePacked( - "forge verify-contract --chain-id ", - vm.toString(chainId), - " --verifier-url ", - explorerUrl, - " --etherscan-api-key $ROOTSTOCK_EXPLORER_API_KEY", - " --constructor-args 0x", - _bytesToHex(contracts[i].constructorArgs), - " ", - vm.toString(contracts[i].contractAddress), - " ", - contracts[i].contractPath - )) - ); + 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 _bytesToHex(bytes memory data) internal pure returns (string memory) { - bytes memory alphabet = "0123456789abcdef"; - bytes memory str = new bytes(2 + data.length * 2); - str[0] = "0"; - str[1] = "x"; - for (uint256 i = 0; i < data.length; i++) { - str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; - str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; + 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)); } - return string(str); + + string memory finalCommand = string(abi.encodePacked( + baseCommand, + " \\", + "\n ", vm.toString(contractInfo.contractAddress), " \\", + "\n ", contractInfo.contractPath, ":", contractInfo.contractName + )); + + console.log(finalCommand); } } ``` @@ -254,118 +393,167 @@ contract BatchVerifyScript is Script { ### Common Verification Issues -#### 1. Mismatched Bytecode +#### 1. Bytecode Mismatch -**Error Message:** -``` -Error: Contract verification failed: Bytecode does not match -``` +**Error**: "Bytecode does not match" -**Solutions:** -- Ensure you're using the exact same compiler version and settings -- Check that all imported libraries and dependencies are identical -- Verify that constructor arguments are correctly encoded -- Confirm you're verifying the correct contract file and name +**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 -#### 2. Incorrect Constructor Arguments +```bash +# Check compiler version +forge --version -**Error Message:** -``` -Error: Invalid constructor arguments +# Verify compilation matches deployment +forge build --force ``` -**Solutions:** -```bash -# Check constructor signature in your contract -grep -A 5 "constructor" src/YourContract.sol +#### 2. Constructor Argument Encoding -# Encode arguments properly -cast abi-encode "constructor(uint256,string,address)" 1000 "MyToken" "0x1234567890123456789012345678901234567890" +**Error**: "Constructor arguments verification failed" -# Verify encoding with cast -cast abi-decode "constructor(uint256,string,address)" 0x00000000000000000000000000000000000000000000000000000000000003e8... +**Solutions**: +- Double-check argument types and order +- Use proper encoding for complex types +- Verify argument values match deployment + +```bash +# Debug constructor encoding +cast abi-encode "constructor(uint256,string)" 1000 "TestToken" --verbose ``` #### 3. Network Configuration Issues -**Error Message:** -``` -Error: Failed to connect to verification service -``` +**Error**: "Invalid chain ID" or "Network not supported" -**Solutions:** -- Verify the correct chain ID and RPC URL -- Check internet connectivity -- Ensure the explorer API is accessible -- Try using alternative RPC endpoints +**Solutions**: +- Verify correct chain ID (30 for mainnet, 31 for testnet) +- Check RPC URL configuration +- Confirm API key validity -#### 4. API Key Problems +#### 4. API Rate Limits -**Error Message:** -``` -Error: API key required or invalid +**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](/developers/smart-contracts/verify-smart-contracts/foundry-blockscout/) + +### Verification Status Monitoring + +Create a status checking script `script/CheckVerification.s.sol`: + +```solidity +// 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"); + } +} ``` -**Solutions:** -- Obtain a valid API key from the Rootstock Explorer -- Ensure the API key is properly set in environment variables -- Check API key permissions and rate limits +## Integration with CI/CD -### Verification Status Check +### GitHub Actions Workflow -After initiating verification, you can check the status through: +Create `.github/workflows/verify-contracts.yml`: -**Rootstock Testnet Explorer:** -- Navigate to: https://explorer.testnet.rootstock.io/ -- Search for your contract address -- Check the "Contract" tab for verification status +```yaml +name: Deploy and Verify Contracts -**Rootstock Mainnet Explorer:** -- Navigate to: https://explorer.rootstock.io/ -- Search for your contract address -- Check the "Contract" tab for verification status +on: + push: + branches: [main] + pull_request: + branches: [main] -### Advanced Troubleshooting +env: + FOUNDRY_PROFILE: ci -#### Verify Locally First +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 -Before submitting for verification, ensure your contract compiles correctly: + - name: Install dependencies + run: forge install -```bash -# Clean and rebuild -forge clean -forge build + - name: Compile contracts + run: forge build --sizes -# Check for compilation warnings -forge build --verbose + - name: Run tests + run: forge test -vvv -# Verify contract size -forge build --sizes + - 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 }} ``` -#### Check Bytecode Match +## Best Practices for Production -Compare local and deployed bytecode: +### 1. Comprehensive Testing Before Verification ```bash -# Get deployed bytecode -cast code --rpc-url $RSK_TESTNET_RPC_URL - -# Compare with local compilation -forge inspect YourContract bytecode +# Complete testing pipeline +forge test --gas-report +forge coverage +forge snapshot ``` -## Best Practices +### 2. Consistent Development Environment -### 1. Consistent Development Environment +- Lock compiler versions in `foundry.toml` +- Document all optimization settings +- Use identical configurations across environments -- Use the same Solidity compiler version across all environments -- Lock dependency versions in your `foundry.toml` -- Document compiler settings and optimization parameters +### 3. Automated Verification Pipeline -### 2. Automated Verification Pipeline - -Create a deployment script that includes verification: +Integrate verification into deployment scripts: ```solidity // script/DeployAndVerify.s.sol @@ -374,73 +562,46 @@ contract DeployAndVerifyScript is Script { vm.startBroadcast(); // Deploy contract - YourContract contract = new YourContract(constructorArgs); + YourContract deployedContract = new YourContract(constructorArgs); vm.stopBroadcast(); - // Log deployment info - console.log("Contract deployed at:", address(contract)); - console.log("Verification 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(contract), "src/YourContract.sol:YourContract"); + // 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"); } } ``` -### 3. Documentation and Logging - -- Keep detailed logs of deployment and verification commands -- Document constructor arguments and their meanings -- Maintain a verification checklist for complex projects - -## Integration with CI/CD - -### GitHub Actions Example +### 4. Security Considerations -```yaml -name: Deploy and Verify -on: - push: - branches: [main] - -jobs: - deploy-and-verify: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Build contracts - run: forge build - - - name: Deploy to testnet - run: | - forge script script/Deploy.s.sol --rpc-url ${{ secrets.RSK_TESTNET_RPC_URL }} --private-key ${{ secrets.PRIVATE_KEY }} --broadcast - - - name: Verify contracts - run: | - forge verify-contract --chain-id 31 --verifier-url https://explorer.testnet.rootstock.io/api --etherscan-api-key ${{ secrets.ROOTSTOCK_EXPLORER_API_KEY }} $CONTRACT_ADDRESS src/YourContract.sol:YourContract -``` +- Never commit private keys or API keys +- Use environment variables for sensitive data +- Implement proper access controls for verification scripts +- Regular API key rotation -## Resources +## Additional Resources -- [Deploy Smart Contracts](/developers/smart-contracts/foundry/deploy-smart-contracts/) -- [Foundry Configuration Guide](/developers/smart-contracts/foundry/configure-foundry-rootstock/) -- [Rootstock Testnet Explorer](https://explorer.testnet.rootstock.io/) -- [Rootstock Mainnet Explorer](https://explorer.rootstock.io/) -- [Foundry Documentation](https://book.getfoundry.sh/) -- [Verify Smart Contract using Foundry and Blockscout](/developers/smart-contracts/verify-smart-contracts/foundry-blockscout/) +- **Foundry Configuration**: [Configure Foundry for Rootstock](/developers/smart-contracts/foundry/configure-foundry-rootstock/) +- **Contract Deployment**: [Deploy Smart Contracts](/developers/smart-contracts/foundry/deploy-smart-contracts/) +- **Alternative Verification**: [Verify with Foundry and Blockscout](/developers/smart-contracts/verify-smart-contracts/foundry-blockscout/) +- **Hardhat Verification**: [Verify Smart Contract using Hardhat](/developers/smart-contracts/verify-smart-contracts/hardhat-verify-plugin/) +- **Foundry Official Documentation**: [Foundry Book](https://book.getfoundry.sh/) +- **Rootstock Testnet Explorer**: [https://explorer.testnet.rootstock.io/](https://explorer.testnet.rootstock.io/) +- **Rootstock Mainnet Explorer**: [https://explorer.rootstock.io/](https://explorer.rootstock.io/) ## Next Steps -After successfully verifying your smart contracts, consider: +After successfully verifying your smart contracts: -1. **Setting up monitoring** for your verified contracts -2. **Implementing automated testing** for verification workflows -3. **Creating documentation** for your verified contract interfaces -4. **Exploring advanced verification features** like proxy contract verification +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 -:::info[Credit] -This guide provides comprehensive coverage of smart contract verification on Rootstock using Foundry. For additional support or questions, join the [Rootstock Discord community](http://discord.gg/rootstock). -::: +**Important**: Always test verification on Rootstock Testnet before attempting mainnet verification to avoid API key rate limits and ensure proper configuration.