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

Refactor with family specific chain selector support and added solana #70

Merged
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
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

CCIP uses its own set of chain selectors represented by uint64 to identify blockchains. This repository contains a
mapping between the custom chain identifiers (`chainSelectorId`) chain names and the chain identifiers
used by the blockchains themselves (`chainId`).
used by the blockchains themselves (`chainId`). For solana we use the base58 encoded genesis hash as the chain id.

Please refer to the [official documentation](https://docs.chain.link/ccip/supported-networks) to learn more about
supported networks and their selectors.
Expand All @@ -19,6 +19,13 @@ import (
)

func main() {
// -------------------Chains agnostic --------------------:

// Getting chain family based on selector
family, err := GetSelectorFamily(2664363617261496610)

// -------------------For EVM chains--------------------

// Getting selector based on ChainId
selector, err := chainselectors.SelectorFromChainId(420)

Expand All @@ -34,8 +41,22 @@ func main() {
// Accessing mapping directly
lookupChainId := uint64(1337)
if chainSelector, exists := chainselectors.EvmChainIdToChainSelector()[lookupChainId]; exists {
fmt.Println("Found chain selector for chain", lookupChainId, ":", chainSelector)
fmt.Println("Found evm chain selector for chain", lookupChainId, ":", chainSelector)
}

// -------------------Solana Chain --------------------:

// Getting chain family based on selector
family, err := SolanaNameFromChainId("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d")

// Getting chain id from chain selector
chainId, err := chainselectors.SolanaChainIdFromSelector(124615329519749607)

// Accessing mapping directly
lookupChainId := "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d"
if chainSelector, exists:= chainselectors.SolanaChainIdToChainSelector()[lookupChainId]; exists {
fmt.Println("Found solana chain selector for chain", lookupChainId, ":", chainSelector)
}
}
```

Expand Down
139 changes: 139 additions & 0 deletions evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package chain_selectors

import (
_ "embed"
"fmt"
"strconv"

"gopkg.in/yaml.v3"
)

//go:generate go run genchains_evm.go

//go:embed selectors.yml
var selectorsYml []byte

//go:embed test_selectors.yml
var testSelectorsYml []byte

type ChainDetails struct {
ChainSelector uint64 `yaml:"selector"`
ChainName string `yaml:"name"`
}

var (
evmSelectorsMap = parseYml(selectorsYml)
evmTestSelectorsMap = parseYml(testSelectorsYml)
evmChainIdToChainSelector = loadAllEVMSelectors()
evmChainsBySelector = make(map[uint64]Chain)
evmChainsByEvmChainID = make(map[uint64]Chain)
)

func init() {
for _, ch := range ALL {
evmChainsBySelector[ch.Selector] = ch
evmChainsByEvmChainID[ch.EvmChainID] = ch
}
}

func loadAllEVMSelectors() map[uint64]ChainDetails {
output := make(map[uint64]ChainDetails, len(evmSelectorsMap)+len(evmTestSelectorsMap))
for k, v := range evmSelectorsMap {
output[k] = v
}
for k, v := range evmTestSelectorsMap {
output[k] = v
}
return output
}

func parseYml(ymlFile []byte) map[uint64]ChainDetails {
type ymlData struct {
SelectorsByEvmChainId map[uint64]ChainDetails `yaml:"selectors"`
}

var data ymlData
err := yaml.Unmarshal(ymlFile, &data)
if err != nil {
panic(err)
}

return data.SelectorsByEvmChainId
}

func EvmChainIdToChainSelector() map[uint64]uint64 {
copyMap := make(map[uint64]uint64, len(evmChainIdToChainSelector))
for k, v := range evmChainIdToChainSelector {
copyMap[k] = v.ChainSelector
}
return copyMap
}

func ChainIdFromSelector(chainSelectorId uint64) (uint64, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: mark this function as deprecated and create a parallel func EVMChainIDFromChainSelector to make it clear this is EVM which should be used instead. The "chain ID" concept should now be abstract and be more scoped with the chain family that you want a chain ID for. Feels like someone can easily use this without intending to and run into issues.

func EVMChainIDFromSelector(chainSelector uint64) (*big.Int, error) {
// .. impl
// btw, chain IDs are technically uint256
}

// Deprecated: use EVMChainIDFromSelector instead
func ChainIdFromSelector(chainSelectorId uint64) (uint64, error) {
// ...
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it might be easier conceptually to just leave it as "Chain" is an EVM chain and then otherwise have a family prefix like SolXXX?

for k, v := range evmChainIdToChainSelector {
if v.ChainSelector == chainSelectorId {
return k, nil
}
}
return 0, fmt.Errorf("chain not found for chain selector %d", chainSelectorId)
}

func SelectorFromChainId(chainId uint64) (uint64, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here re: deprecation see above.

if chainSelectorId, exist := evmChainIdToChainSelector[chainId]; exist {
return chainSelectorId.ChainSelector, nil
}
return 0, fmt.Errorf("chain selector not found for chain %d", chainId)
}

func NameFromChainId(chainId uint64) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here re: deprecation

details, exist := evmChainIdToChainSelector[chainId]
if !exist {
return "", fmt.Errorf("chain name not found for chain %d", chainId)
}
if details.ChainName == "" {
return strconv.FormatUint(chainId, 10), nil
}
return details.ChainName, nil
}

func ChainIdFromName(name string) (uint64, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here re: deprecation

for k, v := range evmChainIdToChainSelector {
if v.ChainName == name {
return k, nil
}
}
chainId, err := strconv.ParseUint(name, 10, 64)
if err == nil {
if details, exist := evmChainIdToChainSelector[chainId]; exist && details.ChainName == "" {
return chainId, nil
}
}
return 0, fmt.Errorf("chain not found for name %s", name)
}

func TestChainIds() []uint64 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here re: deprecation

chainIds := make([]uint64, 0, len(evmTestSelectorsMap))
for k := range evmTestSelectorsMap {
chainIds = append(chainIds, k)
}
return chainIds
}

func ChainBySelector(sel uint64) (Chain, bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here re: deprecation

ch, exists := evmChainsBySelector[sel]
return ch, exists
}

func ChainByEvmChainID(evmChainID uint64) (Chain, bool) {
ch, exists := evmChainsByEvmChainID[evmChainID]
return ch, exists
}

func IsEvm(chainSel uint64) (bool, error) {
_, exists := ChainBySelector(chainSel)
if !exists {
return false, fmt.Errorf("chain %d not found", chainSel)
}
// We always return true since only evm chains are supported atm.
return true, nil
Comment on lines +137 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect, should return false if its solana

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the condition is a bit confusing but still looks correct to me. ChainBySelector is EVM only, so if it does exist there it is EVM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops you're right, misread ChainBySelector already. Was already thinking it'd be chain agnostic hehe

}
File renamed without changes.
21 changes: 16 additions & 5 deletions selectors_test.go → evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chain_selectors

import (
"math/rand"
"strconv"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -20,8 +21,8 @@ func TestNoSameChainSelectorsAreGenerated(t *testing.T) {
}

func TestNoOverlapBetweenRealAndTestChains(t *testing.T) {
for k, _ := range selectorsMap {
_, exist := testSelectorsMap[k]
for k, _ := range evmSelectorsMap {
_, exist := evmTestSelectorsMap[k]
assert.False(t, exist, "Chain %d is duplicated between real and test chains", k)
}
}
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -113,10 +115,10 @@ func Test_ChainSelectors(t *testing.T) {

func Test_TestChainIds(t *testing.T) {
chainIds := TestChainIds()
assert.Equal(t, len(chainIds), len(testSelectorsMap), "Should return correct number of test chain ids")
assert.Equal(t, len(chainIds), len(evmTestSelectorsMap), "Should return correct number of test chain ids")

for _, chainId := range chainIds {
_, exist := testSelectorsMap[chainId]
_, exist := evmTestSelectorsMap[chainId]
assert.True(t, exist)
}
}
Expand Down Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion genchains.go → genchains_evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
113 changes: 113 additions & 0 deletions genchains_solana.go
Original file line number Diff line number Diff line change
@@ -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
}
File renamed without changes.
21 changes: 21 additions & 0 deletions generated_chains_solana.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/smartcontractkit/chain-selectors
go 1.20

require (
github.com/mr-tron/base58 v1.2.0
github.com/stretchr/testify v1.8.4
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
Loading
Loading