Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

### FEATURES

- [\#665](https://github.com/cosmos/evm/pull/665) Add EvmCodec address codec implementation
- [\#346](https://github.com/cosmos/evm/pull/346) Add eth_createAccessList method and implementation
- [\#502](https://github.com/cosmos/evm/pull/502) Add block time in derived logs.
- [\#633](https://github.com/cosmos/evm/pull/633) go-ethereum metrics are now emitted on a separate server. default address: 127.0.0.1:8100.
Expand Down
56 changes: 56 additions & 0 deletions utils/address_codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package utils

import (
"strings"

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

"cosmossdk.io/core/address"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// EvmCodec defines an address codec for EVM compatible cosmos modules
type EvmCodec struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

This lgtm but I think this should live elsewhere (not in a utils package and also be piped into our evmd application

Copy link
Contributor

Choose a reason for hiding this comment

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

@vladjdk for thoughts on locations etc

Copy link
Member Author

Choose a reason for hiding this comment

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

If we are going to pipe this into our evmd. Then we might have a better case for #665 (comment) since if we used bech32, this codec would be a better drop-in replacement for the default cosmos sdk one

Copy link
Contributor

Choose a reason for hiding this comment

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

@vladjdk thoughts?

Bech32Prefix string
}

var _ address.Codec = (*EvmCodec)(nil)

// NewEvmCodec returns a new EvmCodec with the given bech32 prefix
func NewEvmCodec(prefix string) address.Codec {
return EvmCodec{prefix}
}

// StringToBytes decodes text to bytes using either hex or bech32 encoding
func (bc EvmCodec) StringToBytes(text string) ([]byte, error) {
if len(strings.TrimSpace(text)) == 0 {
return []byte{}, sdkerrors.ErrInvalidAddress.Wrap("empty address string is not allowed")
}

switch {
case common.IsHexAddress(text):
return common.HexToAddress(text).Bytes(), nil
case IsBech32Address(text):
hrp, bz, err := bech32.DecodeAndConvert(text)
if err != nil {
return nil, err
}
if hrp != bc.Bech32Prefix {
return nil, sdkerrors.ErrLogic.Wrapf("hrp does not match bech32 prefix: expected '%s' got '%s'", bc.Bech32Prefix, hrp)
}
if err := sdk.VerifyAddressFormat(bz); err != nil {
return nil, err
}
return bz, nil
default:
return nil, sdkerrors.ErrUnknownAddress.Wrapf("unknown address format: %s", text)
}
}

// BytesToString encodes bytes to EIP55-compliant hex string representation of the address
func (bc EvmCodec) BytesToString(bz []byte) (string, error) {
return common.BytesToAddress(bz).Hex(), nil
}
183 changes: 183 additions & 0 deletions utils/address_codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package utils_test

import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"

"github.com/cosmos/evm/utils"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

func TestStringToBytes(t *testing.T) {
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")
addrBz := common.HexToAddress(hex).Bytes()

testCases := []struct {
name string
cdcPrefix string
input string
expBz []byte
expErr error
}{
{
"success: valid bech32 address",
"cosmos",
bech32,
addrBz,
nil,
},
{
"success: valid hex address",
"cosmos",
hex,
addrBz,
nil,
},
{
"failure: invalid bech32 address (wrong prefix)",
"evmos",
bech32,
nil,
sdkerrors.ErrLogic.Wrapf("hrp does not match bech32 prefix: expected '%s' got '%s'", "evmos", "cosmos"),
},
{
"failure: invalid bech32 address (too long)",
"cosmos",
"cosmos10jmp6sgh4cc6zt3e8gw05wavvejgr5pwsjskvvv", // extra char at the end
nil,
sdkerrors.ErrUnknownAddress,
},
{
"failure: invalid bech32 address (invalid format)",
"cosmos",
"cosmos10jmp6sgh4cc6zt3e8gw05wavvejgr5pwsjskv", // missing last char
nil,
sdkerrors.ErrUnknownAddress,
},
{
"failure: invalid hex address (odd length)",
"cosmos",
"0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02", // missing last char
nil,
sdkerrors.ErrUnknownAddress,
},
{
"failure: invalid hex address (even length)",
"cosmos",
"0x7cB61D4117AE31a12E393a1Cfa3BaC666481D0", // missing last 2 char
nil,
sdkerrors.ErrUnknownAddress,
},
{
"failure: invalid hex address (too long)",
"cosmos",
"0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E00", // extra 2 char at the end
nil,
sdkerrors.ErrUnknownAddress,
},
{
"failure: empty string",
"cosmos",
"",
nil,
sdkerrors.ErrInvalidAddress.Wrap("empty address string is not allowed"),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cdc := utils.NewEvmCodec(tc.cdcPrefix)
bz, err := cdc.StringToBytes(tc.input)
if tc.expErr == nil {
require.NoError(t, err)
require.Equal(t, tc.expBz, bz)
} else {
require.ErrorContains(t, err, tc.expErr.Error())
}
})
}
}

func TestBytesToString(t *testing.T) {
// Keep the same fixtures as your StringToBytes test
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")

addrBz := common.HexToAddress(hex).Bytes() // 20 bytes
zeroAddr := common.Address{}.Hex() // "0x000..."

// Helper codec (used only where we want to derive bytes from the bech32 string)
cdc := utils.NewEvmCodec("cosmos")

type tc struct {
name string
input func() []byte
expHex string
}

testCases := []tc{
{
name: "success: from 20-byte input (hex-derived)",
input: func() []byte {
return addrBz
},
expHex: common.HexToAddress(hex).Hex(), // checksummed
},
{
name: "success: from bech32-derived bytes",
input: func() []byte {
bz, err := cdc.StringToBytes(bech32)
require.NoError(t, err)
require.Len(t, bz, 20)
return bz
},
expHex: common.HexToAddress(hex).Hex(), // same address as above
},
{
name: "success: empty slice -> zero address",
input: func() []byte {
return []byte{}
},
expHex: zeroAddr,
},
{
name: "success: shorter than 20 bytes -> left-padded to zeroes",
input: func() []byte {
// Drop first byte -> 19 bytes; common.BytesToAddress pads on the left
return addrBz[1:]
},
expHex: common.BytesToAddress(addrBz[1:]).Hex(),
},
{
name: "success: longer than 20 bytes -> rightmost 20 used",
input: func() []byte {
// Prepend one byte; common.BytesToAddress will use last 20 bytes (which are addrBz)
return append([]byte{0xAA}, addrBz...)
},
expHex: common.BytesToAddress(append([]byte{0xAA}, addrBz...)).Hex(),
},
{
name: "success: much longer (32 bytes) -> rightmost 20 used",
input: func() []byte {
prefix := make([]byte, 12) // 12 + 20 = 32
return append(prefix, addrBz...)
},
expHex: common.BytesToAddress(append(make([]byte, 12), addrBz...)).Hex(),
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
codec := utils.NewEvmCodec("cosmos")

got, err := codec.BytesToString(tc.input())
require.NoError(t, err)
require.Equal(t, tc.expHex, got)
})
}
}
7 changes: 7 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
)

Expand Down Expand Up @@ -127,6 +128,12 @@ func IsSupportedKey(pubkey cryptotypes.PubKey) bool {
}
}

// IsBech32Address checks if the address is a valid bech32 address.
func IsBech32Address(address string) bool {
_, _, err := bech32.DecodeAndConvert(address)
return err == nil
}

// GetAccAddressFromBech32 returns the sdk.Account address of given address,
// while also changing bech32 human readable prefix (HRP) to the value set on
// the global sdk.Config (eg: `evmos`).
Expand Down
59 changes: 54 additions & 5 deletions utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
hex = "0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E"
bech32 = "cosmos10jmp6sgh4cc6zt3e8gw05wavvejgr5pwsjskvv"
)

func TestIsSupportedKeys(t *testing.T) {
testCases := []struct {
name string
Expand Down Expand Up @@ -86,6 +91,53 @@ func TestIsSupportedKeys(t *testing.T) {
}
}

func TestIsBech32Address(t *testing.T) {
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")

testCases := []struct {
name string
address string
expResp bool
}{
{
"blank bech32 address",
" ",
false,
},
{
"invalid bech32 address",
"evmos",
false,
},
{
"invalid address bytes",
"cosmos1123",
false,
},
{
"evmos address",
"evmos1ltzy54ms24v590zz37r2q9hrrdcc8eslndsqwv",
true,
},
{
"cosmos address",
"cosmos1qql8ag4cluz6r4dz28p3w00dnc9w8ueulg2gmc",
true,
},
{
"osmosis address",
"osmo1qql8ag4cluz6r4dz28p3w00dnc9w8ueuhnecd2",
true,
},
}

for _, tc := range testCases {
isValid := utils.IsBech32Address(tc.address)
require.Equal(t, tc.expResp, isValid, tc.name)
}
}

func TestGetAccAddressFromBech32(t *testing.T) {
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")
Expand Down Expand Up @@ -116,8 +168,8 @@ func TestGetAccAddressFromBech32(t *testing.T) {
},
{
"evmos address",
"cosmos1qql8ag4cluz6r4dz28p3w00dnc9w8ueulg2gmc",
"cosmos1qql8ag4cluz6r4dz28p3w00dnc9w8ueulg2gmc",
"evmos1ltzy54ms24v590zz37r2q9hrrdcc8eslndsqwv",
"cosmos1ltzy54ms24v590zz37r2q9hrrdcc8esl3vpw5y",
false,
},
{
Expand Down Expand Up @@ -254,9 +306,6 @@ func TestAddressConversion(t *testing.T) {
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")

hex := "0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E"
bech32 := "cosmos10jmp6sgh4cc6zt3e8gw05wavvejgr5pwsjskvv"

require.Equal(t, bech32, utils.Bech32StringFromHexAddress(hex))
gotAddr, err := utils.HexAddressFromBech32String(bech32)
require.NoError(t, err)
Expand Down
Loading