forked from cerc-io/laconicd-deprecated
fear(eip712): Add EIP-712 encoding for multiple messages of the same type (#1483)
* Add EIP-712 encoding for multiple messages of the same type * Fix Protobuf encoding bug * Add ante tests * Refactor naming and minor implementation details * Test empty transaction coverage * Address revisions for code clarity * Move aminoMessage type definition
This commit is contained in:
parent
f0f3810bf4
commit
9c41edb674
@ -387,7 +387,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
|||||||
from, grantee, &banktypes.SendAuthorization{SpendLimit: gasAmount}, &expiresAt,
|
from, grantee, &banktypes.SendAuthorization{SpendLimit: gasAmount}, &expiresAt,
|
||||||
)
|
)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, privKey, "ethermint_9000-1", gas, gasAmount, msg).GetTx()
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, privKey, "ethermint_9000-1", gas, gasAmount, msg).GetTx()
|
||||||
}, false, false, true,
|
}, false, false, true,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -457,6 +457,28 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
|||||||
return txBuilder.GetTx()
|
return txBuilder.GetTx()
|
||||||
}, false, false, true,
|
}, false, false, true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"success- DeliverTx EIP712 Multiple MsgSend",
|
||||||
|
func() sdk.Tx {
|
||||||
|
from := acc.GetAddress()
|
||||||
|
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))
|
||||||
|
amount := sdk.NewCoins(coinAmount)
|
||||||
|
gas := uint64(200000)
|
||||||
|
txBuilder := suite.CreateTestEIP712MultipleMsgSend(from, privKey, "ethermint_9000-1", gas, amount)
|
||||||
|
return txBuilder.GetTx()
|
||||||
|
}, false, false, true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fails - DeliverTx EIP712 Multiple Signers",
|
||||||
|
func() sdk.Tx {
|
||||||
|
from := acc.GetAddress()
|
||||||
|
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))
|
||||||
|
amount := sdk.NewCoins(coinAmount)
|
||||||
|
gas := uint64(200000)
|
||||||
|
txBuilder := suite.CreateTestEIP712MultipleSignerMsgs(from, privKey, "ethermint_9000-1", gas, amount)
|
||||||
|
return txBuilder.GetTx()
|
||||||
|
}, false, false, false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
|
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
|
||||||
func() sdk.Tx {
|
func() sdk.Tx {
|
||||||
|
@ -272,7 +272,7 @@ func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress
|
|||||||
// Build MsgSend
|
// Build MsgSend
|
||||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -280,7 +280,7 @@ func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAdd
|
|||||||
valEthAddr := tests.GenerateAddress()
|
valEthAddr := tests.GenerateAddress()
|
||||||
valAddr := sdk.ValAddress(valEthAddr.Bytes())
|
valAddr := sdk.ValAddress(valEthAddr.Bytes())
|
||||||
msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -296,7 +296,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator(from sdk.AccAddre
|
|||||||
sdk.OneInt(),
|
sdk.OneInt(),
|
||||||
)
|
)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -313,7 +313,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgCreateValidator2(from sdk.AccAddr
|
|||||||
sdk.OneInt(),
|
sdk.OneInt(),
|
||||||
)
|
)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgCreate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, deposit sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, deposit sdk.Coins) client.TxBuilder {
|
||||||
@ -321,7 +321,7 @@ func (suite *AnteTestSuite) CreateTestEIP712SubmitProposal(from sdk.AccAddress,
|
|||||||
suite.Require().True(ok)
|
suite.Require().True(ok)
|
||||||
msgSubmit, err := types5.NewMsgSubmitProposal(proposal, deposit, from)
|
msgSubmit, err := types5.NewMsgSubmitProposal(proposal, deposit, from)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSubmit)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgSubmit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -335,7 +335,7 @@ func (suite *AnteTestSuite) CreateTestEIP712GrantAllowance(from sdk.AccAddress,
|
|||||||
grantedAddr := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, granted.Bytes())
|
grantedAddr := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, granted.Bytes())
|
||||||
msgGrant, err := feegrant.NewMsgGrantAllowance(basic, from, grantedAddr.GetAddress())
|
msgGrant, err := feegrant.NewMsgGrantAllowance(basic, from, grantedAddr.GetAddress())
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgGrant)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgGrant)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -346,7 +346,7 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgEditValidator(from sdk.AccAddress
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgEdit)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgEdit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -359,12 +359,12 @@ func (suite *AnteTestSuite) CreateTestEIP712MsgSubmitEvidence(from sdk.AccAddres
|
|||||||
})
|
})
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgEvidence)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgEvidence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgVoteV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgVoteV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
msgVote := govtypes.NewMsgVote(from, 1, govtypes.VoteOption_VOTE_OPTION_YES, "")
|
msgVote := govtypes.NewMsgVote(from, 1, govtypes.VoteOption_VOTE_OPTION_YES, "")
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgVote)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgVote)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
@ -403,14 +403,28 @@ func (suite *AnteTestSuite) CreateTestEIP712SubmitProposalV1(from sdk.AccAddress
|
|||||||
|
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, msgProposal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712MsgExec(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
func (suite *AnteTestSuite) CreateTestEIP712MsgExec(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||||
msgExec := authz.NewMsgExec(from, []sdk.Msg{msgSend})
|
msgExec := authz.NewMsgExec(from, []sdk.Msg{msgSend})
|
||||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, &msgExec)
|
return suite.CreateTestEIP712SingleMessageTxBuilder(from, priv, chainId, gas, gasAmount, &msgExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) CreateTestEIP712MultipleMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
|
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||||
|
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||||
|
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msgSend, msgSend, msgSend})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fails
|
||||||
|
func (suite *AnteTestSuite) CreateTestEIP712MultipleSignerMsgs(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||||
|
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||||
|
msgSend1 := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||||
|
msgSend2 := types2.NewMsgSend(recipient, from, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(1))))
|
||||||
|
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msgSend1, msgSend2})
|
||||||
}
|
}
|
||||||
|
|
||||||
// StdSignBytes returns the bytes to sign for a transaction.
|
// StdSignBytes returns the bytes to sign for a transaction.
|
||||||
@ -451,8 +465,14 @@ func StdSignBytes(cdc *codec.LegacyAmino, chainID string, accnum uint64, sequenc
|
|||||||
return sdk.MustSortJSON(bz)
|
return sdk.MustSortJSON(bz)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
func (suite *AnteTestSuite) CreateTestEIP712SingleMessageTxBuilder(
|
||||||
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg,
|
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg,
|
||||||
|
) client.TxBuilder {
|
||||||
|
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, []sdk.Msg{msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||||
|
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msgs []sdk.Msg,
|
||||||
) client.TxBuilder {
|
) client.TxBuilder {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -473,8 +493,8 @@ func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
|||||||
fee := legacytx.NewStdFee(gas, gasAmount)
|
fee := legacytx.NewStdFee(gas, gasAmount)
|
||||||
accNumber := suite.app.AccountKeeper.GetAccount(suite.ctx, from).GetAccountNumber()
|
accNumber := suite.app.AccountKeeper.GetAccount(suite.ctx, from).GetAccountNumber()
|
||||||
|
|
||||||
data := legacytx.StdSignBytes(chainId, accNumber, nonce, 0, fee, []sdk.Msg{msg}, "", nil)
|
data := legacytx.StdSignBytes(chainId, accNumber, nonce, 0, fee, msgs, "", nil)
|
||||||
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msg, data, &eip712.FeeDelegationOptions{
|
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, ethChainId, msgs[0], data, &eip712.FeeDelegationOptions{
|
||||||
FeePayer: from,
|
FeePayer: from,
|
||||||
})
|
})
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
@ -517,7 +537,7 @@ func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
|||||||
err = builder.SetSignatures(sigsV2)
|
err = builder.SetSignatures(sigsV2)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
err = builder.SetMsgs(msg)
|
err = builder.SetMsgs(msgs...)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
|
@ -93,6 +93,9 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
|||||||
signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
|
signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixed test address
|
||||||
|
testAddress := suite.createTestAddress()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
title string
|
title string
|
||||||
chainId string
|
chainId string
|
||||||
@ -176,7 +179,30 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
|||||||
expectSuccess: true,
|
expectSuccess: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Fails - Two MsgVotes",
|
title: "Succeeds - Two Single-Signer MsgDelegate",
|
||||||
|
fee: txtypes.Fee{
|
||||||
|
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||||
|
GasLimit: 20000,
|
||||||
|
},
|
||||||
|
memo: "",
|
||||||
|
msgs: []sdk.Msg{
|
||||||
|
stakingtypes.NewMsgDelegate(
|
||||||
|
testAddress,
|
||||||
|
sdk.ValAddress(suite.createTestAddress()),
|
||||||
|
suite.makeCoins("photon", math.NewInt(1))[0],
|
||||||
|
),
|
||||||
|
stakingtypes.NewMsgDelegate(
|
||||||
|
testAddress,
|
||||||
|
sdk.ValAddress(suite.createTestAddress()),
|
||||||
|
suite.makeCoins("photon", math.NewInt(5))[0],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
accountNumber: 25,
|
||||||
|
sequence: 78,
|
||||||
|
expectSuccess: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fails - Two MsgVotes with Different Signers",
|
||||||
fee: txtypes.Fee{
|
fee: txtypes.Fee{
|
||||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||||
GasLimit: 20000,
|
GasLimit: 20000,
|
||||||
@ -196,10 +222,22 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
|||||||
},
|
},
|
||||||
accountNumber: 25,
|
accountNumber: 25,
|
||||||
sequence: 78,
|
sequence: 78,
|
||||||
expectSuccess: false, // Multiple messages are currently not allowed
|
expectSuccess: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Fails - MsgSend + MsgVote",
|
title: "Fails - Empty transaction",
|
||||||
|
fee: txtypes.Fee{
|
||||||
|
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||||
|
GasLimit: 20000,
|
||||||
|
},
|
||||||
|
memo: "",
|
||||||
|
msgs: []sdk.Msg{},
|
||||||
|
accountNumber: 25,
|
||||||
|
sequence: 78,
|
||||||
|
expectSuccess: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fails - Single-Signer MsgSend + MsgVote",
|
||||||
fee: txtypes.Fee{
|
fee: txtypes.Fee{
|
||||||
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
|
||||||
GasLimit: 20000,
|
GasLimit: 20000,
|
||||||
@ -207,12 +245,12 @@ func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
|
|||||||
memo: "",
|
memo: "",
|
||||||
msgs: []sdk.Msg{
|
msgs: []sdk.Msg{
|
||||||
govtypes.NewMsgVote(
|
govtypes.NewMsgVote(
|
||||||
suite.createTestAddress(),
|
testAddress,
|
||||||
5,
|
5,
|
||||||
govtypes.OptionNo,
|
govtypes.OptionNo,
|
||||||
),
|
),
|
||||||
banktypes.NewMsgSend(
|
banktypes.NewMsgSend(
|
||||||
suite.createTestAddress(),
|
testAddress,
|
||||||
suite.createTestAddress(),
|
suite.createTestAddress(),
|
||||||
suite.makeCoins("photon", math.NewInt(50)),
|
suite.makeCoins("photon", math.NewInt(50)),
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package eip712
|
package eip712
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/simapp/params"
|
"github.com/cosmos/cosmos-sdk/simapp/params"
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||||
|
|
||||||
cosmosTypes "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
txTypes "github.com/cosmos/cosmos-sdk/types/tx"
|
txTypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||||
|
|
||||||
apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
apitypes "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
@ -16,9 +17,14 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type aminoMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ethermintProtoCodec codec.ProtoCodecMarshaler
|
protoCodec codec.ProtoCodecMarshaler
|
||||||
ethermintAminoCodec *codec.LegacyAmino
|
aminoCodec *codec.LegacyAmino
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetEncodingConfig set the encoding config to the singleton codecs (Amino and Protobuf).
|
// SetEncodingConfig set the encoding config to the singleton codecs (Amino and Protobuf).
|
||||||
@ -26,8 +32,8 @@ var (
|
|||||||
// populated with all relevant message types. As a result, we must call this method on app
|
// populated with all relevant message types. As a result, we must call this method on app
|
||||||
// initialization with the app's encoding config.
|
// initialization with the app's encoding config.
|
||||||
func SetEncodingConfig(cfg params.EncodingConfig) {
|
func SetEncodingConfig(cfg params.EncodingConfig) {
|
||||||
ethermintAminoCodec = cfg.Amino
|
aminoCodec = cfg.Amino
|
||||||
ethermintProtoCodec = codec.NewProtoCodec(cfg.InterfaceRegistry)
|
protoCodec = codec.NewProtoCodec(cfg.InterfaceRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the EIP-712 object hash for the given SignDoc bytes by first decoding the bytes into
|
// Get the EIP-712 object hash for the given SignDoc bytes by first decoding the bytes into
|
||||||
@ -59,67 +65,69 @@ func GetEIP712TypedDataForMsg(signDocBytes []byte) (apitypes.TypedData, error) {
|
|||||||
// Attempt to decode as both Amino and Protobuf since the message format is unknown.
|
// Attempt to decode as both Amino and Protobuf since the message format is unknown.
|
||||||
// If either decode works, we can move forward with the corresponding typed data.
|
// If either decode works, we can move forward with the corresponding typed data.
|
||||||
typedDataAmino, errAmino := decodeAminoSignDoc(signDocBytes)
|
typedDataAmino, errAmino := decodeAminoSignDoc(signDocBytes)
|
||||||
if errAmino == nil && verifyEIP712Payload(typedDataAmino) {
|
if errAmino == nil && isValidEIP712Payload(typedDataAmino) {
|
||||||
return typedDataAmino, nil
|
return typedDataAmino, nil
|
||||||
}
|
}
|
||||||
typedDataProtobuf, errProtobuf := decodeProtobufSignDoc(signDocBytes)
|
typedDataProtobuf, errProtobuf := decodeProtobufSignDoc(signDocBytes)
|
||||||
if errProtobuf == nil && verifyEIP712Payload(typedDataProtobuf) {
|
if errProtobuf == nil && isValidEIP712Payload(typedDataProtobuf) {
|
||||||
return typedDataProtobuf, nil
|
return typedDataProtobuf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return apitypes.TypedData{}, fmt.Errorf("could not decode sign doc as either Amino or Protobuf. amino: %v protobuf: %v", errAmino, errProtobuf)
|
return apitypes.TypedData{}, fmt.Errorf("could not decode sign doc as either Amino or Protobuf.\n amino: %v\n protobuf: %v", errAmino, errProtobuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyEIP712Payload ensures that the given TypedData does not contain empty fields from
|
// isValidEIP712Payload ensures that the given TypedData does not contain empty fields from
|
||||||
// an improper initialization.
|
// an improper initialization.
|
||||||
func verifyEIP712Payload(typedData apitypes.TypedData) bool {
|
func isValidEIP712Payload(typedData apitypes.TypedData) bool {
|
||||||
return len(typedData.Message) != 0 && len(typedData.Types) != 0 && typedData.PrimaryType != "" && typedData.Domain != apitypes.TypedDataDomain{}
|
return len(typedData.Message) != 0 && len(typedData.Types) != 0 && typedData.PrimaryType != "" && typedData.Domain != apitypes.TypedDataDomain{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to decode the SignDoc bytes as an Amino SignDoc and return an error on failure
|
// decodeAminoSignDoc attempts to decode the provided sign doc (bytes) as an Amino payload
|
||||||
|
// and returns a signable EIP-712 TypedData object.
|
||||||
func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||||
var aminoDoc legacytx.StdSignDoc
|
var aminoDoc legacytx.StdSignDoc
|
||||||
|
if err := aminoCodec.UnmarshalJSON(signDocBytes, &aminoDoc); err != nil {
|
||||||
if err := ethermintAminoCodec.UnmarshalJSON(signDocBytes, &aminoDoc); err != nil {
|
|
||||||
return apitypes.TypedData{}, err
|
return apitypes.TypedData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap fees
|
|
||||||
var fees legacytx.StdFee
|
var fees legacytx.StdFee
|
||||||
if err := ethermintAminoCodec.UnmarshalJSON(aminoDoc.Fee, &fees); err != nil {
|
if err := aminoCodec.UnmarshalJSON(aminoDoc.Fee, &fees); err != nil {
|
||||||
return apitypes.TypedData{}, err
|
return apitypes.TypedData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(aminoDoc.Msgs) != 1 {
|
// Validate payload messages
|
||||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of messages in SignDoc, expected 1 but got %v", len(aminoDoc.Msgs))
|
msgs := make([]sdk.Msg, len(aminoDoc.Msgs))
|
||||||
|
for i, jsonMsg := range aminoDoc.Msgs {
|
||||||
|
var m sdk.Msg
|
||||||
|
if err := aminoCodec.UnmarshalJSON(jsonMsg, &m); err != nil {
|
||||||
|
return apitypes.TypedData{}, fmt.Errorf("failed to unmarshal sign doc message: %w", err)
|
||||||
|
}
|
||||||
|
msgs[i] = m
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg cosmosTypes.Msg
|
if err := validatePayloadMessages(msgs); err != nil {
|
||||||
if err := ethermintAminoCodec.UnmarshalJSON(aminoDoc.Msgs[0], &msg); err != nil {
|
return apitypes.TypedData{}, err
|
||||||
return apitypes.TypedData{}, fmt.Errorf("failed to unmarshal first message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// By default, use first address in list of signers to cover fee
|
// Use first message for fee payer and type inference
|
||||||
// Currently, support only one signer
|
msg := msgs[0]
|
||||||
if len(msg.GetSigners()) != 1 {
|
|
||||||
return apitypes.TypedData{}, errors.New("expected exactly one signer for message")
|
// By convention, the fee payer is the first address in the list of signers.
|
||||||
}
|
|
||||||
feePayer := msg.GetSigners()[0]
|
feePayer := msg.GetSigners()[0]
|
||||||
feeDelegation := &FeeDelegationOptions{
|
feeDelegation := &FeeDelegationOptions{
|
||||||
FeePayer: feePayer,
|
FeePayer: feePayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse ChainID
|
|
||||||
chainID, err := ethermint.ParseChainID(aminoDoc.ChainID)
|
chainID, err := ethermint.ParseChainID(aminoDoc.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apitypes.TypedData{}, errors.New("invalid chain ID passed as argument")
|
return apitypes.TypedData{}, errors.New("invalid chain ID passed as argument")
|
||||||
}
|
}
|
||||||
|
|
||||||
typedData, err := WrapTxToTypedData(
|
typedData, err := WrapTxToTypedData(
|
||||||
ethermintProtoCodec,
|
protoCodec,
|
||||||
chainID.Uint64(),
|
chainID.Uint64(),
|
||||||
msg,
|
msg,
|
||||||
signDocBytes, // Amino StdSignDocBytes
|
signDocBytes,
|
||||||
feeDelegation,
|
feeDelegation,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,21 +137,19 @@ func decodeAminoSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
|||||||
return typedData, nil
|
return typedData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to decode the SignDoc bytes as a Protobuf SignDoc and return an error on failure
|
// decodeProtobufSignDoc attempts to decode the provided sign doc (bytes) as a Protobuf payload
|
||||||
|
// and returns a signable EIP-712 TypedData object.
|
||||||
func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
||||||
// Decode sign doc
|
|
||||||
signDoc := &txTypes.SignDoc{}
|
signDoc := &txTypes.SignDoc{}
|
||||||
if err := signDoc.Unmarshal(signDocBytes); err != nil {
|
if err := signDoc.Unmarshal(signDocBytes); err != nil {
|
||||||
return apitypes.TypedData{}, err
|
return apitypes.TypedData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode auth info
|
|
||||||
authInfo := &txTypes.AuthInfo{}
|
authInfo := &txTypes.AuthInfo{}
|
||||||
if err := authInfo.Unmarshal(signDoc.AuthInfoBytes); err != nil {
|
if err := authInfo.Unmarshal(signDoc.AuthInfoBytes); err != nil {
|
||||||
return apitypes.TypedData{}, err
|
return apitypes.TypedData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode body
|
|
||||||
body := &txTypes.TxBody{}
|
body := &txTypes.TxBody{}
|
||||||
if err := body.Unmarshal(signDoc.BodyBytes); err != nil {
|
if err := body.Unmarshal(signDoc.BodyBytes); err != nil {
|
||||||
return apitypes.TypedData{}, err
|
return apitypes.TypedData{}, err
|
||||||
@ -154,60 +160,60 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
|||||||
return apitypes.TypedData{}, errors.New("body contains unsupported fields: TimeoutHeight, ExtensionOptions, or NonCriticalExtensionOptions")
|
return apitypes.TypedData{}, errors.New("body contains unsupported fields: TimeoutHeight, ExtensionOptions, or NonCriticalExtensionOptions")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify single message
|
if len(authInfo.SignerInfos) != 1 {
|
||||||
if len(body.Messages) != 1 {
|
return apitypes.TypedData{}, fmt.Errorf("invalid number of signer infos provided, expected 1 got %v", len(authInfo.SignerInfos))
|
||||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of messages, expected 1 got %v", len(body.Messages))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode signer info (single signer for now)
|
// Validate payload messages
|
||||||
|
msgs := make([]sdk.Msg, len(body.Messages))
|
||||||
|
for i, protoMsg := range body.Messages {
|
||||||
|
var m sdk.Msg
|
||||||
|
if err := protoCodec.UnpackAny(protoMsg, &m); err != nil {
|
||||||
|
return apitypes.TypedData{}, fmt.Errorf("could not unpack message object with error %w", err)
|
||||||
|
}
|
||||||
|
msgs[i] = m
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validatePayloadMessages(msgs); err != nil {
|
||||||
|
return apitypes.TypedData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first message for fee payer and type inference
|
||||||
|
msg := msgs[0]
|
||||||
|
|
||||||
signerInfo := authInfo.SignerInfos[0]
|
signerInfo := authInfo.SignerInfos[0]
|
||||||
|
|
||||||
// Parse ChainID
|
|
||||||
chainID, err := ethermint.ParseChainID(signDoc.ChainId)
|
chainID, err := ethermint.ParseChainID(signDoc.ChainId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apitypes.TypedData{}, fmt.Errorf("invalid chain ID passed as argument: %w", err)
|
return apitypes.TypedData{}, fmt.Errorf("invalid chain ID passed as argument: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create StdFee
|
|
||||||
stdFee := &legacytx.StdFee{
|
stdFee := &legacytx.StdFee{
|
||||||
Amount: authInfo.Fee.Amount,
|
Amount: authInfo.Fee.Amount,
|
||||||
Gas: authInfo.Fee.GasLimit,
|
Gas: authInfo.Fee.GasLimit,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse Message (single message only)
|
|
||||||
var msg cosmosTypes.Msg
|
|
||||||
if err := ethermintProtoCodec.UnpackAny(body.Messages[0], &msg); err != nil {
|
|
||||||
return apitypes.TypedData{}, fmt.Errorf("could not unpack message object with error %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify single signer (single signer for now)
|
|
||||||
if len(msg.GetSigners()) != 1 {
|
|
||||||
return apitypes.TypedData{}, fmt.Errorf("invalid number of signers, expected 1 got %v", len(authInfo.SignerInfos))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init fee payer
|
|
||||||
feePayer := msg.GetSigners()[0]
|
feePayer := msg.GetSigners()[0]
|
||||||
feeDelegation := &FeeDelegationOptions{
|
feeDelegation := &FeeDelegationOptions{
|
||||||
FeePayer: feePayer,
|
FeePayer: feePayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get tip
|
|
||||||
tip := authInfo.Tip
|
tip := authInfo.Tip
|
||||||
|
|
||||||
// Create Legacy SignBytes (expected type for WrapTxToTypedData)
|
// WrapTxToTypedData expects the payload as an Amino Sign Doc
|
||||||
signBytes := legacytx.StdSignBytes(
|
signBytes := legacytx.StdSignBytes(
|
||||||
signDoc.ChainId,
|
signDoc.ChainId,
|
||||||
signDoc.AccountNumber,
|
signDoc.AccountNumber,
|
||||||
signerInfo.Sequence,
|
signerInfo.Sequence,
|
||||||
body.TimeoutHeight,
|
body.TimeoutHeight,
|
||||||
*stdFee,
|
*stdFee,
|
||||||
[]cosmosTypes.Msg{msg},
|
msgs,
|
||||||
body.Memo,
|
body.Memo,
|
||||||
tip,
|
tip,
|
||||||
)
|
)
|
||||||
|
|
||||||
typedData, err := WrapTxToTypedData(
|
typedData, err := WrapTxToTypedData(
|
||||||
ethermintProtoCodec,
|
protoCodec,
|
||||||
chainID.Uint64(),
|
chainID.Uint64(),
|
||||||
msg,
|
msg,
|
||||||
signBytes,
|
signBytes,
|
||||||
@ -219,3 +225,61 @@ func decodeProtobufSignDoc(signDocBytes []byte) (apitypes.TypedData, error) {
|
|||||||
|
|
||||||
return typedData, nil
|
return typedData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validatePayloadMessages ensures that the transaction messages can be represented in an EIP-712
|
||||||
|
// encoding by checking that messages exist, are of the same type, and share a single signer.
|
||||||
|
func validatePayloadMessages(msgs []sdk.Msg) error {
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return errors.New("unable to build EIP-712 payload: transaction does contain any messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgType string
|
||||||
|
var msgSigner sdk.AccAddress
|
||||||
|
|
||||||
|
for i, m := range msgs {
|
||||||
|
t, err := getMsgType(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.GetSigners()) != 1 {
|
||||||
|
return errors.New("unable to build EIP-712 payload: expect exactly 1 signer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
msgType = t
|
||||||
|
msgSigner = m.GetSigners()[0]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if t != msgType {
|
||||||
|
return errors.New("unable to build EIP-712 payload: different types of messages detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !msgSigner.Equals(m.GetSigners()[0]) {
|
||||||
|
return errors.New("unable to build EIP-712 payload: multiple signers detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMsgType returns the message type prefix for the given Cosmos SDK Msg
|
||||||
|
func getMsgType(msg sdk.Msg) (string, error) {
|
||||||
|
jsonBytes, err := aminoCodec.MarshalJSON(msg)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonMsg aminoMessage
|
||||||
|
if err := json.Unmarshal(jsonBytes, &jsonMsg); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Type was successfully filled in
|
||||||
|
if jsonMsg.Type == "" {
|
||||||
|
return "", errors.New("could not decode message: type is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonMsg.Type, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user