-
Notifications
You must be signed in to change notification settings - Fork 101
feat(utils): include an address codec implementation #665
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
Open
srdtrk
wants to merge
4
commits into
main
Choose a base branch
from
serdar/322-default-addrcdc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
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 | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 ourevmd
applicationThere was a problem hiding this comment.
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
There was a problem hiding this comment.
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 oneThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vladjdk thoughts?