Skip to content

Commit

Permalink
Merge pull request #13 from moonstream-to/evm-foundry
Browse files Browse the repository at this point in the history
Added `--foundry` flag to `seer evm generate`
  • Loading branch information
zomglings authored Feb 17, 2024
2 parents 32fd3f4 + 434ec0a commit 2e5a4eb
Show file tree
Hide file tree
Showing 5 changed files with 400 additions and 311 deletions.
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

0 comments on commit 2e5a4eb

Please sign in to comment.