chainid_generator.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //go:build ignore
  2. // ChainID Auto-Generation Tool
  3. //
  4. // This file generates the ChainID methods for the VAA struct in the SDK.
  5. // It automatically creates String(), ChainIDFromString(), and GetAllNetworkIDs()
  6. // methods by parsing ChainID constants from vaa/structs.go.
  7. //
  8. // ADDING A NEW CHAINID:
  9. // 1. Add the new ChainID constant to vaa/structs.go following the pattern:
  10. // // ChainIDNewChain is the ChainID of NewChain
  11. // ChainIDNewChain ChainID = 99
  12. //
  13. // 2. Run `make go-generate` to regenerate the methods in vaa/chainid_generated.go
  14. //
  15. // 3. The generator will automatically:
  16. // - Extract the chain name from the constant (removes "ChainID" prefix)
  17. // - Convert to lowercase for string representation
  18. // - Handle special cases like testnet names (e.g., "PolygonSepolia" -> "polygon_sepolia")
  19. // - Add the new chain to all generated methods
  20. //
  21. // The generated file should never be edited manually - always regenerate it.
  22. package main
  23. import (
  24. "fmt"
  25. "go/ast"
  26. "go/parser"
  27. "go/token"
  28. "log"
  29. "os"
  30. "sort"
  31. "strconv"
  32. "strings"
  33. "text/template"
  34. )
  35. type ChainInfo struct {
  36. Name string
  37. Value int
  38. GoName string
  39. }
  40. func main() {
  41. // Parse the source file to extract ChainID constants from vaa/structs.go
  42. // This reads the Go source code and builds an Abstract Syntax Tree (AST)
  43. // to programmatically find all ChainID constant declarations
  44. fset := token.NewFileSet()
  45. node, err := parser.ParseFile(fset, "vaa/structs.go", nil, parser.ParseComments)
  46. if err != nil {
  47. log.Fatal(err)
  48. }
  49. // Stores each ChainID declared as a constant in vaa/structs.go
  50. // Each ChainInfo contains the chain name, numeric value, and Go constant name
  51. var chains []ChainInfo
  52. // Walk the AST to find ChainID constants
  53. // This traverses the parsed Go code looking for constant declarations
  54. // that have the type "ChainID"
  55. ast.Inspect(node, func(n ast.Node) bool {
  56. switch x := n.(type) {
  57. case *ast.GenDecl:
  58. if x.Tok == token.CONST {
  59. for _, spec := range x.Specs {
  60. if vspec, ok := spec.(*ast.ValueSpec); ok {
  61. // Check if this is a ChainID constant by examining the type
  62. if vspec.Type != nil {
  63. if ident, ok := vspec.Type.(*ast.Ident); ok && ident.Name == "ChainID" {
  64. for i, name := range vspec.Names {
  65. if name.Name == "ChainIDUnset" {
  66. // Skip the unset value (ChainIDUnset = 0)
  67. // This is a special case that shouldn't be included in generated methods
  68. continue
  69. }
  70. // Extract the numeric value from the constant declaration
  71. var value int
  72. if len(vspec.Values) > i {
  73. if basic, ok := vspec.Values[i].(*ast.BasicLit); ok {
  74. if v, err := strconv.Atoi(basic.Value); err == nil {
  75. value = v
  76. }
  77. }
  78. }
  79. // Extract the chain name from the constant name
  80. // e.g., "ChainIDEthereum" -> "Ethereum"
  81. chainName := strings.TrimPrefix(name.Name, "ChainID")
  82. // Convert to lowercase for string representation
  83. // e.g., "Ethereum" -> "ethereum"
  84. chainNameLower := strings.ToLower(chainName)
  85. // Handle special naming for testnet chains
  86. // Separate alt-EVM testnet names with underscores.
  87. // e.g. `PolygonSepolia` --> `polygon_sepolia`.
  88. // (Don't match on "sepolia" itself though.)
  89. if strings.HasSuffix(chainNameLower, "sepolia") && len(chainNameLower) > len("sepolia") {
  90. chainNameLower = fmt.Sprintf("%s_sepolia", strings.TrimSuffix(chainNameLower, "sepolia"))
  91. }
  92. // Store the chain information for code generation
  93. chains = append(chains, ChainInfo{
  94. Name: chainNameLower, // String representation (e.g., "ethereum")
  95. Value: value, // Numeric ID (e.g., 2)
  96. GoName: name.Name, // Go constant name (e.g., "ChainIDEthereum")
  97. })
  98. }
  99. }
  100. }
  101. }
  102. }
  103. }
  104. }
  105. return true
  106. })
  107. // Sort by numeric value for consistent output
  108. // This ensures the generated code has chains in the same order as the constants
  109. sort.Slice(chains, func(i, j int) bool {
  110. return chains[i].Value < chains[j].Value
  111. })
  112. // Generate the code template for the output file
  113. // This template creates three methods:
  114. // 1. String() - converts ChainID to string representation
  115. // 2. ChainIDFromString() - converts string to ChainID
  116. // 3. GetAllNetworkIDs() - returns slice of all known ChainIDs
  117. // NOTE: This should follow gofumpt formatting in order to pass CI checks.
  118. tmpl := `// Code generated by go generate; DO NOT EDIT.
  119. package vaa
  120. import (
  121. "fmt"
  122. "strings"
  123. )
  124. // String returns the string representation of the ChainID
  125. func (c ChainID) String() string {
  126. switch c {
  127. case ChainIDUnset:
  128. return "unset"
  129. {{- range .Chains }}
  130. case {{ .GoName }}:
  131. return "{{ .Name }}"
  132. {{- end }}
  133. default:
  134. return fmt.Sprintf("unknown chain ID: %d", c)
  135. }
  136. }
  137. // ChainIDFromString converts from a chain's full name to its corresponding ChainID.
  138. func ChainIDFromString(s string) (ChainID, error) {
  139. s = strings.ToLower(s)
  140. switch s {
  141. {{- range .Chains }}
  142. case "{{ .Name }}":
  143. return {{ .GoName }}, nil
  144. {{- end }}
  145. default:
  146. return ChainIDUnset, fmt.Errorf("unknown chain ID: %s", s)
  147. }
  148. }
  149. // GetAllNetworkIDs returns all known ChainIDs
  150. func GetAllNetworkIDs() []ChainID {
  151. return []ChainID{
  152. {{- range .Chains }}
  153. {{ .GoName }},
  154. {{- end }}
  155. }
  156. }
  157. `
  158. // Write the generated code to the output file
  159. outfile := "vaa/chainid_generated.go"
  160. t, err := template.New("chainid").Parse(tmpl)
  161. if err != nil {
  162. log.Fatal(err)
  163. }
  164. output, err := os.Create(outfile)
  165. if err != nil {
  166. log.Fatal(err)
  167. }
  168. defer output.Close()
  169. // Execute the template with the extracted chain information
  170. err = t.Execute(output, struct {
  171. Chains []ChainInfo
  172. }{
  173. Chains: chains,
  174. })
  175. if err != nil {
  176. log.Fatal(err)
  177. }
  178. fmt.Printf("Generated %s with %d chains\n", outfile, len(chains))
  179. }