diff --git a/app/ante.go b/app/ante.go index f690e4d0..7a4be48f 100644 --- a/app/ante.go +++ b/app/ante.go @@ -231,7 +231,7 @@ func validateEthTxCheckTx( // validate sender/signature signer, err := ethTxMsg.VerifySig(chainID) if err != nil { - return sdk.ErrUnauthorized("signature verification failed").Result() + return sdk.ErrUnauthorized(fmt.Sprintf("signature verification failed: %s", err)).Result() } // validate account (nonce and balance checks) diff --git a/cmd/emintcli/main.go b/cmd/emintcli/main.go index a09ee935..a36d5e99 100644 --- a/cmd/emintcli/main.go +++ b/cmd/emintcli/main.go @@ -13,6 +13,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" emintkeys "github.com/cosmos/ethermint/keys" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + emintapp "github.com/cosmos/ethermint/app" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -48,7 +51,7 @@ func main() { sdkrpc.StatusCommand(), client.ConfigCmd(emintapp.DefaultCLIHome), queryCmd(cdc), - // TODO: Set up tx command + txCmd(cdc), // TODO: Set up rest routes (if included, different from web3 api) rpc.Web3RpcCmd(cdc), client.LineBreak, @@ -83,6 +86,28 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { return queryCmd } +func txCmd(cdc *amino.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + } + + txCmd.AddCommand( + bankcmd.SendTxCmd(cdc), + client.LineBreak, + authcmd.GetSignCommand(cdc), + client.LineBreak, + authcmd.GetBroadcastCommand(cdc), + authcmd.GetEncodeCommand(cdc), + client.LineBreak, + ) + + // add modules' tx commands + emintapp.ModuleBasics.AddTxCommands(txCmd, cdc) + + return txCmd +} + func initConfig(cmd *cobra.Command) error { home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) if err != nil { diff --git a/crypto/codec.go b/crypto/codec.go index 219585e2..a5ff5a7f 100644 --- a/crypto/codec.go +++ b/crypto/codec.go @@ -6,6 +6,12 @@ import ( var cryptoCodec = codec.New() +const ( + // Amino encoding names + PrivKeyAminoName = "crypto/PrivKeySecp256k1" + PubKeyAminoName = "crypto/PubKeySecp256k1" +) + func init() { RegisterCodec(cryptoCodec) } @@ -13,6 +19,7 @@ func init() { // RegisterCodec registers all the necessary types with amino for the given // codec. func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(PubKeySecp256k1{}, "crypto/PubKeySecp256k1", nil) - cdc.RegisterConcrete(PrivKeySecp256k1{}, "crypto/PrivKeySecp256k1", nil) + cdc.RegisterConcrete(PubKeySecp256k1{}, PubKeyAminoName, nil) + + cdc.RegisterConcrete(PrivKeySecp256k1{}, PrivKeyAminoName, nil) } diff --git a/crypto/encoding/amino.go b/crypto/encoding/amino.go new file mode 100644 index 00000000..cba9ae38 --- /dev/null +++ b/crypto/encoding/amino.go @@ -0,0 +1,35 @@ +package encoding + +import ( + emintcrypto "github.com/cosmos/ethermint/crypto" + amino "github.com/tendermint/go-amino" + tmcrypto "github.com/tendermint/tendermint/crypto" +) + +var cdc = amino.NewCodec() + +func init() { + RegisterAmino(cdc) +} + +// RegisterAmino registers all crypto related types in the given (amino) codec. +func RegisterAmino(cdc *amino.Codec) { + // These are all written here instead of + cdc.RegisterInterface((*tmcrypto.PubKey)(nil), nil) + cdc.RegisterConcrete(emintcrypto.PubKeySecp256k1{}, emintcrypto.PubKeyAminoName, nil) + + cdc.RegisterInterface((*tmcrypto.PrivKey)(nil), nil) + cdc.RegisterConcrete(emintcrypto.PrivKeySecp256k1{}, emintcrypto.PrivKeyAminoName, nil) +} + +// PrivKeyFromBytes unmarshalls emint private key from encoded bytes +func PrivKeyFromBytes(privKeyBytes []byte) (privKey tmcrypto.PrivKey, err error) { + err = cdc.UnmarshalBinaryBare(privKeyBytes, &privKey) + return +} + +// PubKeyFromBytes unmarshalls emint public key from encoded bytes +func PubKeyFromBytes(pubKeyBytes []byte) (pubKey tmcrypto.PubKey, err error) { + err = cdc.UnmarshalBinaryBare(pubKeyBytes, &pubKey) + return +} diff --git a/crypto/encoding/amino_test.go b/crypto/encoding/amino_test.go new file mode 100644 index 00000000..b946b2a9 --- /dev/null +++ b/crypto/encoding/amino_test.go @@ -0,0 +1,28 @@ +package encoding + +import ( + "testing" + + "github.com/stretchr/testify/require" + + emintcrypto "github.com/cosmos/ethermint/crypto" +) + +func TestKeyEncodingDecoding(t *testing.T) { + // Priv Key encoding and decoding + privKey, err := emintcrypto.GenerateKey() + require.NoError(t, err) + privBytes := privKey.Bytes() + + decodedPriv, err := PrivKeyFromBytes(privBytes) + require.NoError(t, err) + require.Equal(t, privKey, decodedPriv) + + // Pub key encoding and decoding + pubKey := privKey.PubKey() + pubBytes := pubKey.Bytes() + + decodedPub, err := PubKeyFromBytes(pubBytes) + require.NoError(t, err) + require.Equal(t, pubKey, decodedPub) +} diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index dceda0b8..14c2534b 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -13,7 +13,7 @@ import ( cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" - "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" + "github.com/cosmos/ethermint/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/types" bip39 "github.com/cosmos/go-bip39" diff --git a/crypto/keys/mintkey/mintkey.go b/crypto/keys/mintkey/mintkey.go new file mode 100644 index 00000000..544120bf --- /dev/null +++ b/crypto/keys/mintkey/mintkey.go @@ -0,0 +1,157 @@ +package mintkey + +import ( + "encoding/hex" + "fmt" + + "github.com/tendermint/crypto/bcrypt" + + emintEncoding "github.com/cosmos/ethermint/crypto/encoding" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/armor" + + "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" +) + +const ( + blockTypePrivKey = "ETHERMINT PRIVATE KEY" + blockTypeKeyInfo = "ETHERMINT KEY INFO" + blockTypePubKey = "ETHERMINT PUBLIC KEY" +) + +// Make bcrypt security parameter var, so it can be changed within the lcd test +// Making the bcrypt security parameter a var shouldn't be a security issue: +// One can't verify an invalid key by maliciously changing the bcrypt +// parameter during a runtime vulnerability. The main security +// threat this then exposes would be something that changes this during +// runtime before the user creates their key. This vulnerability must +// succeed to update this to that same value before every subsequent call +// to the keys command in future startups / or the attacker must get access +// to the filesystem. However, with a similar threat model (changing +// variables in runtime), one can cause the user to sign a different tx +// than what they see, which is a significantly cheaper attack then breaking +// a bcrypt hash. (Recall that the nonce still exists to break rainbow tables) +// For further notes on security parameter choice, see README.md +var BcryptSecurityParameter = 12 + +//----------------------------------------------------------------- +// add armor + +// Armor the InfoBytes +func ArmorInfoBytes(bz []byte) string { + return armorBytes(bz, blockTypeKeyInfo) +} + +// Armor the PubKeyBytes +func ArmorPubKeyBytes(bz []byte) string { + return armorBytes(bz, blockTypePubKey) +} + +func armorBytes(bz []byte, blockType string) string { + header := map[string]string{ + "type": "Info", + "version": "0.0.0", + } + return armor.EncodeArmor(blockType, header, bz) +} + +//----------------------------------------------------------------- +// remove armor + +// Unarmor the InfoBytes +func UnarmorInfoBytes(armorStr string) (bz []byte, err error) { + return unarmorBytes(armorStr, blockTypeKeyInfo) +} + +// Unarmor the PubKeyBytes +func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) { + return unarmorBytes(armorStr, blockTypePubKey) +} + +func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { + bType, header, bz, err := armor.DecodeArmor(armorStr) + if err != nil { + return + } + if bType != blockType { + err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType) + return + } + if header["version"] != "0.0.0" { + err = fmt.Errorf("Unrecognized version: %v", header["version"]) + return + } + return +} + +//----------------------------------------------------------------- +// encrypt/decrypt with armor + +// Encrypt and armor the private key. +func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { + saltBytes, encBytes := encryptPrivKey(privKey, passphrase) + header := map[string]string{ + "kdf": "bcrypt", + "salt": fmt.Sprintf("%X", saltBytes), + } + armorStr := armor.EncodeArmor(blockTypePrivKey, header, encBytes) + return armorStr +} + +// encrypt the given privKey with the passphrase using a randomly +// generated salt and the xsalsa20 cipher. returns the salt and the +// encrypted priv key. +func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) +} + +// Unarmor and decrypt the private key. +func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { + var privKey crypto.PrivKey + blockType, header, encBytes, err := armor.DecodeArmor(armorStr) + if err != nil { + return privKey, err + } + if blockType != blockTypePrivKey { + return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType) + } + if header["kdf"] != "bcrypt" { + return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"]) + } + if header["salt"] == "" { + return privKey, fmt.Errorf("Missing salt bytes") + } + saltBytes, err := hex.DecodeString(header["salt"]) + if err != nil { + return privKey, fmt.Errorf("Error decoding salt: %v", err.Error()) + } + privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase) + return privKey, err +} + +func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // Get 32 bytes + privKeyBytes, err := xsalsa20symmetric.DecryptSymmetric(encBytes, key) + if err != nil && err.Error() == "Ciphertext decryption failed" { + return privKey, keyerror.NewErrWrongPassword() + } else if err != nil { + return privKey, err + } + privKey, err = emintEncoding.PrivKeyFromBytes(privKeyBytes) + return privKey, err +} diff --git a/crypto/keys/output.go b/crypto/keys/output.go index 30e7a302..cd255bd2 100644 --- a/crypto/keys/output.go +++ b/crypto/keys/output.go @@ -3,25 +3,41 @@ package keys import ( "encoding/hex" + sdk "github.com/cosmos/cosmos-sdk/types" + cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" ) // KeyOutput defines a structure wrapping around an Info object used for output // functionality. type KeyOutput struct { - Name string `json:"name"` - Type string `json:"type"` - Address string `json:"address"` - PubKey string `json:"pubkey"` - Mnemonic string `json:"mnemonic,omitempty"` - Threshold uint `json:"threshold,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Address string `json:"address"` + ETHAddress string `json:"ethaddress"` + PubKey string `json:"pubkey"` + ETHPubKey string `json:"ethpubkey"` + Mnemonic string `json:"mnemonic,omitempty"` + Threshold uint `json:"threshold,omitempty"` +} + +// NewKeyOutput creates a default KeyOutput instance without Mnemonic, Threshold and PubKeys +func NewKeyOutput(name, keyType, address, ethaddress, pubkey, ethpubkey string) KeyOutput { + return KeyOutput{ + Name: name, + Type: keyType, + Address: address, + ETHAddress: ethaddress, + PubKey: pubkey, + ETHPubKey: ethpubkey, + } } // Bech32KeysOutput returns a slice of KeyOutput objects, each with the "acc" // Bech32 prefixes, given a slice of Info objects. It returns an error if any // call to Bech32KeyOutput fails. -func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) { - kos := make([]cosmosKeys.KeyOutput, len(infos)) +func Bech32KeysOutput(infos []cosmosKeys.Info) ([]KeyOutput, error) { + kos := make([]KeyOutput, len(infos)) for i, info := range infos { ko, err := Bech32KeyOutput(info) if err != nil { @@ -34,52 +50,62 @@ func Bech32KeysOutput(infos []cosmosKeys.Info) ([]cosmosKeys.KeyOutput, error) { } // Bech32ConsKeyOutput create a KeyOutput in with "cons" Bech32 prefixes. -func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - consAddr := keyInfo.GetPubKey().Address() - bytes := keyInfo.GetPubKey().Bytes() +func Bech32ConsKeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) { + address := keyInfo.GetPubKey().Address() - // bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) - // if err != nil { - // return KeyOutput{}, err - // } + bechPubKey, err := sdk.Bech32ifyConsPub(keyInfo.GetPubKey()) + if err != nil { + return KeyOutput{}, err + } - return cosmosKeys.KeyOutput{ - Name: keyInfo.GetName(), - Type: keyInfo.GetType().String(), - Address: consAddr.String(), - PubKey: hex.EncodeToString(bytes), - }, nil + return NewKeyOutput( + keyInfo.GetName(), + keyInfo.GetType().String(), + sdk.ConsAddress(address.Bytes()).String(), + getEthAddress(keyInfo), + bechPubKey, + hex.EncodeToString(keyInfo.GetPubKey().Bytes()), + ), nil } // Bech32ValKeyOutput create a KeyOutput in with "val" Bech32 prefixes. -func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - valAddr := keyInfo.GetPubKey().Address() - bytes := keyInfo.GetPubKey().Bytes() +func Bech32ValKeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) { + address := keyInfo.GetPubKey().Address() - // bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) - // if err != nil { - // return KeyOutput{}, err - // } + bechPubKey, err := sdk.Bech32ifyValPub(keyInfo.GetPubKey()) + if err != nil { + return KeyOutput{}, err + } - return cosmosKeys.KeyOutput{ - Name: keyInfo.GetName(), - Type: keyInfo.GetType().String(), - Address: valAddr.String(), - PubKey: hex.EncodeToString(bytes), - }, nil + return NewKeyOutput( + keyInfo.GetName(), + keyInfo.GetType().String(), + sdk.ValAddress(address.Bytes()).String(), + getEthAddress(keyInfo), + bechPubKey, + hex.EncodeToString(keyInfo.GetPubKey().Bytes()), + ), nil } // Bech32KeyOutput create a KeyOutput in with "acc" Bech32 prefixes. -func Bech32KeyOutput(info cosmosKeys.Info) (cosmosKeys.KeyOutput, error) { - accAddr := info.GetPubKey().Address() - bytes := info.GetPubKey().Bytes() +func Bech32KeyOutput(keyInfo cosmosKeys.Info) (KeyOutput, error) { + address := keyInfo.GetPubKey().Address() - ko := cosmosKeys.KeyOutput{ - Name: info.GetName(), - Type: info.GetType().String(), - Address: accAddr.String(), - PubKey: hex.EncodeToString(bytes), + bechPubKey, err := sdk.Bech32ifyAccPub(keyInfo.GetPubKey()) + if err != nil { + return KeyOutput{}, err } - return ko, nil + return NewKeyOutput( + keyInfo.GetName(), + keyInfo.GetType().String(), + sdk.AccAddress(address.Bytes()).String(), + getEthAddress(keyInfo), + bechPubKey, + hex.EncodeToString(keyInfo.GetPubKey().Bytes()), + ), nil +} + +func getEthAddress(info cosmosKeys.Info) string { + return info.GetPubKey().Address().String() } diff --git a/crypto/secp256k1.go b/crypto/secp256k1.go index 6087d922..0470b2b4 100644 --- a/crypto/secp256k1.go +++ b/crypto/secp256k1.go @@ -38,7 +38,7 @@ func (privkey PrivKeySecp256k1) PubKey() tmcrypto.PubKey { // Bytes returns the raw ECDSA private key bytes. func (privkey PrivKeySecp256k1) Bytes() []byte { - return privkey + return cryptoCodec.MustMarshalBinaryBare(privkey) } // Sign creates a recoverable ECDSA signature on the secp256k1 curve over the @@ -59,7 +59,7 @@ func (privkey PrivKeySecp256k1) Equals(other tmcrypto.PrivKey) bool { // ToECDSA returns the ECDSA private key as a reference to ecdsa.PrivateKey type. func (privkey PrivKeySecp256k1) ToECDSA() *ecdsa.PrivateKey { - key, _ := ethcrypto.ToECDSA(privkey.Bytes()) + key, _ := ethcrypto.ToECDSA(privkey) return key } @@ -80,7 +80,11 @@ func (key PubKeySecp256k1) Address() tmcrypto.Address { // Bytes returns the raw bytes of the ECDSA public key. func (key PubKeySecp256k1) Bytes() []byte { - return key + bz, err := cryptoCodec.MarshalBinaryBare(key) + if err != nil { + panic(err) + } + return bz } // VerifyBytes verifies that the ECDSA public key created a given signature over @@ -93,7 +97,7 @@ func (key PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool { } // the signature needs to be in [R || S] format when provided to VerifySignature - return ethsecp256k1.VerifySignature(key.Bytes(), ethcrypto.Keccak256Hash(msg).Bytes(), sig) + return ethsecp256k1.VerifySignature(key, ethcrypto.Keccak256Hash(msg).Bytes(), sig) } // Equals returns true if two ECDSA public keys are equal and false otherwise. diff --git a/crypto/secp256k1_test.go b/crypto/secp256k1_test.go index 1e16dec2..2f0fb72c 100644 --- a/crypto/secp256k1_test.go +++ b/crypto/secp256k1_test.go @@ -30,7 +30,7 @@ func TestPrivKeySecp256k1PrivKey(t *testing.T) { // validate we can sign some bytes msg := []byte("hello world") sigHash := ethcrypto.Keccak256Hash(msg) - expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey.Bytes()) + expectedSig, _ := ethsecp256k1.Sign(sigHash.Bytes(), privKey) sig, err := privKey.Sign(msg) require.NoError(t, err) diff --git a/go.mod b/go.mod index aff19070..15393ca7 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect github.com/stretchr/testify v1.3.0 + github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 github.com/tendermint/go-amino v0.15.0 github.com/tendermint/tendermint v0.32.2 github.com/tendermint/tm-db v0.1.1 diff --git a/keys/add.go b/keys/add.go index ec69d783..aebf1d13 100644 --- a/keys/add.go +++ b/keys/add.go @@ -90,7 +90,7 @@ func runAddCmd(cmd *cobra.Command, args []string) error { interactive := viper.GetBool(flagInteractive) showMnemonic := !viper.GetBool(flagNoBackup) - kb, err = clientkeys.NewKeyBaseFromHomeFlag() + kb, err = NewKeyBaseFromHomeFlag() if err != nil { return err } diff --git a/keys/show_test.go b/keys/show_test.go index 6732006c..6c605c38 100644 --- a/keys/show_test.go +++ b/keys/show_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/cosmos/cosmos-sdk/client/flags" - clientkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/tests" ) @@ -32,7 +31,7 @@ func TestRunShowCmd(t *testing.T) { fakeKeyName1 := "runShowCmd_Key1" fakeKeyName2 := "runShowCmd_Key2" - kb, err := clientkeys.NewKeyBaseFromHomeFlag() + kb, err := NewKeyBaseFromHomeFlag() assert.NoError(t, err) _, err = kb.CreateAccount(fakeKeyName1, tests.TestMnemonic, "", "", 0, 0) assert.NoError(t, err) diff --git a/keys/utils.go b/keys/utils.go index 3cc3df21..b10ecc42 100644 --- a/keys/utils.go +++ b/keys/utils.go @@ -2,28 +2,33 @@ package keys import ( "fmt" + "path/filepath" "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" "gopkg.in/yaml.v2" - "github.com/cosmos/cosmos-sdk/client/flags" clientkeys "github.com/cosmos/cosmos-sdk/client/keys" + + "github.com/cosmos/cosmos-sdk/client/flags" cosmosKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + emintKeys "github.com/cosmos/ethermint/crypto/keys" ) // available output formats. const ( OutputFormatText = "text" OutputFormatJSON = "json" + + defaultKeyDBName = "emintkeys" ) -type bechKeyOutFn func(keyInfo cosmosKeys.Info) (cosmosKeys.KeyOutput, error) +type bechKeyOutFn func(keyInfo cosmosKeys.Info) (emintKeys.KeyOutput, error) // GetKeyInfo returns key info for a given name. An error is returned if the // keybase cannot be retrieved or getting the info fails. func GetKeyInfo(name string) (cosmosKeys.Info, error) { - keybase, err := clientkeys.NewKeyBaseFromHomeFlag() + keybase, err := NewKeyBaseFromHomeFlag() if err != nil { return nil, err } @@ -31,6 +36,47 @@ func GetKeyInfo(name string) (cosmosKeys.Info, error) { return keybase.Get(name) } +// NewKeyBaseFromHomeFlag initializes a Keybase based on the configuration. +func NewKeyBaseFromHomeFlag() (cosmosKeys.Keybase, error) { + rootDir := viper.GetString(flags.FlagHome) + return NewKeyBaseFromDir(rootDir) +} + +// NewKeyBaseFromDir initializes a keybase at a particular dir. +func NewKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { + return getLazyKeyBaseFromDir(rootDir) +} + +// NewInMemoryKeyBase returns a storage-less keybase. +func NewInMemoryKeyBase() cosmosKeys.Keybase { return emintKeys.NewInMemory() } + +func getLazyKeyBaseFromDir(rootDir string) (cosmosKeys.Keybase, error) { + return emintKeys.New(defaultKeyDBName, filepath.Join(rootDir, defaultKeyDBName)), nil +} + +// GetPassphrase returns a passphrase for a given name. It will first retrieve +// the key info for that name if the type is local, it'll fetch input from +// STDIN. Otherwise, an empty passphrase is returned. An error is returned if +// the key info cannot be fetched or reading from STDIN fails. +func GetPassphrase(name string) (string, error) { + var passphrase string + + keyInfo, err := GetKeyInfo(name) + if err != nil { + return passphrase, err + } + + // we only need a passphrase for locally stored keys + if keyInfo.GetType().String() == emintKeys.TypeLocal.String() { + passphrase, err = clientkeys.ReadPassphraseFromStdin(name) + if err != nil { + return passphrase, err + } + } + + return passphrase, nil +} + func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) { ko, err := bechKeyOut(keyInfo) if err != nil { @@ -39,7 +85,7 @@ func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) { switch viper.Get(cli.OutputFlag) { case OutputFormatText: - printTextInfos([]cosmosKeys.KeyOutput{ko}) + printTextInfos([]emintKeys.KeyOutput{ko}) case OutputFormatJSON: var out []byte @@ -84,7 +130,7 @@ func printKeyInfo(keyInfo cosmosKeys.Info, bechKeyOut bechKeyOutFn) { // } // } -func printTextInfos(kos []cosmosKeys.KeyOutput) { +func printTextInfos(kos []emintKeys.KeyOutput) { out, err := yaml.Marshal(&kos) if err != nil { panic(err) diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go new file mode 100644 index 00000000..d6a6e71e --- /dev/null +++ b/x/evm/client/cli/tx.go @@ -0,0 +1,135 @@ +package cli + +import ( + "math/big" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + emintkeys "github.com/cosmos/ethermint/keys" + emintTypes "github.com/cosmos/ethermint/types" + emintUtils "github.com/cosmos/ethermint/x/evm/client/utils" + "github.com/cosmos/ethermint/x/evm/types" + + ethcmn "github.com/ethereum/go-ethereum/common" +) + +// GetTxCmd defines the CLI commands regarding evm module transactions +func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { + evmTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "EVM transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + evmTxCmd.AddCommand(client.PostCommands( + // TODO: Add back generating cosmos tx for Ethereum tx message + // GetCmdGenTx(cdc), + GetCmdGenETHTx(cdc), + )...) + + return evmTxCmd +} + +// GetCmdGenTx generates an ethereum transaction wrapped in a Cosmos standard transaction +func GetCmdGenTx(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "generate-tx [ethaddress] [amount] [gaslimit] [gasprice] [payload]", + Short: "generate eth tx wrapped in a Cosmos Standard tx", + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: remove inputs and infer based on StdTx + cliCtx := emintUtils.NewETHCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + kb, err := emintkeys.NewKeyBaseFromHomeFlag() + if err != nil { + panic(err) + } + + coins, err := sdk.ParseCoins(args[1]) + if err != nil { + return err + } + + gasLimit, err := strconv.ParseUint(args[2], 0, 64) + if err != nil { + return err + } + + gasPrice, err := strconv.ParseUint(args[3], 0, 64) + if err != nil { + return err + } + + payload := args[4] + + // TODO: Remove explicit photon check and check variables + msg := types.NewEthereumTxMsg(0, ethcmn.HexToAddress(args[0]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) + err = msg.ValidateBasic() + if err != nil { + return err + } + + // TODO: possibly overwrite gas values in txBldr + return emintUtils.GenerateOrBroadcastETHMsgs(cliCtx, txBldr.WithKeybase(kb), []sdk.Msg{msg}) + }, + } +} + +// GetCmdGenTx generates an ethereum transaction +func GetCmdGenETHTx(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "generate-eth-tx [nonce] [ethaddress] [amount] [gaslimit] [gasprice] [payload]", + Short: "geberate and broadcast an Ethereum tx", + Args: cobra.ExactArgs(6), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := emintUtils.NewETHCLIContext().WithCodec(cdc) + + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + kb, err := emintkeys.NewKeyBaseFromHomeFlag() + if err != nil { + panic(err) + } + + nonce, err := strconv.ParseUint(args[0], 0, 64) + if err != nil { + return err + } + + coins, err := sdk.ParseCoins(args[2]) + if err != nil { + return err + } + + gasLimit, err := strconv.ParseUint(args[3], 0, 64) + if err != nil { + return err + } + + gasPrice, err := strconv.ParseUint(args[4], 0, 64) + if err != nil { + return err + } + + payload := args[5] + + tx := types.NewEthereumTxMsg(nonce, ethcmn.HexToAddress(args[1]), big.NewInt(coins.AmountOf(emintTypes.DenomDefault).Int64()), gasLimit, new(big.Int).SetUint64(gasPrice), []byte(payload)) + err = tx.ValidateBasic() + if err != nil { + return err + } + + return emintUtils.BroadcastETHTx(cliCtx, txBldr.WithSequence(nonce).WithKeybase(kb), tx) + }, + } +} diff --git a/x/evm/client/utils/tx.go b/x/evm/client/utils/tx.go new file mode 100644 index 00000000..e20da0fe --- /dev/null +++ b/x/evm/client/utils/tx.go @@ -0,0 +1,348 @@ +package utils + +import ( + "bufio" + "fmt" + "math/big" + "os" + + "github.com/pkg/errors" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/input" + crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + emintcrypto "github.com/cosmos/ethermint/crypto" + emintkeys "github.com/cosmos/ethermint/keys" + emint "github.com/cosmos/ethermint/types" + evmtypes "github.com/cosmos/ethermint/x/evm/types" + + "github.com/tendermint/tendermint/libs/cli" + rpcclient "github.com/tendermint/tendermint/rpc/client" +) + +// * Code from this file is a modified version of cosmos-sdk/auth/client/utils/tx.go +// * to allow for using the Ethermint keybase for signing the transaction + +// GenerateOrBroadcastMsgs creates a StdTx given a series of messages. If +// the provided context has generate-only enabled, the tx will only be printed +// to STDOUT in a fully offline manner. Otherwise, the tx will be signed and +// broadcasted. +func GenerateOrBroadcastETHMsgs(cliCtx context.CLIContext, txBldr authtypes.TxBuilder, msgs []sdk.Msg) error { + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(txBldr, cliCtx, msgs) + } + + return completeAndBroadcastETHTxCLI(txBldr, cliCtx, msgs) +} + +// BroadcastETHTx Broadcasts an Ethereum Tx not wrapped in a Std Tx +func BroadcastETHTx(cliCtx context.CLIContext, txBldr authtypes.TxBuilder, tx *evmtypes.EthereumTxMsg) error { + txBldr, err := utils.PrepareTxBuilder(txBldr, cliCtx) + if err != nil { + return err + } + + fromName := cliCtx.GetFromName() + + passphrase, err := emintkeys.GetPassphrase(fromName) + if err != nil { + return err + } + + // Sign V, R, S fields for tx + ethTx, err := signEthTx(txBldr.Keybase(), fromName, passphrase, tx, txBldr.ChainID()) + if err != nil { + return err + } + + // Use default Tx Encoder since it will just be broadcasted to TM node at this point + txEncoder := txBldr.TxEncoder() + + txBytes, err := txEncoder(ethTx) + if err != nil { + return err + } + + // broadcast to a Tendermint node + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + return err + } + + return cliCtx.PrintOutput(res) +} + +// completeAndBroadcastETHTxCLI implements a utility function that facilitates +// sending a series of messages in a signed transaction given a TxBuilder and a +// QueryContext. It ensures that the account exists, has a proper number and +// sequence set. In addition, it builds and signs a transaction with the +// supplied messages. Finally, it broadcasts the signed transaction to a node. +// * Modified version from github.com/cosmos/cosmos-sdk/x/auth/client/utils/tx.go +func completeAndBroadcastETHTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + txBldr, err := utils.PrepareTxBuilder(txBldr, cliCtx) + if err != nil { + return err + } + + fromName := cliCtx.GetFromName() + + if txBldr.SimulateAndExecute() || cliCtx.Simulate { + txBldr, err = utils.EnrichWithGas(txBldr, cliCtx, msgs) + if err != nil { + return err + } + + gasEst := utils.GasEstimateResponse{GasEstimate: txBldr.Gas()} + _, _ = fmt.Fprintf(os.Stderr, "%s\n", gasEst.String()) + } + + if cliCtx.Simulate { + return nil + } + + if !cliCtx.SkipConfirm { + stdSignMsg, err := txBldr.BuildSignMsg(msgs) + if err != nil { + return err + } + + var json []byte + if viper.GetBool(flags.FlagIndentResponse) { + json, err = cliCtx.Codec.MarshalJSONIndent(stdSignMsg, "", " ") + if err != nil { + panic(err) + } + } else { + json = cliCtx.Codec.MustMarshalJSON(stdSignMsg) + } + + _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", json) + + buf := bufio.NewReader(os.Stdin) + ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf) + if err != nil || !ok { + _, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction") + return err + } + } + + // * This function is overriden to change the keybase reference here + passphrase, err := emintkeys.GetPassphrase(fromName) + if err != nil { + return err + } + + // build and sign the transaction + // * needed to be modified also to change how the data is signed + txBytes, err := buildAndSign(txBldr, fromName, passphrase, msgs) + if err != nil { + return err + } + + // broadcast to a Tendermint node + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + return err + } + + return cliCtx.PrintOutput(res) +} + +// BuildAndSign builds a single message to be signed, and signs a transaction +// with the built message given a name, passphrase, and a set of messages. +// * overriden from github.com/cosmos/cosmos-sdk/x/auth/types/txbuilder.go +// * This is just modified to change the functionality in makeSignature, through sign +func buildAndSign(bldr authtypes.TxBuilder, name, passphrase string, msgs []sdk.Msg) ([]byte, error) { + msg, err := bldr.BuildSignMsg(msgs) + if err != nil { + return nil, err + } + + return sign(bldr, name, passphrase, msg) +} + +// Sign signs a transaction given a name, passphrase, and a single message to +// signed. An error is returned if signing fails. +func sign(bldr authtypes.TxBuilder, name, passphrase string, msg authtypes.StdSignMsg) ([]byte, error) { + sig, err := makeSignature(bldr.Keybase(), name, passphrase, msg) + if err != nil { + return nil, err + } + + txEncoder := bldr.TxEncoder() + + return txEncoder(authtypes.NewStdTx(msg.Msgs, msg.Fee, []authtypes.StdSignature{sig}, msg.Memo)) +} + +// MakeSignature builds a StdSignature given keybase, key name, passphrase, and a StdSignMsg. +func makeSignature(keybase crkeys.Keybase, name, passphrase string, + msg authtypes.StdSignMsg) (sig authtypes.StdSignature, err error) { + if keybase == nil { + // * This is overriden to allow ethermint keys, but not used because keybase is set + keybase, err = emintkeys.NewKeyBaseFromHomeFlag() + if err != nil { + return + } + } + + // EthereumTxMsg always returns the data in the 0th index so it is safe to do this + var ethTx *evmtypes.EthereumTxMsg + ethTx, ok := msg.Msgs[0].(*evmtypes.EthereumTxMsg) + if !ok { + return sig, fmt.Errorf("Transaction message not an Ethereum Tx") + } + + // TODO: Move this logic to after tx is rlp decoded in keybase Sign function + // parse the chainID from a string to a base-10 integer + chainID, ok := new(big.Int).SetString(msg.ChainID, 10) + if !ok { + return sig, emint.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", msg.ChainID)) + } + + privKey, err := keybase.ExportPrivateKeyObject(name, passphrase) + if err != nil { + return + } + + emintKey, ok := privKey.(emintcrypto.PrivKeySecp256k1) + if !ok { + panic(fmt.Sprintf("invalid private key type: %T", privKey)) + } + + ethTx.Sign(chainID, emintKey.ToECDSA()) + + // * This is needed to be overriden to get bytes to sign (RLPSignBytes) with the chainID + sigBytes, pubkey, err := keybase.Sign(name, passphrase, ethTx.RLPSignBytes(chainID).Bytes()) + if err != nil { + return + } + return authtypes.StdSignature{ + PubKey: pubkey, + Signature: sigBytes, + }, nil +} + +// signEthTx populates the V, R, and S fields of an EthereumTxMsg using an ethermint key +func signEthTx(keybase crkeys.Keybase, name, passphrase string, + ethTx *evmtypes.EthereumTxMsg, chainID string) (_ *evmtypes.EthereumTxMsg, err error) { + if keybase == nil { + keybase, err = emintkeys.NewKeyBaseFromHomeFlag() + if err != nil { + return + } + } + + // parse the chainID from a string to a base-10 integer + intChainID, ok := new(big.Int).SetString(chainID, 10) + if !ok { + return ethTx, emint.ErrInvalidChainID(fmt.Sprintf("invalid chainID: %s", chainID)) + } + + privKey, err := keybase.ExportPrivateKeyObject(name, passphrase) + if err != nil { + return + } + + // Key must be a ethermint key to be able to be converted into an ECDSA private key to sign + emintKey, ok := privKey.(emintcrypto.PrivKeySecp256k1) + if !ok { + panic(fmt.Sprintf("invalid private key type: %T", privKey)) + } + + ethTx.Sign(intChainID, emintKey.ToECDSA()) + + return ethTx, err +} + +// * This context is needed because the previous GetFromFields function would initialize a +// * default keybase to lookup the address or name. The new one overrides the keybase with the +// * ethereum compatible one + +// NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the +// command line using Viper. It takes a key name or address and populates the FromName and +// FromAddress field accordingly. +func NewETHCLIContext() context.CLIContext { + var nodeURI string + var rpc rpcclient.Client + + from := viper.GetString(flags.FlagFrom) + + genOnly := viper.GetBool(flags.FlagGenerateOnly) + + // * This function is needed only to override this call to access correct keybase + fromAddress, fromName, err := getFromFields(from, genOnly) + if err != nil { + fmt.Printf("failed to get from fields: %v", err) + os.Exit(1) + } + + if !genOnly { + nodeURI = viper.GetString(flags.FlagNode) + if nodeURI != "" { + rpc = rpcclient.NewHTTP(nodeURI, "/websocket") + } + } + + return context.CLIContext{ + Client: rpc, + Output: os.Stdout, + NodeURI: nodeURI, + From: viper.GetString(flags.FlagFrom), + OutputFormat: viper.GetString(cli.OutputFlag), + Height: viper.GetInt64(flags.FlagHeight), + TrustNode: viper.GetBool(flags.FlagTrustNode), + UseLedger: viper.GetBool(flags.FlagUseLedger), + BroadcastMode: viper.GetString(flags.FlagBroadcastMode), + // Verifier: verifier, + Simulate: viper.GetBool(flags.FlagDryRun), + GenerateOnly: genOnly, + FromAddress: fromAddress, + FromName: fromName, + Indent: viper.GetBool(flags.FlagIndentResponse), + SkipConfirm: viper.GetBool(flags.FlagSkipConfirmation), + } +} + +// GetFromFields returns a from account address and Keybase name given either +// an address or key name. If genOnly is true, only a valid Bech32 cosmos +// address is returned. +func getFromFields(from string, genOnly bool) (sdk.AccAddress, string, error) { + if from == "" { + return nil, "", nil + } + + if genOnly { + addr, err := sdk.AccAddressFromBech32(from) + if err != nil { + return nil, "", errors.Wrap(err, "must provide a valid Bech32 address for generate-only") + } + + return addr, "", nil + } + + // * This is the line that needed to be overriden, change could be to pass in optional keybase? + keybase, err := emintkeys.NewKeyBaseFromHomeFlag() + if err != nil { + return nil, "", err + } + + var info crkeys.Info + if addr, err := sdk.AccAddressFromBech32(from); err == nil { + info, err = keybase.GetByAddress(addr) + if err != nil { + return nil, "", err + } + } else { + info, err = keybase.Get(from) + if err != nil { + return nil, "", err + } + } + + return info.GetAddress(), info.GetName(), nil +} diff --git a/x/evm/module.go b/x/evm/module.go index ccfeb968..bb7968f7 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -55,7 +55,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { // Get the root tx command of this module func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { - return nil // cli.GetTxCmd(StoreKey, cdc) + return cli.GetTxCmd(types.ModuleName, cdc) } type AppModule struct { diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 013f6def..b2383102 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -28,7 +28,7 @@ var big8 = big.NewInt(8) // message type and route constants const ( TypeEthereumTxMsg = "ethereum_tx" - RouteEthereumTxMsg = "evm" + RouteEthereumTxMsg = RouterKey ) // EthereumTxMsg encapsulates an Ethereum transaction as an SDK message. @@ -128,11 +128,11 @@ func (msg EthereumTxMsg) Type() string { return TypeEthereumTxMsg } // checks of a Transaction. If returns an sdk.Error if validation fails. func (msg EthereumTxMsg) ValidateBasic() sdk.Error { if msg.Data.Price.Sign() != 1 { - return types.ErrInvalidValue("price must be positive") + return types.ErrInvalidValue(fmt.Sprintf("Price must be positive: %x", msg.Data.Price)) } if msg.Data.Amount.Sign() != 1 { - return types.ErrInvalidValue("amount must be positive") + return types.ErrInvalidValue(fmt.Sprintf("amount must be positive: %x", msg.Data.Amount)) } return nil diff --git a/x/evm/types/msg_encoding.go b/x/evm/types/msg_encoding.go new file mode 100644 index 00000000..86218d28 --- /dev/null +++ b/x/evm/types/msg_encoding.go @@ -0,0 +1,153 @@ +package types + +import ( + "math/big" + + "github.com/cosmos/cosmos-sdk/codec" + + ethcmn "github.com/ethereum/go-ethereum/common" +) + +var cdc = codec.New() + +func init() { + RegisterAmino(cdc) +} + +// RegisterAmino registers all crypto related types in the given (amino) codec. +func RegisterAmino(cdc *codec.Codec) { + cdc.RegisterConcrete(EncodableTxData{}, "ethermint/EncodedMessage", nil) +} + +// TxData implements the Ethereum transaction data structure. It is used +// solely as intended in Ethereum abiding by the protocol. +type EncodableTxData struct { + AccountNonce uint64 `json:"nonce"` + Price string `json:"gasPrice"` + GasLimit uint64 `json:"gas"` + Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation + Amount string `json:"value"` + Payload []byte `json:"input"` + + // signature values + V string `json:"v"` + R string `json:"r"` + S string `json:"s"` + + // hash is only used when marshaling to JSON + Hash *ethcmn.Hash `json:"hash" rlp:"-"` +} + +func marshalAmino(td EncodableTxData) (string, error) { + bz, err := cdc.MarshalBinaryBare(td) + return string(bz), err +} + +func unmarshalAmino(td *EncodableTxData, text string) (err error) { + return cdc.UnmarshalBinaryBare([]byte(text), td) +} + +func marshalBigInt(i *big.Int) string { + bz, err := i.MarshalText() + if err != nil { + panic(err) + } + return string(bz) +} + +func unmarshalBigInt(s string) (*big.Int, error) { + ret := new(big.Int) + err := ret.UnmarshalText([]byte(s)) + return ret, err +} + +// MarshalAmino defines custom encoding scheme for TxData +func (td TxData) MarshalAmino() (string, error) { + e := EncodableTxData{ + AccountNonce: td.AccountNonce, + Price: marshalBigInt(td.Price), + GasLimit: td.GasLimit, + Recipient: td.Recipient, + Amount: marshalBigInt(td.Amount), + Payload: td.Payload, + + V: marshalBigInt(td.V), + R: marshalBigInt(td.R), + S: marshalBigInt(td.S), + + Hash: td.Hash, + } + + return marshalAmino(e) +} + +// UnmarshalAmino defines custom decoding scheme for TxData +func (td *TxData) UnmarshalAmino(text string) (err error) { + e := new(EncodableTxData) + err = unmarshalAmino(e, text) + if err != nil { + return + } + + td.AccountNonce = e.AccountNonce + td.GasLimit = e.GasLimit + td.Recipient = e.Recipient + td.Payload = e.Payload + td.Hash = e.Hash + + price, err := unmarshalBigInt(e.Price) + if err != nil { + return + } + if td.Price != nil { + td.Price.Set(price) + } else { + td.Price = price + } + + amt, err := unmarshalBigInt(e.Amount) + if err != nil { + return + } + if td.Amount != nil { + td.Amount.Set(amt) + } else { + td.Amount = amt + } + + v, err := unmarshalBigInt(e.V) + if err != nil { + return + } + if td.V != nil { + td.V.Set(v) + } else { + td.V = v + } + + r, err := unmarshalBigInt(e.R) + if err != nil { + return + } + if td.R != nil { + td.R.Set(r) + } else { + td.R = r + } + + s, err := unmarshalBigInt(e.S) + if err != nil { + return + } + if td.S != nil { + td.S.Set(s) + } else { + td.S = s + } + + return +} + +// TODO: Implement JSON marshaling/ unmarshaling for this type + +// TODO: Implement YAML marshaling/ unmarshaling for this type diff --git a/x/evm/types/msg_test.go b/x/evm/types/msg_test.go index 161b49db..c2f3ecad 100644 --- a/x/evm/types/msg_test.go +++ b/x/evm/types/msg_test.go @@ -122,7 +122,11 @@ func TestMsgEthereumTxSig(t *testing.T) { func TestMsgEthereumTxAmino(t *testing.T) { addr := GenerateEthAddress() - msg := NewEthereumTxMsg(0, addr, nil, 100000, nil, []byte("test")) + msg := NewEthereumTxMsg(5, addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test")) + + msg.Data.V = big.NewInt(1) + msg.Data.R = big.NewInt(2) + msg.Data.S = big.NewInt(3) raw, err := ModuleCdc.MarshalBinaryBare(msg) require.NoError(t, err) @@ -133,3 +137,39 @@ func TestMsgEthereumTxAmino(t *testing.T) { require.NoError(t, err) require.Equal(t, msg.Data, msg2.Data) } + +func TestMarshalAndUnmarshalInt(t *testing.T) { + i := big.NewInt(3) + m := marshalBigInt(i) + i2, err := unmarshalBigInt(m) + require.NoError(t, err) + + require.Equal(t, i, i2) +} + +func TestMarshalAndUnmarshalData(t *testing.T) { + addr := GenerateEthAddress() + hash := ethcmn.BigToHash(big.NewInt(2)) + e := EncodableTxData{ + AccountNonce: 2, + Price: marshalBigInt(big.NewInt(3)), + GasLimit: 1, + Recipient: &addr, + Amount: marshalBigInt(big.NewInt(4)), + Payload: []byte("test"), + + V: marshalBigInt(big.NewInt(5)), + R: marshalBigInt(big.NewInt(6)), + S: marshalBigInt(big.NewInt(7)), + + Hash: &hash, + } + str, err := marshalAmino(e) + require.NoError(t, err) + + e2 := new(EncodableTxData) + + err = unmarshalAmino(e2, str) + require.NoError(t, err) + require.Equal(t, e, *e2) +}