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

support address codec implementation #16485

Merged
merged 17 commits into from
Feb 20, 2025
5 changes: 5 additions & 0 deletions .changeset/forty-taxis-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

address codec implementation #added
24 changes: 24 additions & 0 deletions core/capabilities/ccip/ccipevm/addresscodec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ccipevm

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
)

type AddressCodec struct{}

func (a AddressCodec) AddressBytesToString(addr []byte) (string, error) {
if len(addr) != common.AddressLength {
return "", fmt.Errorf("invalid EVM address length, expected %v, got %d", common.AddressLength, len(addr))
}

return common.BytesToAddress(addr).Hex(), nil
}

func (a AddressCodec) AddressStringToBytes(addr string) ([]byte, error) {
if !common.IsHexAddress(addr) {
return nil, fmt.Errorf("invalid EVM address %s", addr)
}
return common.HexToAddress(addr).Bytes(), nil
}
55 changes: 55 additions & 0 deletions core/capabilities/ccip/ccipevm/addresscodec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ccipevm

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestAddressBytesToString(t *testing.T) {
addressCodec := AddressCodec{}
addr := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}
want := "0x000102030405060708090a0b0c0d0e0f10111213"
got, err := addressCodec.AddressBytesToString(addr)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestAddressStringToBytes(t *testing.T) {
addressCodec := AddressCodec{}
addr := "0x000102030405060708090a0b0c0d0e0f10111213"
want := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}
got, err := addressCodec.AddressStringToBytes(addr)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestInvalidAddressBytesToString(t *testing.T) {
addressCodec := AddressCodec{}
addr := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12}
_, err := addressCodec.AddressBytesToString(addr)
require.Error(t, err)
}

func TestInvalidAddressStringToBytes(t *testing.T) {
addressCodec := AddressCodec{}
addr := "0x000102030405060708090a0b0c0d0e0f1011121"
_, err := addressCodec.AddressStringToBytes(addr)
require.Error(t, err)
}

func TestValidEVMAddress(t *testing.T) {
addressCodec := AddressCodec{}
addr := []byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}
want := "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
got, err := addressCodec.AddressBytesToString(addr)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestInvalidHexString(t *testing.T) {
addressCodec := AddressCodec{}
addr := "0xZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
_, err := addressCodec.AddressStringToBytes(addr)
require.Error(t, err)
}
24 changes: 24 additions & 0 deletions core/capabilities/ccip/ccipsolana/addresscodec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ccipsolana

import (
"fmt"

"github.com/gagliardetto/solana-go"
)

type AddressCodec struct{}

func (a AddressCodec) AddressBytesToString(addr []byte) (string, error) {
if len(addr) != solana.PublicKeyLength {
return "", fmt.Errorf("invalid SVM address length, expected %v, got %d", solana.PublicKeyLength, len(addr))
}
return solana.PublicKeyFromBytes(addr).String(), nil
}

func (a AddressCodec) AddressStringToBytes(addr string) ([]byte, error) {
pk, err := solana.PublicKeyFromBase58(addr)
if err != nil {
return nil, fmt.Errorf("failed to decode SVM address '%s': %w", addr, err)
}
return pk.Bytes(), nil
}
96 changes: 96 additions & 0 deletions core/capabilities/ccip/ccipsolana/addresscodec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package ccipsolana

import (
"encoding/hex"
"errors"
"testing"

"github.com/gagliardetto/solana-go"
"github.com/stretchr/testify/require"
)

func TestPublicKeyFromBytes(t *testing.T) {
tests := []struct {
name string
inHex string
isErr bool
expected string
}{
{
"empty",
"",
true,
solana.PublicKey{}.String(),
},
{
"smaller than required",
"010203040506",
true,
solana.PublicKey{}.String(),
},
{
"equal to 32 bytes",
"0102030405060102030405060102030405060102030405060102030405060101",
false,
solana.MustPublicKeyFromBase58("4wBqpZM9msxygzsdeLPq6Zw3LoiAxJk3GjtKPpqkcsi").String(),
},
{
"longer than required",
"0102030405060102030405060102030405060102030405060102030405060101FFFFFFFFFF",
true,
solana.PublicKey{}.String(),
},
}

codec := AddressCodec{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
bytes, err := hex.DecodeString(test.inHex)
require.NoError(t, err)

if test.isErr {
_, err := codec.AddressBytesToString(bytes)
require.Error(t, err)
} else {
actual, err := codec.AddressBytesToString(bytes)
require.NoError(t, err)
require.Equal(t, test.expected, actual)
}
})
}
}

func TestPublicKeyFromBase58(t *testing.T) {
tests := []struct {
name string
in string
expected []byte
expectedErr error
}{
{
"hand crafted",
"SerumkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
solana.MustPublicKeyFromBase58("SerumkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA").Bytes(),
nil,
},
{
"hand crafted error",
"SerkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
solana.PublicKey{}.Bytes(),
errors.New("invalid length, expected 32, got 30"),
},
}

codec := AddressCodec{}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := codec.AddressStringToBytes(test.in)
if test.expectedErr == nil {
require.NoError(t, err)
require.Equal(t, test.expected, actual)
} else {
require.Error(t, err)
}
})
}
}
6 changes: 2 additions & 4 deletions core/capabilities/ccip/ccipsolana/executecodec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (

"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/common/mocks"

"github.com/smartcontractkit/chainlink-ccip/mocks/pkg/types/ccipocr3"

"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
Expand Down Expand Up @@ -142,7 +140,7 @@ func TestExecutePluginCodecV1(t *testing.T) {
}

ctx := testutils.Context(t)
mockExtraDataCodec := &mocks.ExtraDataCodec{}
mockExtraDataCodec := mocks.NewExtraDataCodec(t)
mockExtraDataCodec.On("DecodeTokenAmountDestExecData", mock.Anything, mock.Anything).Return(map[string]any{
"destGasAmount": uint32(10),
}, nil)
Expand Down Expand Up @@ -181,7 +179,7 @@ func TestExecutePluginCodecV1(t *testing.T) {
}

func Test_DecodingExecuteReport(t *testing.T) {
mockExtraDataCodec := ccipocr3.NewMockExtraDataCodec(t)
mockExtraDataCodec := mocks.NewExtraDataCodec(t)
mockExtraDataCodec.On("DecodeTokenAmountDestExecData", mock.Anything, mock.Anything).Return(map[string]any{
"destGasAmount": uint32(10),
}, nil)
Expand Down
81 changes: 81 additions & 0 deletions core/capabilities/ccip/common/addresscodec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package common

import (
"fmt"

chainsel "github.com/smartcontractkit/chain-selectors"

cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
)

// ChainSpecificAddressCodec is an interface that defines the methods for encoding and decoding addresses
type ChainSpecificAddressCodec interface {
AddressBytesToString([]byte) (string, error)
AddressStringToBytes(string) ([]byte, error)
}

// AddressCodec is a struct that holds the chain specific address codecs
type AddressCodec struct {
EVMAddressCodec ChainSpecificAddressCodec
SolanaAddressCodec ChainSpecificAddressCodec
}

// AddressCodecParams is a struct that holds the parameters for creating a AddressCodec
type AddressCodecParams struct {
evmAddressCodec ChainSpecificAddressCodec
solanaAddressCodec ChainSpecificAddressCodec
}

// NewAddressCodecParams is a constructor for AddressCodecParams
func NewAddressCodecParams(evmAddressCodec ChainSpecificAddressCodec, solanaAddressCodec ChainSpecificAddressCodec) AddressCodecParams {
return AddressCodecParams{
evmAddressCodec: evmAddressCodec,
solanaAddressCodec: solanaAddressCodec,
}
}

// NewAddressCodec is a constructor for AddressCodec
func NewAddressCodec(params AddressCodecParams) AddressCodec {
return AddressCodec{
EVMAddressCodec: params.evmAddressCodec,
SolanaAddressCodec: params.solanaAddressCodec,
}
}

// AddressBytesToString converts an address from bytes to string
func (ac AddressCodec) AddressBytesToString(addr cciptypes.UnknownAddress, chainSelector cciptypes.ChainSelector) (string, error) {
family, err := chainsel.GetSelectorFamily(uint64(chainSelector))
if err != nil {
return "", fmt.Errorf("failed to get chain family for selector %d: %w", chainSelector, err)
}

switch family {
case chainsel.FamilyEVM:
return ac.EVMAddressCodec.AddressBytesToString(addr)

case chainsel.FamilySolana:
return ac.SolanaAddressCodec.AddressBytesToString(addr)

default:
return "", fmt.Errorf("unsupported family for address encode type %s", family)
}
}

// AddressStringToBytes converts an address from string to bytes
func (ac AddressCodec) AddressStringToBytes(addr string, chainSelector cciptypes.ChainSelector) (cciptypes.UnknownAddress, error) {
family, err := chainsel.GetSelectorFamily(uint64(chainSelector))
if err != nil {
return nil, fmt.Errorf("failed to get chain family for selector %d: %w", chainSelector, err)
}

switch family {
case chainsel.FamilyEVM:
return ac.EVMAddressCodec.AddressStringToBytes(addr)

case chainsel.FamilySolana:
return ac.SolanaAddressCodec.AddressStringToBytes(addr)

default:
return nil, fmt.Errorf("unsupported family for address decode type %s", family)
}
}
7 changes: 7 additions & 0 deletions core/capabilities/ccip/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strconv"
"time"

"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipsolana"
"golang.org/x/exp/maps"

"github.com/avast/retry-go/v4"
Expand Down Expand Up @@ -224,6 +226,11 @@ func (d *Delegate) ServicesForSpec(ctx context.Context, spec job.Job) (services
bootstrapperLocators,
hcr,
cciptypes.ChainSelector(homeChainChainSelector),
common.NewAddressCodec(
common.NewAddressCodecParams(
ccipevm.AddressCodec{},
ccipsolana.AddressCodec{},
)),
)
} else {
oracleCreator = oraclecreator.NewBootstrapOracleCreator(
Expand Down
Loading
Loading