Diff deployed EVM-compatible smart contract sourcecode and bytecode against the specified GitHub repo commit.
Key features:
- retrieve and diff sources from the GitHub repo against the queried ones from a blockscan service (e.g. Etherscan)
- compare the bytecode compiled and deployed on the forked network locally against remote (see section 'bytecode_comparison' in
./config_samples/lido_dao_sepolia_config.jsonas an example) - enabled by default - cache sources from blockchain explorers (option
--cache-explorer) and GitHub files (option--cache-github) to avoid re-fetching on repeated runs - preprocess solidity sourcecode by means of prettier solidity plugin before comparing the sources (option
--prettify) if needed. - preprocess imports to flat paths for Brownie compatibility (option
--support-brownie) - skip binary comparison if needed (option
--skip-binary-comparison) - provide own Hardhat config as optional argument
pipx install git+https://github.com/lidofinance/diffyscanIf deployed bytecode binary comparison or prettier source preprocessing are needed:
npm installSet your Etherscan token to fetch verified source code,
export ETHERSCAN_EXPLORER_TOKEN=<your-etherscan-token>Set your Github token to query API without strict rate limiting,
export GITHUB_API_TOKEN=<your-github-token>Set remote RPC URL to validate contract bytecode at remote rpc node,
export REMOTE_RPC_URL =<remote-rpc-url>Set local RPC URL to check immutables against the local deployment and provided constructor arguments. If not set, it defaults to http://127.0.0.1:7545.
export LOCAL_RPC_URL=<local-rpc-url>Start script with one of the examples provided (or entire folder of configs)
diffyscan config_samples/lido_dao_sepolia_config.jsonAlternatively, create a new config file named config.json near the diffyscan.py,
{
"contracts": {
"0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8": "OssifiableProxy",
"0xDba5Ad530425bb1b14EECD76F1b4a517780de537": "LidoLocator"
},
"explorer_hostname": "api.etherscan.io",
"explorer_chain_id": 17000,
"explorer_token_env_var": "ETHERSCAN_EXPLORER_TOKEN",
"github_repo": {
"url": "https://github.com/lidofinance/lido-dao",
"commit": "cadffa46a2b8ed6cfa1127fca2468bae1a82d6bf",
"relative_root": ""
},
"dependencies": {
"@openzeppelin/contracts-v4.4": {
"url": "https://github.com/OpenZeppelin/openzeppelin-contracts",
"commit": "6bd6b76d1156e20e45d1016f355d154141c7e5b9",
"relative_root": "contracts"
}
},
"fail_on_bytecode_comparison_error": true,
"bytecode_comparison": {
"constructor_calldata": {
"0x28FAB2059C713A7F9D8c86Db49f9bb0e96Af1ef8": "000000000000000000000000ab89ed3d8f31bcf8bb7de53f02084d1e6f043d34000000000000000000000000e92329ec7ddb11d25e25b3c21eebf11f15eb325d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"
},
"constructor_args": {
"0xDba5Ad530425bb1b14EECD76F1b4a517780de537": [
[
"0x4E97A3972ce8511D87F334dA17a2C332542a5246",
"0x045dd46212A178428c088573A7d102B9d89a022A",
"0xE73a3602b99f1f913e72F8bdcBC235e206794Ac8",
"0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019",
"0x3F1c547b21f65e10480dE3ad8E19fAAC46C95034",
"0xF0d576c7d934bBeCc68FE15F1c5DAF98ea2B78bb",
"0x072f72BE3AcFE2c52715829F2CD9061A6C8fF019",
"0x4E46BD7147ccf666E1d73A3A456fC7a68de82eCA",
"0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229",
"0xE92329EC7ddB11D25e25b3c21eeBf11f15eB325d",
"0xffDDF7025410412deaa05E3E1cE68FE53208afcb",
"0xc7cc160b58F8Bb0baC94b80847E2CF2800565C50",
"0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9",
"0xC01fC1F2787687Bc656EAc0356ba9Db6e6b7afb7"
]
]
}
}
}then create a new Hardhat config file named hardhat_config.ts near the diffyscan.py
import type { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: "0.8.25",
networks: {
hardhat: {
type: "edr-simulated",
chainId: 560048,
blockGasLimit: 92000000,
hardfork: "prague",
},
},
};
export default config;Note: Hardhat config file is needed to avoid standard config generation routine to be launched.
See also: https://hardhat.org/hardhat-runner/docs/config#configuration
Start the script
diffyscan /path/to/config.json --hardhat-path /path/to/hardhat_config.tsTo skip binary comparison (which is enabled by default):
diffyscan /path/to/config.json --hardhat-path /path/to/hardhat_config.ts --skip-binary-comparisonNote: Brownie verification tooling might rewrite the imports in the source submission. It transforms relative paths to imported contracts into flat paths ('./folder/contract.sol' -> 'contract.sol'), which makes Diffyscan unable to find a contract for verification.
For contracts whose sources were verified by brownie tooling:
diffyscan /path/to/config.json --hardhat-path /path/to/hardhat_config.ts --support-brownieDiffyscan supports two types of caching to speed up repeated runs and reduce API rate limiting:
Cache contract sources from blockchain explorers (Etherscan, Blockscout, etc.):
diffyscan config_samples/lido_dao_sepolia_config.json --cache-explorerExplorer sources are cached in .diffyscan_cache/ with unique identifiers based on chain ID and contract address (e.g., 1_0xcontractaddress.json).
Cache files retrieved from GitHub repositories:
diffyscan config_samples/lido_dao_sepolia_config.json --cache-githubGitHub files are cached in .diffyscan_cache/github/ with SHA256 hash identifiers based on repository, commit, and file path.
For maximum performance, enable both caches:
diffyscan config_samples/lido_dao_sepolia_config.json --cache-explorer --cache-githubBenefits:
- Significantly faster repeated runs (no API calls)
- API rate limit friendly for both Etherscan and GitHub
- Works offline after initial fetch
- Useful for repeated testing and development
Cache management:
# Clear all caches
rm -rf .diffyscan_cache/
# Clear only explorer cache
rm -rf .diffyscan_cache/*.json
# Clear only GitHub cache
rm -rf .diffyscan_cache/github/
# View cached files
ls -la .diffyscan_cache/ℹ️ See more config examples inside the config_samples dir.
This project was developed using these dependencies with their exact versions listed below:
- Python 3.12
- Poetry 1.8
- if deployed bytecode binary comparison or prettier source preprocessing are needed:
- npm
Other versions may work as well but were not tested at all.
- Install Poetry
Use the following command to install poetry:
pip install --user poetry~=1.8alternatively, you could proceed with pipx:
pipx install poetry~=1.8- Activate poetry virtual environment,
poetry shell- Install poetry-dynamic-versioning
- In most cases:
poetry self add "poetry-dynamic-versioning[plugin]" - If you installed Poetry with Pipx:
pipx inject poetry "poetry-dynamic-versioning[plugin]"
- Install Python dependencies
poetry install- If deployed bytecode binary comparison or prettier source preprocessing are needed:
npm install