diff --git a/evm.go b/evm.go index 2cfdac3..877caf1 100644 --- a/evm.go +++ b/evm.go @@ -8,7 +8,7 @@ import ( "gopkg.in/yaml.v3" ) -//go:generate go run genchains.go +//go:generate go run genchains_evm.go //go:embed selectors.yml var selectorsYml []byte @@ -16,7 +16,7 @@ var selectorsYml []byte //go:embed test_selectors.yml var testSelectorsYml []byte -type chainDetails struct { +type ChainDetails struct { ChainSelector uint64 `yaml:"selector"` ChainName string `yaml:"name"` } @@ -36,8 +36,8 @@ func init() { } } -func loadAllEVMSelectors() map[uint64]chainDetails { - output := make(map[uint64]chainDetails, len(evmSelectorsMap)+len(evmTestSelectorsMap)) +func loadAllEVMSelectors() map[uint64]ChainDetails { + output := make(map[uint64]ChainDetails, len(evmSelectorsMap)+len(evmTestSelectorsMap)) for k, v := range evmSelectorsMap { output[k] = v } @@ -47,9 +47,9 @@ func loadAllEVMSelectors() map[uint64]chainDetails { return output } -func parseYml(ymlFile []byte) map[uint64]chainDetails { +func parseYml(ymlFile []byte) map[uint64]ChainDetails { type ymlData struct { - SelectorsByEvmChainId map[uint64]chainDetails `yaml:"selectors"` + SelectorsByEvmChainId map[uint64]ChainDetails `yaml:"selectors"` } var data ymlData diff --git a/evm_test.go b/evm_test.go index 5125cd8..6691f74 100644 --- a/evm_test.go +++ b/evm_test.go @@ -2,6 +2,7 @@ package chain_selectors import ( "math/rand" + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -51,9 +52,10 @@ func TestAllChainSelectorsHaveFamilies(t *testing.T) { for _, ch := range ALL { family, err := GetSelectorFamily(ch.Selector) require.NoError(t, err, - "Family not found for selector %d (chain id %d, name %s), please update selector_families.yml with the appropriate chain family for this chain", + "Family not found for selector %d (chain id %d, name %s), please update selector.yml with the appropriate chain family for this chain", ch.Selector, ch.EvmChainID, ch.Name) require.NotEmpty(t, family) + require.Equal(t, FamilyEVM, family) } } @@ -224,3 +226,12 @@ func Test_IsEvm(t *testing.T) { assert.False(t, isEvm) }) } + +func Test_EVMGetChainDetailsByChainIDAndFamily(t *testing.T) { + for k, v := range evmChainIdToChainSelector { + strChainID := strconv.FormatUint(k, 10) + details, err := GetChainDetailsByChainIDAndFamily(strChainID, FamilyEVM) + assert.NoError(t, err) + assert.Equal(t, v, details) + } +} diff --git a/genchains_evm.go b/genchains_evm.go index 09742c3..62dafdc 100644 --- a/genchains_evm.go +++ b/genchains_evm.go @@ -16,7 +16,7 @@ import ( chain_selectors "github.com/smartcontractkit/chain-selectors" ) -const filename = "generated_chains.go" +const filename = "generated_chains_evm.go" type chain struct { EvmChainID uint64 diff --git a/genchains_solana.go b/genchains_solana.go new file mode 100644 index 0000000..85937ed --- /dev/null +++ b/genchains_solana.go @@ -0,0 +1,113 @@ +//go:build ignore + +package main + +import ( + "bytes" + "fmt" + "go/format" + "html/template" + "os" + "sort" + "strconv" + "strings" + "unicode" + + chain_selectors "github.com/smartcontractkit/chain-selectors" +) + +const filename = "generated_chains_solana.go" + +type chain struct { + ChainID string + Selector uint64 + Name string + VarName string +} + +var chainTemplate, _ = template.New("").Parse(`// Code generated by go generate please DO NOT EDIT +package chain_selectors + +type SolanaChain struct { + ChainID string + Selector uint64 + Name string + VarName string +} + +var ( +{{ range . }} + {{.VarName}} = SolanaChain{ChainID: "{{ .ChainID }}", Selector: {{ .Selector }}, Name: "{{ .Name }}"}{{ end }} +) + +var SolanaALL = []SolanaChain{ +{{ range . }}{{ .VarName }}, +{{ end }} +} + +`) + +func main() { + src, err := genChainsSourceCode() + if err != nil { + panic(err) + } + + formatted, err := format.Source([]byte(src)) + if err != nil { + panic(err) + } + + existingContent, err := os.ReadFile(filename) + if err != nil { + panic(err) + } + + if string(existingContent) == string(formatted) { + fmt.Println("no changes detected") + return + } + + err = os.WriteFile(filename, formatted, 0644) + if err != nil { + panic(err) + } +} + +func genChainsSourceCode() (string, error) { + var wr = new(bytes.Buffer) + chains := make([]chain, 0) + + for ChainID, chainSel := range chain_selectors.SolanaChainIdToChainSelector() { + name, err := chain_selectors.SolanaNameFromChainId(ChainID) + if err != nil { + return "", err + } + + chains = append(chains, chain{ + ChainID: ChainID, + Selector: chainSel, + Name: name, + VarName: toVarName(name, chainSel), + }) + } + + sort.Slice(chains, func(i, j int) bool { return chains[i].VarName < chains[j].VarName }) + if err := chainTemplate.ExecuteTemplate(wr, "", chains); err != nil { + return "", err + } + return wr.String(), nil +} + +func toVarName(name string, chainSel uint64) string { + const unnamed = "TEST" + x := strings.ReplaceAll(name, "-", "_") + x = strings.ToUpper(x) + if len(x) > 0 && unicode.IsDigit(rune(x[0])) { + x = unnamed + "_" + x + } + if len(x) == 0 { + x = unnamed + "_" + strconv.FormatUint(chainSel, 10) + } + return x +} diff --git a/generated_chains_solana.go b/generated_chains_solana.go new file mode 100644 index 0000000..ba261dd --- /dev/null +++ b/generated_chains_solana.go @@ -0,0 +1,21 @@ +// Code generated by go generate please DO NOT EDIT +package chain_selectors + +type SolanaChain struct { + ChainID string + Selector uint64 + Name string + VarName string +} + +var ( + SOLANA_DEVNET = SolanaChain{ChainID: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", Selector: 16423721717087811551, Name: "solana-devnet"} + SOLANA_MAINNET = SolanaChain{ChainID: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", Selector: 124615329519749607, Name: "solana-mainnet"} + SOLANA_TESTNET = SolanaChain{ChainID: "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY", Selector: 6302590918974934319, Name: "solana-testnet"} +) + +var SolanaALL = []SolanaChain{ + SOLANA_DEVNET, + SOLANA_MAINNET, + SOLANA_TESTNET, +} diff --git a/selectors.go b/selectors.go index 5c1374e..eb9ddb4 100644 --- a/selectors.go +++ b/selectors.go @@ -1,6 +1,9 @@ package chain_selectors -import "fmt" +import ( + "fmt" + "strconv" +) const ( FamilyEVM = "evm" @@ -11,11 +14,43 @@ const ( ) func GetSelectorFamily(selector uint64) (string, error) { - if _, exist := evmChainIdToChainSelector[selector]; exist { + // check EVM + _, exist := evmChainsBySelector[selector] + if exist { return FamilyEVM, nil } - if _, exist := solanaChainIdBySelector[selector]; exist { + + // check solana + _, exist = solanaChainIdBySelector[selector] + if exist { return FamilySolana, nil } + return "", fmt.Errorf("unknown chain selector %d", selector) } + +func GetChainDetailsByChainIDAndFamily(chainID string, family string) (ChainDetails, error) { + switch family { + case FamilyEVM: + evmChainId, err := strconv.ParseUint(chainID, 10, 64) + if err != nil { + return ChainDetails{}, fmt.Errorf("invalid chain id %s for %s", chainID, family) + } + + details, exist := evmChainIdToChainSelector[evmChainId] + if !exist { + return ChainDetails{}, fmt.Errorf("invalid chain id %s for %s", chainID, family) + } + + return details, nil + case FamilySolana: + details, exist := solanaSelectorsMap[chainID] + if !exist { + return ChainDetails{}, fmt.Errorf("invalid chain id %s for %s", chainID, family) + } + + return details, nil + default: + return ChainDetails{}, fmt.Errorf("family %s is not yet support", family) + } +} diff --git a/solana.go b/solana.go index e6c1f17..5bf4767 100644 --- a/solana.go +++ b/solana.go @@ -2,10 +2,13 @@ package chain_selectors import ( _ "embed" + "fmt" "gopkg.in/yaml.v3" ) +//go:generate go run genchains_solana.go + //go:embed selectors_solana.yml var solanaSelectorsYml []byte @@ -20,9 +23,9 @@ func init() { } } -func parseSolanaYml(ymlFile []byte) map[string]chainDetails { +func parseSolanaYml(ymlFile []byte) map[string]ChainDetails { type ymlData struct { - SelectorsBySolanaChainId map[string]chainDetails `yaml:"selectors"` + SelectorsBySolanaChainId map[string]ChainDetails `yaml:"selectors"` } var data ymlData @@ -32,3 +35,22 @@ func parseSolanaYml(ymlFile []byte) map[string]chainDetails { } return data.SelectorsBySolanaChainId } + +func SolanaChainIdToChainSelector() map[string]uint64 { + copyMap := make(map[string]uint64, len(solanaSelectorsMap)) + for k, v := range solanaSelectorsMap { + copyMap[k] = v.ChainSelector + } + return copyMap +} + +func SolanaNameFromChainId(chainId string) (string, error) { + details, exist := solanaSelectorsMap[chainId] + if !exist { + return "", fmt.Errorf("chain name not found for chain %v", chainId) + } + if details.ChainName == "" { + return chainId, nil + } + return details.ChainName, nil +} diff --git a/solana_test.go b/solana_test.go index fefe432..1f229e1 100644 --- a/solana_test.go +++ b/solana_test.go @@ -1,9 +1,73 @@ package chain_selectors -import "testing" +import ( + "math/rand" + "testing" -func TestSolana(t *testing.T) { + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_YmlAreValid(t *testing.T) { + tests := []struct { + name string + chainSelector uint64 + chainsId string + expectErr bool + }{ + { + name: "solana-mainnet", + chainSelector: 124615329519749607, + chainsId: "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d", + expectErr: false, + }, + { + name: "solana-testnet", + chainSelector: 6302590918974934319, + chainsId: "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY", + expectErr: false, + }, + { + name: "solana-devnet", + chainSelector: 16423721717087811551, + chainsId: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG", + expectErr: false, + }, + { + name: "non-existing", + chainSelector: rand.Uint64(), + chainsId: "non-existing", + expectErr: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + name, err1 := SolanaNameFromChainId(test.chainsId) + if test.expectErr { + require.Error(t, err1) + return + } + require.NoError(t, err1) + assert.Equal(t, test.name, name) + }) + } +} + +func Test_SolanaChainSelectors(t *testing.T) { + for selector := range solanaChainIdBySelector { + family, err := GetSelectorFamily(selector) + require.NoError(t, err, + "selector %v should be returned as solana family, but received %v", + selector, err) + require.NotEmpty(t, family) + require.Equal(t, FamilySolana, family) + } +} + +func Test_SolanaGetChainDetailsByChainIDAndFamily(t *testing.T) { for k, v := range solanaSelectorsMap { - t.Logf("k: %s, v: %v", k, v) + details, err := GetChainDetailsByChainIDAndFamily(k, FamilySolana) + assert.NoError(t, err) + assert.Equal(t, v, details) } }