Skip to content

Commit

Permalink
support Solana wallet address in zetacore for SOL withdraw
Browse files Browse the repository at this point in the history
  • Loading branch information
ws4charlie committed Jul 19, 2024
1 parent 129c99b commit 9c31e68
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 28 deletions.
19 changes: 18 additions & 1 deletion pkg/chains/address.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package chains

import (
"errors"
"fmt"
"strings"

"github.com/btcsuite/btcutil"
eth "github.com/ethereum/go-ethereum/common"
"github.com/gagliardetto/solana-go"
"github.com/pkg/errors"
)

type Address string
Expand Down Expand Up @@ -87,6 +88,22 @@ func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Addre
return
}

// DecodeSolanaWalletAddress decodes a Solana wallet address from a given string
func DecodeSolanaWalletAddress(inputAddress string) (pk solana.PublicKey, err error) {
// decode the Base58 encoded address
pk, err = solana.PublicKeyFromBase58(inputAddress)
if err != nil {
return solana.PublicKey{}, errors.Wrapf(err, "error decoding solana wallet address %s", inputAddress)
}

// accept address that is generated from keypair
// reject off-curve address such as program derived address from 'findProgramAddress'
if !pk.IsOnCurve() {
return solana.PublicKey{}, fmt.Errorf("address %s is not on ed25519 curve", inputAddress)
}
return
}

// IsBtcAddressSupported returns true if the given BTC address is supported
func IsBtcAddressSupported(addr btcutil.Address) bool {
switch addr.(type) {
Expand Down
52 changes: 52 additions & 0 deletions pkg/chains/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,58 @@ func TestDecodeBtcAddress(t *testing.T) {
})
}

func Test_DecodeSolanaWalletAddress(t *testing.T) {
tests := []struct {
name string
address string
want string
fail bool
message string
}{
{
name: "should decode a valid Solana wallet address",
address: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
want: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
fail: false,
},
{
name: "should fail to decode an address with invalid length",
address: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveK",
want: "",
fail: true,
message: "error decoding solana wallet address",
},
{
name: "should fail to decode an invalid Base58 address",
address: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFl", // contains invalid character 'l'
want: "",
fail: true,
message: "error decoding solana wallet address",
},
{
name: "should fail to decode a program derived address (not on ed25519 curve)",
address: "9dcAyYG4bawApZocwZSyJBi9Mynf5EuKAJfifXdfkqik",
want: "",
fail: true,
message: "is not on ed25519 curve",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pk, err := DecodeSolanaWalletAddress(tt.address)
if tt.fail {
require.Error(t, err)
require.Contains(t, err.Error(), tt.message)
return
}

require.NoError(t, err)
require.Equal(t, tt.want, pk.String())
})
}
}

func Test_IsBtcAddressSupported_P2TR(t *testing.T) {
tests := []struct {
name string
Expand Down
16 changes: 12 additions & 4 deletions pkg/chains/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ func (chain Chain) IsExternalChain() bool {
// on EVM chain, it is 20Bytes
// on Bitcoin chain, it is P2WPKH address, []byte(bech32 encoded string)
func (chain Chain) EncodeAddress(b []byte) (string, error) {
if chain.Consensus == Consensus_ethereum {
switch chain.Consensus {
case Consensus_ethereum:
addr := ethcommon.BytesToAddress(b)
if addr == (ethcommon.Address{}) {
return "", fmt.Errorf("invalid EVM address")
}
return addr.Hex(), nil
} else if chain.Consensus == Consensus_bitcoin {
case Consensus_bitcoin:
addrStr := string(b)
chainParams, err := GetBTCChainParams(chain.ChainId)
if err != nil {
Expand All @@ -72,8 +73,15 @@ func (chain Chain) EncodeAddress(b []byte) (string, error) {
return "", fmt.Errorf("address is not for network %s", chainParams.Name)
}
return addrStr, nil
case Consensus_solana_consensus:
pk, err := DecodeSolanaWalletAddress(string(b))
if err != nil {
return "", err
}
return pk.String(), nil
default:
return "", fmt.Errorf("chain (%d) not supported", chain.ChainId)
}
return "", fmt.Errorf("chain (%d) not supported", chain.ChainId)
}

func (chain Chain) IsEVMChain() bool {
Expand Down Expand Up @@ -111,7 +119,7 @@ func IsEVMChain(chainID int64, additionalChains []Chain) bool {
// additionalChains is a list of additional chains to search from
// in practice, it is used in the protocol to dynamically support new chains without doing an upgrade
func IsBitcoinChain(chainID int64, additionalChains []Chain) bool {
return ChainIDInChainList(chainID, ChainListByConsensus(Consensus_bitcoin, additionalChains))
return ChainIDInChainList(chainID, ChainListByNetwork(Network_btc, additionalChains))
}

// IsSolanaChain returns true if the chain is a Solana chain
Expand Down
53 changes: 30 additions & 23 deletions pkg/chains/chain_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package chains_test

import (
"github.com/zeta-chain/zetacore/testutil/sample"
"testing"

"github.com/zeta-chain/zetacore/testutil/sample"

"github.com/btcsuite/btcd/chaincfg"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -146,43 +147,43 @@ func TestChain_EncodeAddress(t *testing.T) {
wantErr bool
}{
{
name: "should error if b is not a valid address on the bitcoin network",
chain: chains.Chain{
ChainName: chains.ChainName_btc_testnet,
ChainId: 18332,
Consensus: chains.Consensus_bitcoin,
},
name: "should error if b is not a valid address on the bitcoin network",
chain: chains.BitcoinTestnet,
b: []byte("bc1qk0cc73p8m7hswn8y2q080xa4e5pxapnqgp7h9c"),
want: "",
wantErr: true,
},
{
name: "should pass if b is a valid address on the network",
chain: chains.Chain{
ChainName: chains.ChainName_btc_mainnet,
ChainId: 8332,
Consensus: chains.Consensus_bitcoin,
},
name: "should pass if b is a valid address on the network",
chain: chains.BitcoinMainnet,
b: []byte("bc1qk0cc73p8m7hswn8y2q080xa4e5pxapnqgp7h9c"),
want: "bc1qk0cc73p8m7hswn8y2q080xa4e5pxapnqgp7h9c",
wantErr: false,
},
{
name: "should error if b is not a valid address on the evm network",
chain: chains.Chain{
ChainName: chains.ChainName_goerli_testnet,
ChainId: 5,
},
name: "should pass if b is a valid wallet address on the solana network",
chain: chains.SolanaMainnet,
b: []byte("DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw"),
want: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
wantErr: false,
},
{
name: "should error if b is not a valid Base58 address",
chain: chains.SolanaMainnet,
b: []byte("9G0P8HkKqegZ7B6cE2hGvkZjHjSH14WZXDNZQmwYLokAc"), // contains invalid digit '0'
want: "",
wantErr: true,
},
{
name: "should error if b is not a valid address on the evm network",
chain: chains.Ethereum,
b: ethcommon.Hex2Bytes("0x321"),
want: "",
wantErr: true,
},
{
name: "should pass if b is a valid address on the evm network",
chain: chains.Chain{
ChainName: chains.ChainName_goerli_testnet,
ChainId: 5,
},
name: "should pass if b is a valid address on the evm network",
chain: chains.Ethereum,
b: []byte("0x321"),
want: "0x0000000000000000000000000000003078333231",
wantErr: false,
Expand Down Expand Up @@ -294,6 +295,12 @@ func TestDecodeAddressFromChainID(t *testing.T) {
addr: "bc1qk0cc73p8m7hswn8y2q080xa4e5pxapnqgp7h9c",
want: []byte("bc1qk0cc73p8m7hswn8y2q080xa4e5pxapnqgp7h9c"),
},
{
name: "Solana",
chainID: chains.SolanaMainnet.ChainId,
addr: "DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw",
want: []byte("DCAK36VfExkPdAkYUQg6ewgxyinvcEyPLyHjRbmveKFw"),
},
{
name: "Non-supported chain",
chainID: 9999,
Expand Down

0 comments on commit 9c31e68

Please sign in to comment.