-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package contracts | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/unpackdev/solgo/utils" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// Audit performs a security analysis of the contract using its associated detector, | ||
// if available. It updates the contract descriptor with the audit results. | ||
func (c *Contract) Audit(ctx context.Context) error { | ||
select { | ||
case <-ctx.Done(): | ||
return nil | ||
default: | ||
if c.descriptor.HasDetector() && c.descriptor.HasContracts() { | ||
detector := c.descriptor.Detector | ||
|
||
semVer := utils.ParseSemanticVersion(c.descriptor.CompilerVersion) | ||
detector.GetAuditor().GetConfig().SetCompilerVersion(semVer.String()) | ||
|
||
audit, err := c.descriptor.Detector.Analyze() | ||
if err != nil { | ||
zap.L().Debug( | ||
"failed to analyze contract", | ||
zap.Error(err), | ||
zap.String("contract_address", c.descriptor.Address.Hex()), | ||
) | ||
return err | ||
} | ||
c.descriptor.Audit = audit | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package contracts | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// DiscoverDeployedBytecode retrieves the deployed bytecode of the contract deployed at the specified address. | ||
// It queries the blockchain using the provided client to fetch the bytecode associated with the contract address. | ||
// The fetched bytecode is then stored in the contract descriptor for further processing. | ||
func (c *Contract) DiscoverDeployedBytecode() error { | ||
code, err := c.client.CodeAt(c.ctx, c.addr, nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to get code at address %s: %s", c.addr.Hex(), err) | ||
} | ||
c.descriptor.DeployedBytecode = code | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package contracts | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/unpackdev/solgo/bindings" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/unpackdev/solgo/utils" | ||
) | ||
|
||
// DiscoverChainInfo retrieves information about the contract's deployment chain, including transaction, receipt, and block details. | ||
// If `otsLookup` is true, it queries the contract creator's information using the provided context. If `otsLookup` is false or | ||
// if the creator's information is not available, it queries the contract creation transaction hash using etherscan. | ||
// It then fetches the transaction, receipt, and block information associated with the contract deployment from the blockchain. | ||
// This method populates the contract descriptor with the retrieved information. | ||
func (c *Contract) DiscoverChainInfo(ctx context.Context, otsLookup bool) error { | ||
var info *bindings.CreatorInformation | ||
|
||
// What we are going to do, as erigon node is used in this particular case, is to query etherscan only if | ||
// otterscan is not available. | ||
if otsLookup { | ||
var err error | ||
info, err = c.bindings.GetContractCreator(ctx, c.network, c.addr) | ||
if err != nil { | ||
return fmt.Errorf("failed to get contract creator: %w", err) | ||
} | ||
} | ||
|
||
var txHash common.Hash | ||
|
||
if info == nil || info.CreationHash == utils.ZeroHash { | ||
// Prior to continuing with the unpacking of the contract, we want to make sure that we can reach properly | ||
// contract transaction and associated creation block. If we can't, we're not going to unpack it. | ||
cInfo, err := c.etherscan.QueryContractCreationTx(ctx, c.addr) | ||
if err != nil { | ||
return fmt.Errorf("failed to query contract creation block and tx hash: %w", err) | ||
} | ||
txHash = cInfo.GetTransactionHash() | ||
} else { | ||
txHash = info.CreationHash | ||
} | ||
|
||
// Alright now lets extract block and transaction as well as receipt from the blockchain. | ||
// We're going to use archive node for this, as we want to be sure that we can get all the data. | ||
|
||
tx, _, err := c.client.TransactionByHash(ctx, txHash) | ||
if err != nil { | ||
return fmt.Errorf("failed to get transaction by hash: %s", err) | ||
} | ||
c.descriptor.Transaction = tx | ||
|
||
receipt, err := c.client.TransactionReceipt(ctx, txHash) | ||
if err != nil { | ||
return fmt.Errorf("failed to get transaction receipt by hash: %s", err) | ||
} | ||
c.descriptor.Receipt = receipt | ||
|
||
block, err := c.client.BlockByNumber(ctx, receipt.BlockNumber) | ||
if err != nil { | ||
return fmt.Errorf("failed to get block by number: %s", err) | ||
} | ||
c.descriptor.Block = block.Header() | ||
|
||
if len(c.descriptor.ExecutionBytecode) < 1 { | ||
c.descriptor.ExecutionBytecode = c.descriptor.Transaction.Data() | ||
} | ||
|
||
if len(c.descriptor.DeployedBytecode) < 1 { | ||
code, err := c.client.CodeAt(ctx, receipt.ContractAddress, nil) | ||
if err != nil { | ||
return fmt.Errorf("failed to get contract code: %s", err) | ||
} | ||
c.descriptor.DeployedBytecode = code | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package contracts | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/unpackdev/solgo/bytecode" | ||
"github.com/unpackdev/solgo/utils" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// DiscoverConstructor discovers and decodes the constructor of the contract based on the provided context. | ||
// It utilizes the contract's descriptor to gather information about the contract's bytecode, ABI, and transaction data. | ||
// If a constructor is found in the bytecode, it decodes it using the provided ABI. | ||
// The decoded constructor information is stored within the contract descriptor. | ||
func (c *Contract) DiscoverConstructor(ctx context.Context) error { | ||
select { | ||
case <-ctx.Done(): | ||
return nil | ||
default: | ||
if c.descriptor.Detector != nil && c.descriptor.Detector.GetIR() != nil && c.descriptor.Detector.GetIR().GetRoot() != nil { | ||
detector := c.descriptor.Detector | ||
irRoot := detector.GetIR().GetRoot() | ||
abiRoot := detector.GetABI().GetRoot() | ||
|
||
if irRoot.GetEntryContract() != nil && irRoot.GetEntryContract().GetConstructor() != nil && | ||
abiRoot != nil && abiRoot.GetEntryContract().GetMethodByType("constructor") != nil { | ||
cAbi, _ := utils.ToJSON(abiRoot.GetEntryContract().GetMethodByType("constructor")) | ||
constructorAbi := fmt.Sprintf("[%s]", string(cAbi)) | ||
|
||
tx := c.descriptor.Transaction | ||
deployedBytecode := c.descriptor.DeployedBytecode | ||
|
||
// Ensure that empty bytecode is not processed, otherwise: | ||
// panic: runtime error: slice bounds out of range [:20] with capacity 0 | ||
if len(deployedBytecode) < 20 { | ||
return nil | ||
} | ||
|
||
position := bytes.Index(tx.Data(), deployedBytecode[:20]) | ||
if position != -1 { | ||
adjustedData := tx.Data()[position:] | ||
constructorDataIndex := len(deployedBytecode) | ||
if constructorDataIndex > len(adjustedData) { | ||
return fmt.Errorf("constructor data index out of range") | ||
} | ||
|
||
constructor, err := bytecode.DecodeConstructorFromAbi(adjustedData[constructorDataIndex:], constructorAbi) | ||
if err != nil { | ||
if !strings.Contains(err.Error(), "would go over slice boundary") { | ||
zap.L().Error( | ||
"failed to decode constructor from bytecode", | ||
zap.Error(err), | ||
zap.Any("network", c.network), | ||
zap.String("contract_address", c.addr.String()), | ||
) | ||
} | ||
return fmt.Errorf("failed to decode constructor from bytecode: %s", err) | ||
} | ||
c.descriptor.Constructor = constructor | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package contracts | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/0x19/solc-switch" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/core/types" | ||
"github.com/unpackdev/solgo/bindings" | ||
"github.com/unpackdev/solgo/clients" | ||
"github.com/unpackdev/solgo/metadata" | ||
"github.com/unpackdev/solgo/providers/bitquery" | ||
"github.com/unpackdev/solgo/providers/etherscan" | ||
"github.com/unpackdev/solgo/storage" | ||
"github.com/unpackdev/solgo/tokens" | ||
"github.com/unpackdev/solgo/utils" | ||
) | ||
|
||
// Metadata holds essential data related to an Ethereum smart contract. | ||
// It includes information about the contract's bytecode, associated transactions, and blockchain context. | ||
type Metadata struct { | ||
RuntimeBytecode []byte | ||
DeployedBytecode []byte | ||
Block *types.Block | ||
Transaction *types.Transaction | ||
Receipt *types.Receipt | ||
} | ||
|
||
// Contract represents an Ethereum smart contract within the context of a specific network. | ||
// It encapsulates the contract's address, network information, and associated metadata, | ||
// and provides methods to interact with the contract on the blockchain. | ||
type Contract struct { | ||
ctx context.Context | ||
clientPool *clients.ClientPool | ||
client *clients.Client | ||
addr common.Address | ||
network utils.Network | ||
descriptor *Descriptor | ||
token *tokens.Token | ||
bqp *bitquery.Provider | ||
etherscan *etherscan.Provider | ||
compiler *solc.Solc | ||
bindings *bindings.Manager | ||
tokenBind *bindings.Token | ||
stor *storage.Storage | ||
ipfsProvider metadata.Provider | ||
} | ||
|
||
// NewContract creates a new instance of Contract for a given Ethereum address and network. | ||
// It initializes the contract's context, metadata, and associated blockchain clients. | ||
// The function validates the contract's existence and its bytecode before creation. | ||
func NewContract(ctx context.Context, network utils.Network, clientPool *clients.ClientPool, stor *storage.Storage, bqp *bitquery.Provider, etherscan *etherscan.Provider, compiler *solc.Solc, bindManager *bindings.Manager, ipfsProvider metadata.Provider, addr common.Address) (*Contract, error) { | ||
if clientPool == nil { | ||
return nil, fmt.Errorf("client pool is nil") | ||
} | ||
|
||
client := clientPool.GetClientByGroup(network.String()) | ||
if client == nil { | ||
return nil, fmt.Errorf("client for network %s is nil", network.String()) | ||
} | ||
|
||
if !common.IsHexAddress(addr.Hex()) { | ||
return nil, fmt.Errorf("invalid address provided: %s", addr.Hex()) | ||
} | ||
|
||
tokenBind, err := bindings.NewToken(ctx, network, bindManager, bindings.DefaultTokenBindOptions(addr)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create new token %s bindings: %w", addr, err) | ||
} | ||
|
||
token, err := tokens.NewToken( | ||
ctx, | ||
network, | ||
addr, | ||
bindManager, | ||
clientPool, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create new token %s instance: %w", addr, err) | ||
} | ||
|
||
toReturn := &Contract{ | ||
ctx: ctx, | ||
network: network, | ||
clientPool: clientPool, | ||
client: client, | ||
addr: addr, | ||
bqp: bqp, | ||
etherscan: etherscan, | ||
compiler: compiler, | ||
descriptor: &Descriptor{ | ||
Network: network, | ||
NetworkID: utils.GetNetworkID(network), | ||
Address: addr, | ||
Implementations: make([]common.Address, 0), | ||
}, | ||
bindings: bindManager, | ||
token: token, | ||
tokenBind: tokenBind, | ||
stor: stor, | ||
ipfsProvider: ipfsProvider, | ||
} | ||
|
||
return toReturn, nil | ||
} | ||
|
||
// GetAddress returns the Ethereum address of the contract. | ||
func (c *Contract) GetAddress() common.Address { | ||
return c.addr | ||
} | ||
|
||
// GetNetwork returns the network (e.g., Mainnet, Ropsten) on which the contract is deployed. | ||
func (c *Contract) GetNetwork() utils.Network { | ||
return c.network | ||
} | ||
|
||
// GetDeployedBytecode returns the deployed bytecode of the contract. | ||
// This bytecode is the compiled contract code that exists on the Ethereum blockchain. | ||
func (c *Contract) GetDeployedBytecode() []byte { | ||
return c.descriptor.DeployedBytecode | ||
} | ||
|
||
// GetExecutionBytecode returns the runtime bytecode of the contract. | ||
// This bytecode is used during the execution of contract calls and transactions. | ||
func (c *Contract) GetExecutionBytecode() []byte { | ||
return c.descriptor.ExecutionBytecode | ||
} | ||
|
||
// GetBlock returns the blockchain block in which the contract was deployed or involved. | ||
func (c *Contract) GetBlock() *types.Header { | ||
return c.descriptor.Block | ||
} | ||
|
||
// SetBlock sets the blockchain block in which the contract was deployed or involved. | ||
func (c *Contract) SetBlock(block *types.Header) { | ||
c.descriptor.Block = block | ||
} | ||
|
||
// GetTransaction returns the Ethereum transaction associated with the contract's deployment or a specific operation. | ||
func (c *Contract) GetTransaction() *types.Transaction { | ||
return c.descriptor.Transaction | ||
} | ||
|
||
// SetTransaction sets the Ethereum transaction associated with the contract's deployment or a specific operation. | ||
func (c *Contract) SetTransaction(tx *types.Transaction) { | ||
c.descriptor.Transaction = tx | ||
c.descriptor.ExecutionBytecode = tx.Data() | ||
} | ||
|
||
// GetReceipt returns the receipt of the transaction in which the contract was involved, | ||
// providing details such as gas used and logs generated. | ||
func (c *Contract) GetReceipt() *types.Receipt { | ||
return c.descriptor.Receipt | ||
} | ||
|
||
// SetReceipt sets the receipt of the transaction in which the contract was involved, | ||
// providing details such as gas used and logs generated. | ||
func (c *Contract) SetReceipt(receipt *types.Receipt) { | ||
c.descriptor.Receipt = receipt | ||
} | ||
|
||
// GetSender returns the Ethereum address of the sender of the contract's transaction. | ||
// It extracts the sender's address using the transaction's signature. | ||
func (c *Contract) GetSender() (common.Address, error) { | ||
from, err := types.Sender(types.LatestSignerForChainID(c.descriptor.Transaction.ChainId()), c.descriptor.Transaction) | ||
if err != nil { | ||
return common.Address{}, fmt.Errorf("failed to get sender: %s", err) | ||
} | ||
|
||
return from, nil | ||
} | ||
|
||
// GetToken returns contract related discovered token (if found) | ||
func (c *Contract) GetToken() *tokens.Token { | ||
return c.token | ||
} | ||
|
||
// IsValid checks if the contract is valid by verifying its deployed bytecode. | ||
// A contract is considered valid if it has non-empty deployed bytecode on the blockchain. | ||
func (c *Contract) IsValid() (bool, error) { | ||
if err := c.DiscoverDeployedBytecode(); err != nil { | ||
return false, err | ||
} | ||
return len(c.descriptor.DeployedBytecode) > 2, nil | ||
} | ||
|
||
// GetDescriptor a public member to return back processed contract descriptor | ||
func (c *Contract) GetDescriptor() *Descriptor { | ||
return c.descriptor | ||
} |
Oops, something went wrong.