-
Notifications
You must be signed in to change notification settings - Fork 0
Usage
The detector package in SolGo serves as a utility structure that offers functionalities to detect and analyze Solidity source code. It's designed to encapsulate various components such as the context, sources, ABI builder, and solc compiler selector, providing a comprehensive toolset for Solidity analysis.
In near future, contract bytecode verification, security vulnerabilities, control flow graphs, bytecode decompiler to solidity code and other packages will be built and incorporated into this package.
It's equipped with:
- GetContext: Context associated with the Detector.
- GetSources: Solidity source files associated with the Detector.
- GetABI: Provides access to the ABI builder instance.
- GetIR: Provides access to the intermediate representation (IR) builder instance.
- GetAST: Provides access to the abstract syntax tree (AST) builder instance.
- GetParser: Provides the parser associated with the Detector.
- GetSolc: Returns the solc compiler selector.
- GetOpcodes: Decompiles the bytecode of the contract, transaction, or log to EVM opcodes from a provided byte array.
-
GetOpcodesFromHex: Similar to
GetOpcodes
, but accepts a hexadecimal string as input.
Each of these methods offers a unique functionality, allowing users to delve deep into the intricacies of Solidity source code. Whether you're looking to generate an AST, decompile opcodes, or select a specific version of the solc compiler, the Detector
provides the necessary tools to achieve your goals.
Note: By executing
NewDetectorFromSources()
, the provided Solidity code will NOT be automatically parsed. This ensures that the source code is ready for parsing when needed instead of at time of creation of new detector.
Before you start, ensure you've set up the required environment variable for the GitHub personal access token:
export SOLC_SWITCH_GITHUB_TOKEN="{your_github_token_here}"
Replace {your_github_token_here} with your actual GitHub personal access token. If you don't have one, you can create it by following the instructions here.
It is used to fetch the latest Solidity compiler releases from the official Solidity GitHub repository. If you don't set it up, you'll get an rate limit error quite quickly.
To create a new instance of Detector
, you can use the NewDetectorFromSources
function. This function initializes a new Detector
instance using the provided sources, setting up the ABI builder and solc compiler selector.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
config, err := solc.NewDefaultConfig()
if err != nil {
zap.L().Error("Failed to construct solc config", zap.Error(err))
return
}
usr, err := user.Current()
if err != nil {
zap.L().Error("Failed to get current user", zap.Error(err))
return
}
// Make sure that {HOME}/.solc/releases exists prior running this example.
releasesPath := filepath.Join(usr.HomeDir, ".solc", "releases")
if err = config.SetReleasesPath(releasesPath); err != nil {
zap.L().Error("Failed to set releases path", zap.Error(err))
return
}
compiler, err := solc.New(ctx, config)
if err != nil {
zap.L().Error("Failed to construct solc compiler", zap.Error(err))
return
}
// Ensure to sync the solc releases prior to compiling or doing literally anything else
// This is a blocking call and will take a while to complete if there are no sources.
// It can be executed 4 times a day at most. Otherwise it won't allow you to sync and instead return
// latest cache it has. This is to prevent abuse of GitHub API and to ensure that performance of solgo is not
// affected as it. Best to place as a goroutine call and execute it few times a day in the background.
if err := compiler.Sync(); err != nil {
zap.L().Error("Failed to sync solc releases", zap.Error(err))
return
}
sources := &solgo.Sources{
SourceUnits: []*solgo.SourceUnit{
{
// Ensure the name matches the contract name. This is crucial!
Name: "MyToken",
// Ensure the name in the path matches the contract name. This is crucial!
Path: "MyToken.sol",
Content: `// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MyToken{}`,
},
},
// Ensure the name matches the entry contract name. This is crucial!
EntrySourceUnitName: "MyToken",
LocalSourcesPath: utils.GetLocalSourcesPath(),
}
detector, err := NewDetectorFromSources(ctx, compiler, sources)
if err != nil {
zap.L().Error("failure to construct detector", zap.Error(err))
return
}
Let's say you have the source code for an ERC20 token and you want to analyze its functionalities using SolGo's detector
. However, you do not have in your possession openzeppelin contracts and you are lazy to search for them.
To initialize the detector with the ERC20 source code, you can use the following structure:
package main
import (
"context"
"os/user"
"path/filepath"
"time"
"github.com/0x19/solc-switch"
"github.com/unpackdev/solgo"
"github.com/unpackdev/solgo/detector"
"github.com/unpackdev/solgo/utils"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
currentTick := time.Now()
defer func() {
zap.S().Infof("Total time taken: %v", time.Since(currentTick))
}()
// Logger setup
zapConfig := zap.NewDevelopmentConfig()
zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
logger, err := zapConfig.Build()
if err != nil {
panic(err)
}
zap.ReplaceGlobals(logger)
// Define the Solidity source code for the MyToken contract
sources := &solgo.Sources{
SourceUnits: []*solgo.SourceUnit{
{
// Ensure the name matches the contract name. This is crucial!
Name: "MyToken",
// Ensure the name in the path matches the contract name. This is crucial!
Path: "MyToken.sol",
Content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply);
}
}`,
},
},
// Ensure the name matches the base contract name. This is crucial!
EntrySourceUnitName: "MyToken",
// Will not show in response full path to the zeppelin files.
MaskLocalSourcesPath: true,
// Path where additional third party such as openzeppelin are
LocalSourcesPath: utils.GetLocalSourcesPath(),
}
config, err := solc.NewDefaultConfig()
if err != nil {
zap.L().Error("Failed to construct solc config", zap.Error(err))
return
}
usr, err := user.Current()
if err != nil {
zap.L().Error("Failed to get current user", zap.Error(err))
return
}
// Make sure that {HOME}/.solc/releases exists prior running this example.
releasesPath := filepath.Join(usr.HomeDir, ".solc", "releases")
if err = config.SetReleasesPath(releasesPath); err != nil {
zap.L().Error("Failed to set releases path", zap.Error(err))
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
compiler, err := solc.New(ctx, config)
if err != nil {
zap.L().Error("Failed to construct solc compiler", zap.Error(err))
return
}
// Ensure to sync the solc releases prior to compiling or doing literally anything else
// This is a blocking call and will take a while to complete if there are no sources.
// It can be executed 4 times a day at most. Otherwise it won't allow you to sync and instead return
// latest cache it has. This is to prevent abuse of GitHub API and to ensure that performance of solgo is not
// affected as it. Best to place as a goroutine call and execute it few times a day in the background.
if err := compiler.Sync(); err != nil {
zap.L().Error("Failed to sync solc releases", zap.Error(err))
return
}
// Initialize the detector with the provided Solidity source code
detector, err := detector.NewDetectorFromSources(ctx, compiler, sources)
if err != nil {
zap.L().Error("Failed to construct detector", zap.Error(err))
return
}
// Parse the provided sources and check for syntax errors
if errs := detector.Parse(); errs != nil {
for _, err := range errs {
zap.L().Error("Failed to parse sources", zap.Error(err))
}
return
}
// Build all components, e.g., ABIs
if err := detector.Build(); err != nil {
zap.L().Error("Failed to compile sources", zap.Error(err))
return
}
// Retrieve the IR instance
irBuilder := detector.GetIR()
// Retrieve the root IR node
irRoot := irBuilder.GetRoot()
// Print the number of discovered contracts
zap.L().Info(
"Number of discovered contracts",
zap.Int("count", int(irRoot.GetContractsCount())),
)
// Print the entry contract's name and internal ID
zap.L().Info(
"Entry Contract Name and Internal ID",
zap.String("name", irRoot.GetEntryName()),
zap.Int64("internal_id", irRoot.GetEntryId()),
)
// Print the license of the entry contract
zap.L().Info(
"Entry Contract License",
zap.String("license", irRoot.GetEntryContract().License),
)
// Print the discovered contract types for the entry contract
zap.L().Info(
"Discovered Contract Types",
zap.Any("types", irRoot.GetContractTypes()),
)
// Print potential EIPs discovered in the entry contract
for _, eip := range irRoot.GetEips() {
zap.L().Info(
"Discovered Potential EIPs",
zap.String("eip", eip.GetStandard().Name),
zap.Any("confidence_level", eip.GetConfidence().Confidence.String()),
zap.Any("confidence_points", eip.GetConfidence().ConfidencePoints),
)
}
// Print all functions discovered in the entry contract
var discoveredFunctions []string
for _, function := range irRoot.GetEntryContract().GetFunctions() {
discoveredFunctions = append(discoveredFunctions, function.Name)
}
zap.L().Info(
"Discovered Functions",
zap.Any("functions", discoveredFunctions),
)
// If no functions are discovered, display the constructor
zap.L().Info(
"Discovered Constructor Information",
zap.Any("constructor", irRoot.GetEntryContract().GetConstructor()),
)
// Print the ABI of the entry contract
contractAbi, err := detector.GetABI().ToJSON(
detector.GetABI().GetRoot().GetEntryContract(),
)
if err != nil {
zap.L().Error("Failed to convert ABI to ABIv2", zap.Error(err))
return
}
zap.L().Info(
"Discovered Contract ABI",
zap.String("abi", string(contractAbi)),
)
}
Response for the example above can be seen here: https://gist.github.com/0x19/5588da0a957e63db3c8a53f727ff7e17
On my machine it took 41.910304ms to discover 5 related contracts parse them and give back completed result.
🚀 Congratulations! You've just navigated the vast cosmos of SolGo's detector
. Not a small piece of code that's for sure. You in for more?
Remember, every great Solidity explorer started with a single line of code. Keep exploring, keep coding, and who knows? Maybe the next big thing in the blockchain universe will be your creation!
Happy coding, and may your contracts always be bug-free! 👨🚀