Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added --foundry flag to seer evm generate #13

Merged
merged 6 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,10 @@ func CreateEVMCommand() *cobra.Command {

func CreateEVMGenerateCommand() *cobra.Command {
var cli, noformat, includemain bool
var infile, packageName, structName, bytecodefile, outfile string
var infile, packageName, structName, bytecodefile, outfile, foundryBuildFile string
var rawABI, bytecode []byte
var readErr error
var aliases map[string]string

evmGenerateCmd := &cobra.Command{
Use: "generate",
Expand All @@ -242,7 +243,27 @@ func CreateEVMGenerateCommand() *cobra.Command {
return errors.New("struct name is required via --struct/-s")
}

if infile != "" {
if foundryBuildFile != "" {
var contents []byte
contents, readErr = os.ReadFile(foundryBuildFile)
if readErr != nil {
return readErr
}

type foundryBytecodeObject struct {
Object string `json:"object"`
}

type foundryBuildArtifact struct {
ABI json.RawMessage `json:"abi"`
Bytecode foundryBytecodeObject `json:"bytecode"`
}

var artifact foundryBuildArtifact
readErr = json.Unmarshal(contents, &artifact)
rawABI = []byte(artifact.ABI)
bytecode = []byte(artifact.Bytecode.Object)
} else if infile != "" {
rawABI, readErr = os.ReadFile(infile)
} else {
rawABI, readErr = io.ReadAll(os.Stdin)
Expand All @@ -255,11 +276,19 @@ func CreateEVMGenerateCommand() *cobra.Command {
return readErr
},
RunE: func(cmd *cobra.Command, args []string) error {
code, codeErr := evm.GenerateTypes(structName, rawABI, bytecode, packageName)

code, codeErr := evm.GenerateTypes(structName, rawABI, bytecode, packageName, aliases)
if codeErr != nil {
return codeErr
}

header, headerErr := evm.GenerateHeader(packageName, cli, includemain, foundryBuildFile, infile, bytecodefile, structName, outfile, noformat)
if headerErr != nil {
return headerErr
}

code = header + code

if cli {
code, readErr = evm.AddCLI(code, structName, noformat, includemain)
if readErr != nil {
Expand Down Expand Up @@ -287,6 +316,8 @@ func CreateEVMGenerateCommand() *cobra.Command {
evmGenerateCmd.Flags().BoolVar(&noformat, "noformat", false, "Set this flag if you do not want the generated code to be formatted (useful to debug errors)")
evmGenerateCmd.Flags().BoolVar(&includemain, "includemain", false, "Set this flag if you want to generate a \"main\" function to execute the CLI and make the generated code self-contained - this option is ignored if --cli is not set")
evmGenerateCmd.Flags().StringVarP(&outfile, "output", "o", "", "Path to output file (default stdout)")
evmGenerateCmd.Flags().StringVar(&foundryBuildFile, "foundry", "", "If your contract is compiled using Foundry, you can specify a path to the build file here (typically \"<foundry project root>/out/<solidit filename>/<contract name>.json\") instead of specifying --abi and --bytecode separately")
evmGenerateCmd.Flags().StringToStringVar(&aliases, "alias", nil, "A map of identifier aliaes (e.g. --alias name=somename)")

return evmGenerateCmd
}
59 changes: 57 additions & 2 deletions evm/generators.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/iancoleman/strcase"
"github.com/moonstream-to/seer/version"
"golang.org/x/tools/imports"
)

Expand All @@ -44,8 +45,9 @@ var ErrParameterUnnamed error = errors.New("parameter is unnamed")
// 3. 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.
// 4. packageName: If this is provided, the generated code will contain a package declaration of this name.
func GenerateTypes(structName string, abi []byte, bytecode []byte, packageName string) (string, error) {
return bind.Bind([]string{structName}, []string{string(abi)}, []string{string(bytecode)}, []map[string]string{}, packageName, bind.LangGo, map[string]string{}, map[string]string{})
// 5. 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 GenerateTypes(structName string, abi []byte, bytecode []byte, packageName string, aliases map[string]string) (string, error) {
return bind.Bind([]string{structName}, []string{string(abi)}, []string{string(bytecode)}, []map[string]string{}, packageName, bind.LangGo, map[string]string{}, aliases)
}

// ABIBoundParameter represents a Go type that is bound to an Ethereum contract ABI item.
Expand Down Expand Up @@ -113,6 +115,49 @@ type CLISpecification struct {
TransactHandlers []HandlerDefinition
}

// Parameters used to generate header comment for generated code.
type HeaderParameters struct {
Version string
PackageName string
CLI bool
IncludeMain bool
Foundry string
ABI string
Bytecode string
StructName string
OutputFile string
NoFormat bool
}

// Generates the header comment for the generated code.
func GenerateHeader(packageName string, cli bool, includeMain bool, foundry string, abi string, bytecode string, structname string, outputfile string, noformat bool) (string, error) {
headerTemplate, headerTemplateParseErr := template.New("header").Parse(HeaderTemplate)
if headerTemplateParseErr != nil {
return "", headerTemplateParseErr
}

parameters := HeaderParameters{
Version: version.SeerVersion,
PackageName: packageName,
CLI: cli,
IncludeMain: includeMain,
Foundry: foundry,
ABI: abi,
Bytecode: bytecode,
StructName: structname,
OutputFile: outputfile,
NoFormat: noformat,
}

var b bytes.Buffer
templateErr := headerTemplate.Execute(&b, parameters)
if templateErr != nil {
return "", templateErr
}

return b.String(), nil
}

// 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.
Expand Down Expand Up @@ -262,6 +307,8 @@ func DeriveMethodArguments(parameters []ABIBoundParameter) ([]MethodArgument, er
"chainId": true,
"network": true,
"simulate": true,
"contractAddress": true,
"name": true,
}

for i, parameter := range parameters {
Expand Down Expand Up @@ -1328,3 +1375,11 @@ func {{.HandlerName}}() *cobra.Command {
}
{{- end}}
`

// 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 HeaderTemplate string = `// This file was generated by seer: https://github.com/moonstream-to/seer.
// seer version: {{.Version}}
// seer command: seer evm generate{{if .PackageName}} --package {{.PackageName}}{{end}}{{if .CLI}} --cli{{end}}{{if .IncludeMain}} --includemain{{end}}{{if (ne .Foundry "")}} --foundry {{.Foundry}}{{end}}{{if (ne .ABI "")}} --abi {{.ABI}}{{end}}{{if (ne .Bytecode "")}} --bytecode {{.Bytecode}}{{end}} --struct {{.StructName}}{{if (ne .OutputFile "")}} --output {{.OutputFile}}{{end}}{{if .NoFormat}} --noformat{{end}}
`
Loading
Loading