Documentation
¶
Index ¶
- Variables
- func AddCLI(sourceCode, structName string, noformat, includemain bool, contractCode string) (string, error)
- func GenerateHeader(packageName string, cli bool, includeMain bool, foundry string, abi string, ...) (string, error)
- func GenerateTypes(structName string, abi []byte, bytecode []byte, packageName string, ...) (string, error)
- func GetFlattenedContractCode(sourceCodePath string) (string, error)
- type ABIBoundParameter
- type CLISpecification
- type HandlerDefinition
- type HeaderParameters
- type MethodArgument
- type MethodReturnValue
Constants ¶
This section is empty.
Variables ¶
var CLICodeTemplate string = `
var ErrNoRPCURL error = errors.New("no RPC URL provided -- please pass an RPC URL from the command line or set the {{(ScreamingSnake .StructName)}}_RPC_URL environment variable")
// Generates an Ethereum client to the JSONRPC API at the given URL. If rpcURL is empty, then it
// attempts to read the RPC URL from the {{(ScreamingSnake .StructName)}}_RPC_URL environment variable. If that is empty,
// too, then it returns an error.
func NewClient(rpcURL string) (*ethclient.Client, error) {
if rpcURL == "" {
rpcURL = os.Getenv("{{(ScreamingSnake .StructName)}}_RPC_URL")
}
if rpcURL == "" {
return nil, ErrNoRPCURL
}
client, err := ethclient.Dial(rpcURL)
return client, err
}
// Creates a new context to be used when interacting with the chain client.
func NewChainContext(timeout uint) (context.Context, context.CancelFunc) {
baseCtx := context.Background()
parsedTimeout := time.Duration(timeout) * time.Second
ctx, cancel := context.WithTimeout(baseCtx, parsedTimeout)
return ctx, cancel
}
// Unlocks a key from a keystore (byte contents of a keystore file) with the given password.
func UnlockKeystore(keystoreData []byte, password string) (*keystore.Key, error) {
key, err := keystore.DecryptKey(keystoreData, password)
return key, err
}
// Loads a key from file, prompting the user for the password if it is not provided as a function argument.
func KeyFromFile(keystoreFile string, password string) (*keystore.Key, error) {
var emptyKey *keystore.Key
keystoreContent, readErr := os.ReadFile(keystoreFile)
if readErr != nil {
return emptyKey, readErr
}
// If password is "", prompt user for password.
if password == "" {
fmt.Printf("Please provide a password for keystore (%s): ", keystoreFile)
passwordRaw, inputErr := term.ReadPassword(int(os.Stdin.Fd()))
if inputErr != nil {
return emptyKey, fmt.Errorf("error reading password: %s", inputErr.Error())
}
fmt.Print("\n")
password = string(passwordRaw)
}
key, err := UnlockKeystore(keystoreContent, password)
return key, err
}
// This method is used to set the parameters on a view call from command line arguments (represented mostly as
// strings).
func SetCallParametersFromArgs(opts *bind.CallOpts, pending bool, fromAddress, blockNumber string) {
if pending {
opts.Pending = true
}
if fromAddress != "" {
opts.From = common.HexToAddress(fromAddress)
}
if blockNumber != "" {
opts.BlockNumber = new(big.Int)
opts.BlockNumber.SetString(blockNumber, 0)
}
}
// This method is used to set the parameters on a transaction from command line arguments (represented mostly as
// strings).
func SetTransactionParametersFromArgs(opts *bind.TransactOpts, nonce, value, gasPrice, maxFeePerGas, maxPriorityFeePerGas string, gasLimit uint64, noSend bool) {
if nonce != "" {
opts.Nonce = new(big.Int)
opts.Nonce.SetString(nonce, 0)
}
if value != "" {
opts.Value = new(big.Int)
opts.Value.SetString(value, 0)
}
if gasPrice != "" {
opts.GasPrice = new(big.Int)
opts.GasPrice.SetString(gasPrice, 0)
}
if maxFeePerGas != "" {
opts.GasFeeCap = new(big.Int)
opts.GasFeeCap.SetString(maxFeePerGas, 0)
}
if maxPriorityFeePerGas != "" {
opts.GasTipCap = new(big.Int)
opts.GasTipCap.SetString(maxPriorityFeePerGas, 0)
}
if gasLimit != 0 {
opts.GasLimit = gasLimit
}
opts.NoSend = noSend
}
func Create{{.StructName}}Command() *cobra.Command {
cmd := &cobra.Command{
Use: "{{(KebabCase .StructName)}}",
Short: "Interact with the {{.StructName}} contract",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
cmd.SetOut(os.Stdout)
{{if .DeployHandler.MethodName}}
DeployGroup := &cobra.Group{
ID: "deploy", Title: "Commands which deploy contracts",
}
cmd.AddGroup(DeployGroup)
{{- end}}
VerifyGroup := &cobra.Group{
ID: "verify", Title: "Commands which verify contract code",
}
cmd.AddGroup(VerifyGroup)
ViewGroup := &cobra.Group{
ID: "view", Title: "Commands which view contract state",
}
TransactGroup := &cobra.Group{
ID: "transact", Title: "Commands which submit transactions",
}
cmd.AddGroup(ViewGroup, TransactGroup)
{{if .DeployHandler.MethodName}}
cmd{{.DeployHandler.MethodName}} := {{.DeployHandler.HandlerName}}()
cmd{{.DeployHandler.MethodName}}.GroupID = DeployGroup.ID
cmd.AddCommand(cmd{{.DeployHandler.MethodName}})
{{- end}}
cmdVerify := VerifyContractCodeCommand()
cmdVerify.GroupID = VerifyGroup.ID
cmd.AddCommand(cmdVerify)
{{range .ViewHandlers}}
cmdView{{.MethodName}} := {{.HandlerName}}()
cmdView{{.MethodName}}.GroupID = ViewGroup.ID
cmd.AddCommand(cmdView{{.MethodName}})
{{- end}}
{{range .TransactHandlers}}
cmdTransact{{.MethodName}} := {{.HandlerName}}()
cmdTransact{{.MethodName}}.GroupID = TransactGroup.ID
cmd.AddCommand(cmdTransact{{.MethodName}})
{{- end}}
return cmd
}
// SafeOperationType represents the type of operation for a Safe transaction
type SafeOperationType uint8
const (
Call SafeOperationType = 0
DelegateCall SafeOperationType = 1
)
// String returns the string representation of the SafeOperationType
func (o SafeOperationType) String() string {
switch o {
case Call:
return "Call"
case DelegateCall:
return "DelegateCall"
default:
return "Unknown"
}
}
// SafeTransactionData represents the data for a Safe transaction
type SafeTransactionData struct {
To string ` + "`" + `json:"to"` + "`" + `
Value string ` + "`" + `json:"value"` + "`" + `
Data string ` + "`" + `json:"data"` + "`" + `
Operation SafeOperationType ` + "`" + `json:"operation"` + "`" + `
SafeTxGas uint64 ` + "`" + `json:"safeTxGas"` + "`" + `
BaseGas uint64 ` + "`" + `json:"baseGas"` + "`" + `
GasPrice string ` + "`" + `json:"gasPrice"` + "`" + `
GasToken string ` + "`" + `json:"gasToken"` + "`" + `
RefundReceiver string ` + "`" + `json:"refundReceiver"` + "`" + `
Nonce *big.Int ` + "`" + `json:"nonce"` + "`" + `
SafeTxHash string ` + "`" + `json:"safeTxHash"` + "`" + `
Sender string ` + "`" + `json:"sender"` + "`" + `
Signature string ` + "`" + `json:"signature"` + "`" + `
Origin string ` + "`" + `json:"origin"` + "`" + `
}
const (
NativeTokenAddress = "0x0000000000000000000000000000000000000000"
)
func DeployWithSafe(client *ethclient.Client, key *keystore.Key, safeAddress common.Address, factoryAddress common.Address, value *big.Int, safeApi string, deployBytecode []byte, safeOperationType SafeOperationType, salt [32]byte, safeNonce *big.Int) error {
abi, err := CreateCall.CreateCallMetaData.GetAbi()
if err != nil {
return fmt.Errorf("failed to get ABI: %v", err)
}
safeCreateCallTxData, err := abi.Pack("performCreate2", value, deployBytecode, salt)
if err != nil {
return fmt.Errorf("failed to pack performCreate2 transaction: %v", err)
}
return CreateSafeProposal(client, key, safeAddress, factoryAddress, safeCreateCallTxData, value, safeApi, SafeOperationType(safeOperationType), safeNonce)
}
func PredictDeploymentAddressSafe(from common.Address, salt [32]byte, deployBytecode []byte) (common.Address, error) {
// Calculate the hash of the init code (deployment bytecode)
initCodeHash := crypto.Keccak256(deployBytecode)
// Calculate the CREATE2 address
deployedAddress := crypto.CreateAddress2(from, salt, initCodeHash)
return deployedAddress, nil
}
func CreateSafeProposal(client *ethclient.Client, key *keystore.Key, safeAddress common.Address, to common.Address, data []byte, value *big.Int, safeApi string, safeOperationType SafeOperationType, safeNonce *big.Int) error {
chainID, err := client.ChainID(context.Background())
if err != nil {
return fmt.Errorf("failed to get chain ID: %v", err)
}
// Create a new instance of the GnosisSafe contract
safeInstance, err := GnosisSafe.NewGnosisSafe(safeAddress, client)
if err != nil {
return fmt.Errorf("failed to create GnosisSafe instance: %v", err)
}
nonce := safeNonce
if safeNonce == nil {
// Fetch the current nonce from the Safe contract
fetchedNonce, err := safeInstance.Nonce(&bind.CallOpts{})
if err != nil {
return fmt.Errorf("failed to fetch nonce from Safe contract: %v", err)
}
nonce = fetchedNonce
} else {
nonce = safeNonce
}
safeTransactionData := SafeTransactionData{
To: to.Hex(),
Value: value.String(),
Data: common.Bytes2Hex(data),
Operation: safeOperationType,
SafeTxGas: 0,
BaseGas: 0,
GasPrice: "0",
GasToken: NativeTokenAddress,
RefundReceiver: NativeTokenAddress,
Nonce: nonce,
}
// Calculate SafeTxHash
safeTxHash, err := CalculateSafeTxHash(safeAddress, safeTransactionData, chainID)
if err != nil {
return fmt.Errorf("failed to calculate SafeTxHash: %v", err)
}
// Sign the SafeTxHash
signature, err := crypto.Sign(safeTxHash.Bytes(), key.PrivateKey)
if err != nil {
return fmt.Errorf("failed to sign SafeTxHash: %v", err)
}
// Adjust V value for Ethereum's replay protection
signature[64] += 27
// Convert signature to hex
senderSignature := "0x" + common.Bytes2Hex(signature)
// Prepare the request body
requestBody := map[string]interface{}{
"to": safeTransactionData.To,
"value": safeTransactionData.Value,
"data": "0x" + safeTransactionData.Data,
"operation": int(safeTransactionData.Operation),
"safeTxGas": fmt.Sprintf("%d", safeTransactionData.SafeTxGas),
"baseGas": fmt.Sprintf("%d", safeTransactionData.BaseGas),
"gasPrice": safeTransactionData.GasPrice,
"gasToken": safeTransactionData.GasToken,
"refundReceiver": safeTransactionData.RefundReceiver,
"nonce": fmt.Sprintf("%d", safeTransactionData.Nonce),
"safeTxHash": safeTxHash.Hex(),
"sender": key.Address.Hex(),
"signature": senderSignature,
"origin": fmt.Sprintf("{\"url\":\"%s\",\"name\":\"TokenSender Deployment\"}", safeApi),
}
// Marshal the request body to JSON
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("failed to marshal request body: %v", err)
}
// Send the request to the Safe Transaction Service
req, err := http.NewRequest("POST", safeApi, bytes.NewBuffer(jsonBody))
if err != nil {
return fmt.Errorf("failed to create request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
fmt.Println("Safe proposal created successfully")
return nil
}
func CalculateSafeTxHash(safeAddress common.Address, txData SafeTransactionData, chainID *big.Int) (common.Hash, error) {
domainSeparator := apitypes.TypedDataDomain{
ChainId: (*math.HexOrDecimal256)(chainID),
VerifyingContract: safeAddress.Hex(),
}
typedData := apitypes.TypedData{
Types: apitypes.Types{
"EIP712Domain": []apitypes.Type{
{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"SafeTx": []apitypes.Type{
{Name: "to", Type: "address"},
{Name: "value", Type: "uint256"},
{Name: "data", Type: "bytes"},
{Name: "operation", Type: "uint8"},
{Name: "safeTxGas", Type: "uint256"},
{Name: "baseGas", Type: "uint256"},
{Name: "gasPrice", Type: "uint256"},
{Name: "gasToken", Type: "address"},
{Name: "refundReceiver", Type: "address"},
{Name: "nonce", Type: "uint256"},
},
},
Domain: domainSeparator,
PrimaryType: "SafeTx",
Message: apitypes.TypedDataMessage{
"to": txData.To,
"value": txData.Value,
"data": "0x" + txData.Data,
"operation": fmt.Sprintf("%d", txData.Operation),
"safeTxGas": fmt.Sprintf("%d", txData.SafeTxGas),
"baseGas": fmt.Sprintf("%d", txData.BaseGas),
"gasPrice": txData.GasPrice,
"gasToken": txData.GasToken,
"refundReceiver": txData.RefundReceiver,
"nonce": fmt.Sprintf("%d", txData.Nonce),
},
}
typedDataHash, _, err := apitypes.TypedDataAndHash(typedData)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to hash typed data: %v", err)
}
return common.BytesToHash(typedDataHash), nil
}
func PrintStruct(cmd *cobra.Command, name string, rv reflect.Value, depth int) {
indent := strings.Repeat(" ", depth)
cmd.Printf("%s%s:\n", indent, name)
rt := rv.Type()
for i := 0; i < rv.NumField(); i++ {
field := rv.Field(i)
fieldName := rt.Field(i).Name
switch f := field.Interface().(type) {
case common.Address:
cmd.Printf("%s %s: %s\n", indent, fieldName, f.Hex())
case *big.Int:
cmd.Printf("%s %s: %s\n", indent, fieldName, f.String())
default:
// Check if field is a nested struct
if field.Kind() == reflect.Struct {
PrintStruct(cmd, fieldName, field, depth+1)
} else {
cmd.Printf("%s %s: %v\n", indent, fieldName, f)
}
}
}
}
`
This template is used to generate the skeleton of the CLI, along with all utility methods that can be used by CLI handlers. It is expected to be applied to a CLISpecification struct.
var DeployCommandTemplate string = `` /* 11081-byte string literal not displayed */
This template generates the handler for smart contract deployment. It is intended to be used with a CLISpecification struct.
var ErrParameterUnnamed error = errors.New("parameter is unnamed")
ErrParameterUnnamed is raised when a method argument is unnamed. go-ethereum's bind.Bind does not leave its method arguments unnamed, so this also indicates a skew in seer's assumptions and the actual generated code it is using from go-ethereum's bind.Bind.
var ErrParsingCLISpecification error = errors.New("error parsing CLI parameters")
ErrParsingCLISpecification is raised when there is an error producing a CLI specification from AST nodes. It indicates that seer's assumptions about the code generated by the go-ethereum Go bindings generators are no longer correct.
var HeaderTemplate string = `` /* 579-byte string literal not displayed */
This is the Go template used to create header information at the top of the generated code. At a bare minimum, the header specifies the version of seer that was used to generate the code. This template should be applied to a EVMHeaderParameters struct.
var TransactMethodCommandsTemplate string = `` /* 11368-byte string literal not displayed */
This template generates the handlers for all smart contract methods that submit transactions. It is intended to be used with a CLISpecification struct.
var VerifyContractCodeCommandTemplate string = `
type CompilerInfo struct {
SolidityVersion string
EVMVersion string
}
func ExtractCompilerInfo(bytecode string) (*CompilerInfo, error) {
// Remove "0x" prefix if present
bytecode = strings.TrimPrefix(bytecode, "0x")
if len(bytecode) < 20 {
return nil, fmt.Errorf("bytecode too short (length: %d)", len(bytecode))
}
// Get the last bytes that contain version info
versionData := bytecode[len(bytecode)-20:]
// Check for solc identifier '736f6c6343' (which is 'solcC' in hex)
if !strings.HasPrefix(versionData, "736f6c6343") {
return nil, fmt.Errorf("no solidity version identifier found in version data: %s", versionData)
}
// Skip first 10 chars (736f6c6343)
versionHex := versionData[10:18]
// Parse major, minor, and patch versions
major := int64(0)
minor, err := strconv.ParseInt(versionHex[2:4], 16, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse minor version: %v", err)
}
patch, err := strconv.ParseInt(versionHex[4:6], 16, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse patch version: %v", err)
}
// Determine EVM version based on Solidity version
evmVersion := determineEVMVersion(major, minor, patch)
return &CompilerInfo{
SolidityVersion: fmt.Sprintf("v0.%d.%d", minor, patch), // Changed format string
EVMVersion: evmVersion,
}, nil
}
func determineEVMVersion(major, minor, patch int64) string {
// This mapping is based on Solidity's default EVM version per compiler version
// Reference: https://docs.soliditylang.org/en/latest/using-the-compiler.html#target-options
switch {
case minor >= 8 && patch >= 24:
return "cancun" // Solidity 0.8.24+ defaults to Cancun
case minor >= 8:
return "london" // Solidity 0.8.0-0.8.23 defaults to London
case minor == 7:
return "istanbul" // Solidity 0.7.x defaults to Istanbul
case minor == 6:
return "istanbul" // Solidity 0.6.x defaults to Istanbul
case minor == 5 && patch >= 5:
return "petersburg" // Solidity 0.5.5+ defaults to Petersburg
case minor == 5:
return "byzantium" // Solidity 0.5.0-0.5.4 defaults to Byzantium
default:
return "homestead" // Earlier versions defaulted to Homestead
}
}
type EtherscanResponse struct {
Status string ` + "`" + `json:"status"` + "`" + `
Message string ` + "`" + `json:"message"` + "`" + `
Result string ` + "`" + `json:"result"` + "`" + `
}
func (r *EtherscanResponse) IsOk() bool {
return r.Status == "1"
}
func (r *EtherscanResponse) IsBytecodeMissingInNetworkError() bool {
return strings.Contains(strings.ToLower(r.Message), "missing bytecode")
}
func (r *EtherscanResponse) IsAlreadyVerified() bool {
return strings.Contains(strings.ToLower(r.Message), "already verified")
}
// SolidityTag represents a tag in the Solidity repository
type SolidityTag struct {
Object struct {
SHA string ` + "`" + `json:"sha"` + "`" + `
} ` + "`" + `json:"object"` + "`" + `
}
// GetSolidityCommitHash fetches the commit hash for a specific Solidity version tag
func GetSolidityCommitHash(version string) (string, error) {
// Clean version string (ensure it starts with 'v')
if !strings.HasPrefix(version, "v") {
version = "v" + version
}
// Create HTTP client with timeout
client := &http.Client{Timeout: 10 * time.Second}
// Get tag info from GitHub API
url := fmt.Sprintf("https://api.github.com/repos/ethereum/solidity/git/refs/tags/%s", version)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// Add User-Agent header to avoid GitHub API limitations
req.Header.Set("User-Agent", "seer-contract-verifier")
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to fetch tag info: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("GitHub API returned status %d for version %s", resp.StatusCode, version)
}
var tag SolidityTag
if err := json.NewDecoder(resp.Body).Decode(&tag); err != nil {
return "", fmt.Errorf("failed to decode GitHub response: %w", err)
}
// Return first 8 characters of the commit hash
if len(tag.Object.SHA) < 8 {
return "", fmt.Errorf("invalid commit hash length")
}
return tag.Object.SHA[:8], nil
}
// GetFullCompilerVersion gets the full compiler version with commit hash
func GetFullCompilerVersion(version string) (string, error) {
// Get commit hash from GitHub tag
commitHash, err := GetSolidityCommitHash(version)
if err != nil {
return "", fmt.Errorf("failed to get commit hash: %w", err)
}
// Format full version string
fullVersion := fmt.Sprintf("%s+commit.%s", version, commitHash)
return fullVersion, nil
}
func VerifyContractCode(
contractAddress common.Address,
contractCode string,
apiURL string,
apiKey string,
contractName string,
compilerVersion string,
runs uint,
evmVersion string,
{{- range .DeployHandler.MethodArgs}}
{{.CLIVar}} {{.CLIType}},
{{- end}}
) error {
fmt.Println("Verifying contract code...")
fmt.Println("EVM version:", evmVersion)
// Pack constructor arguments
abiPacked, err := {{.StructName}}MetaData.GetAbi()
if err != nil {
return fmt.Errorf("failed to get ABI: %v", err)
}
constructorArguments, err := abiPacked.Pack("",
{{- range .DeployHandler.MethodArgs}}
{{.CLIVar}},
{{- end}}
)
if err != nil {
return fmt.Errorf("failed to pack constructor arguments: %v", err)
}
// If no API key is provided, assume it's a Blockscout-compatible API
if apiKey == "" {
// Blockscout verification
formData := url.Values{}
formData.Set("module", "contract")
formData.Set("action", "verify")
formData.Set("addressHash", contractAddress.Hex())
formData.Set("name", contractName)
formData.Set("compilerVersion", compilerVersion)
formData.Set("optimization", fmt.Sprintf("%t", runs > 0))
formData.Set("optimizationRuns", fmt.Sprintf("%d", runs))
formData.Set("evmVersion", evmVersion)
formData.Set("contractSourceCode", contractCode)
formData.Set("constructorArguments", hex.EncodeToString(constructorArguments))
// Send verification request
client := &http.Client{Timeout: time.Second * 30}
resp, err := client.PostForm(apiURL, formData)
if err != nil {
return fmt.Errorf("network request error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Blockscout API returned status %d", resp.StatusCode)
}
fmt.Println("Contract verification submitted successfully to Blockscout")
return nil
}
fullCompilerVersion, err := GetFullCompilerVersion(compilerVersion)
if err != nil {
return fmt.Errorf("failed to get full compiler version: %w", err)
}
fmt.Println("Compiler version:", fullCompilerVersion)
// Prepare the form data
formData := url.Values{}
formData.Set("apikey", apiKey)
formData.Set("module", "contract")
formData.Set("action", "verifysourcecode")
formData.Set("contractaddress", contractAddress.Hex())
formData.Set("sourceCode", contractCode)
formData.Set("codeformat", "solidity-single-file")
formData.Set("contractname", contractName)
formData.Set("compilerversion", fullCompilerVersion)
formData.Set("evmversion", evmVersion)
formData.Set("optimizationUsed", fmt.Sprintf("%t", runs > 0))
formData.Set("runs", fmt.Sprintf("%d", runs))
formData.Set("constructorArguments", hex.EncodeToString(constructorArguments))
// Send the verification request
// Create HTTP client
client := &http.Client{
Timeout: time.Second * 30,
}
// Send POST request
resp, err := client.PostForm(apiURL, formData)
if err != nil {
return fmt.Errorf("network request error: %v", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("invalid status code: %d", resp.StatusCode)
}
// Read and parse response
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %v", err)
}
var response EtherscanResponse
if err := json.Unmarshal(body, &response); err != nil {
return fmt.Errorf("failed to parse response: %v", err)
}
// Check for specific error conditions
if response.IsBytecodeMissingInNetworkError() {
return fmt.Errorf("contract bytecode not found on network for address %s", contractAddress.Hex())
}
if response.IsAlreadyVerified() {
return fmt.Errorf("contract %s at address %s is already verified", contractName, contractAddress.Hex())
}
if !response.IsOk() {
return fmt.Errorf("verification failed: %s", response.Message)
}
guid := response.Result
fmt.Printf("Contract verification submitted successfully. GUID: %s\n", guid)
// Check verification status
fmt.Println("Checking verification status...")
for i := 0; i < 10; i++ { // Try up to 10 times
status, err := CheckVerificationStatus(apiURL, apiKey, guid)
if err != nil {
return fmt.Errorf("failed to check verification status: %v", err)
}
if status == "Pass - Verified" {
fmt.Println("Contract successfully verified!")
return nil
} else if status == "Fail - Unable to verify" {
return fmt.Errorf("contract verification failed")
}
fmt.Println("Verification in progress, waiting 5 seconds...")
time.Sleep(5 * time.Second)
}
return fmt.Errorf("verification status check timed out")
}
func CheckVerificationStatus(apiURL string, apiKey string, guid string) (string, error) {
// Prepare the query parameters
params := url.Values{}
params.Set("apikey", apiKey)
params.Set("module", "contract")
params.Set("action", "checkverifystatus")
params.Set("guid", guid)
// Create the full URL
fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode())
// Create HTTP client
client := &http.Client{
Timeout: time.Second * 10,
}
// Send GET request
resp, err := client.Get(fullURL)
if err != nil {
return "", fmt.Errorf("network request error: %v", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("invalid status code: %d", resp.StatusCode)
}
// Read and parse response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response body: %v", err)
}
var response EtherscanResponse
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("failed to parse response: %v", err)
}
return response.Result, nil
}
func VerifyContractCodeCommand() *cobra.Command {
var contractAddressRaw, apiURL, apiKey string
var contractAddress common.Address
var runs uint
var evmVersion, compilerVersion string
{{range .DeployHandler.MethodArgs}}
var {{.CLIVar}} {{.CLIType}}
{{if (ne .CLIRawVar .CLIVar)}}var {{.CLIRawVar}} {{.CLIRawType}}{{end}}
{{- end}}
cmd := &cobra.Command{
Use: "verify",
Short: "Verify a contract code on a block explorer",
PreRunE: func(cmd *cobra.Command, args []string) error {
if {{.StructName}}ContractCode == "" {
return fmt.Errorf("contract code is empty, please re-run evm generate passing the --source-code flag")
}
if contractAddressRaw == "" {
return fmt.Errorf("--contract not specified")
} else if !common.IsHexAddress(contractAddressRaw) {
return fmt.Errorf("--contract is not a valid Ethereum address")
}
contractAddress = common.HexToAddress(contractAddressRaw)
{{range .DeployHandler.MethodArgs}}
{{.PreRunE}}
{{- end}}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
compilerInfo, err := ExtractCompilerInfo({{.StructName}}Bin)
if err != nil {
return fmt.Errorf("failed to extract compiler info: %v", err)
}
if compilerVersion != "" {
compilerInfo.SolidityVersion = compilerVersion
}
if evmVersion != "" {
compilerInfo.EVMVersion = evmVersion
}
return VerifyContractCode(contractAddress, {{.StructName}}ContractCode, apiURL, apiKey, "{{.StructName}}", compilerInfo.SolidityVersion, runs, compilerInfo.EVMVersion, {{- range .DeployHandler.MethodArgs}}{{.CLIVar}},{{- end}})
},
}
cmd.Flags().StringVar(&contractAddressRaw, "contract", "c", "The address of the contract to verify")
cmd.Flags().StringVar(&apiURL, "api", "a", "The block explorer API to use")
cmd.Flags().StringVar(&apiKey, "api-key", "k", "The API key to use for the block explorer")
cmd.Flags().UintVar(&runs, "runs", 0, "The number of runs to use for optimization")
cmd.Flags().StringVar(&evmVersion, "evm-version", "", "Override the EVM version to use for the contract")
cmd.Flags().StringVar(&compilerVersion, "compiler-version", "", "Override the compiler version to use for the contract")
{{range .DeployHandler.MethodArgs}}
cmd.Flags().{{.Flag}}
{{- end}}
return cmd
}
`
var ViewMethodCommandsTemplate string = `` /* 2501-byte string literal not displayed */
This template generates the handlers for all smart contract call methods. It is intended to be used with a CLISpecification struct.
Functions ¶
func AddCLI ¶
func AddCLI(sourceCode, structName string, noformat, includemain bool, contractCode string) (string, error)
AddCLI adds CLI code (using github.com/spf13/cobra command-line framework) for code generated by the GenerateTypes function. The output of this function *contains* the input, with enrichments (some of then inline). It should not be concatenated with the output of GenerateTypes, but rather be used as part of a chain.
func GenerateHeader ¶
func GenerateHeader(packageName string, cli bool, includeMain bool, foundry string, abi string, bytecode string, sourceCode string, structname string, outputfile string, noformat bool) (string, error)
Generates the header comment for the generated code.
func GenerateTypes ¶
func GenerateTypes(structName string, abi []byte, bytecode []byte, packageName string, aliases map[string]string) (string, error)
GenerateTypes generates Go bindings to an Ethereum contract ABI (or union of such). This functionality is roughly equivalent to that provided by the `abigen` tool provided by go-ethereum: https://github.com/ethereum/go-ethereum/tree/master/cmd/abigen Under the hood, GenerateTypes uses the Bind method implemented in go-ethereum in a manner similar to abigen. It just offers a simpler call signature that makes more sense for users of seer.
Arguments:
- structName: The name of the generated Go struct that will represent this contract.
- abi: The bytes representing the contract's ABI.
- bytecode: The bytes representing the contract's bytecode. If this is provided, a "deploy" method will be generated. If it is not provided, no such method will be generated.
- packageName: If this is provided, the generated code will contain a package declaration of this name.
- aliases: This is a mapping of aliases for identifiers from an ABI. Necessary because Go bindings have trouble with overloaded methods in an ABI.
func GetFlattenedContractCode ¶ added in v0.4.0
Get a flattened source code from a solidity file
Types ¶
type ABIBoundParameter ¶
type ABIBoundParameter struct {
Name string
GoType string
Node ast.Node
IsArray bool
Length int
Subtypes []ABIBoundParameter
}
ABIBoundParameter represents a Go type that is bound to an Ethereum contract ABI item. The different types of types we need to deal with (based on https://github.com/ethereum/go-ethereum/blob/47d76c5f9508d3594bfc9aafa95c04edae71c5a1/accounts/abi/bind/bind.go#L338): - uint8 - uint16 - uint32 - uint64 - int8 - int16 - int32 - int64 - *big.Int - [n]byte - []byte - string - bool - array - struct
func ParseBoundParameter ¶
func ParseBoundParameter(arg ast.Node) (ABIBoundParameter, error)
ParseBoundParameter parses an ast.Node representing a method parameter (or return value). It inspects the ast.Node recursively to determine the information needed to parse that node to the user from command-line input or to present an instance of that type to a user as command output.
type CLISpecification ¶
type CLISpecification struct {
StructName string
DeployHandler HandlerDefinition
ViewHandlers []HandlerDefinition
TransactHandlers []HandlerDefinition
}
Data structure that parametrizes CLI generation.
func ParseCLISpecification ¶
func ParseCLISpecification(structName string, deployMethod *ast.FuncDecl, viewMethods map[string]*ast.FuncDecl, transactMethods map[string]*ast.FuncDecl) (CLISpecification, error)
Produces a CLI specification for the structure with the given name, provided the AST nodes representing the deployment method, the transaction methods, and the view methods for the corresponding smart contract.
The value of the deployMethod argument is used to determine if the deployment functionality will be added to the CLI. If deployMethod is nil, then a deployment command is not generated. This is signified by the result.DeployHandler.MethodName being empty in the resulting CLISpecification.
type HandlerDefinition ¶
type HandlerDefinition struct {
MethodName string
HandlerName string
MethodArgs []MethodArgument
MethodReturns []MethodReturnValue
}
HandlerDefinition specifies a (sub)command handler that needs to be generated as part of the CLI.
type HeaderParameters ¶
type HeaderParameters struct {
Version string
PackageName string
CLI bool
IncludeMain bool
Foundry string
ABI string
Bytecode string
SourceCode string
StructName string
OutputFile string
NoFormat bool
}
Parameters used to generate header comment for generated code.
type MethodArgument ¶
type MethodArgument struct {
Argument ABIBoundParameter
CLIVar string
CLIRawVar string
CLIName string
CLIType string
CLIRawType string
Flag string
PreRunE string
}
MethodArgument specifies a method argument to a smart contract bound method and how it should be handled in the generated CLI.
func DeriveMethodArguments ¶
func DeriveMethodArguments(parameters []ABIBoundParameter) ([]MethodArgument, error)
Fills in the information required to represent the given parameters as command-line argument. Takes an array of ABIBoundParameter structs because it deduplicates flags. This is where we map the Go types used in the methods to the Go types used to parse those arguments from the command line.
type MethodReturnValue ¶
type MethodReturnValue struct {
ReturnValue ABIBoundParameter
CaptureName string
CaptureType string
InitializeCode string
PrintCode string
}
MethodReturnValue specifies a value returned by a smart contract bound method and how it should be handled in the generated CLI.
func DeriveMethodReturnValues ¶
func DeriveMethodReturnValues(parameters []ABIBoundParameter) ([]MethodReturnValue, error)
Fills in the information required to present the given return values to the user as output from a CLI.