feat: Add Tips middleware (#10208)
<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description R4R Closes: #9912 This PR introduces 1 new middleware: - `TipsMiddleware`: transfer tip from tipper to feePayer when relevant. It also makes sure in the DIRECT_AUX sign mode handler that the fee payer cannot use that sign mode. Depends on: - [x] #10028 - [x] #10268 - [x] #10322 - [x] #10346 <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
parent
3184cfcae7
commit
a72f6a8d4f
@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* [\#9837](https://github.com/cosmos/cosmos-sdk/issues/9837) `--generate-only` flag will accept the keyname now.
|
||||
* [\#10326](https://github.com/cosmos/cosmos-sdk/pull/10326) `x/authz` add query all grants by granter query.
|
||||
* [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) Add `fee.{payer,granter}` and `tip` fields to StdSignDoc for signing tipped transactions.
|
||||
* [\#10208](https://github.com/cosmos/cosmos-sdk/pull/10208) Add `TipsTxMiddleware` for transferring tips.
|
||||
* [\#10379](https://github.com/cosmos/cosmos-sdk/pull/10379) Add validation to `x/upgrade` CLI `software-upgrade` command `--plan-info` value.
|
||||
|
||||
### Improvements
|
||||
@ -105,7 +106,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* Rename simulation helper methods `baseapp.{Check,Deliver}` to `baseapp.Sim{Check,Deliver}**`.
|
||||
* (x/gov) [\#10373](https://github.com/cosmos/cosmos-sdk/pull/10373) Removed gov `keeper.{MustMarshal, MustUnmarshal}`.
|
||||
* [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) StdSignBytes takes a new argument of type `*tx.Tip` for signing over tips using LEGACY_AMINO_JSON.
|
||||
|
||||
* [\#10208](https://github.com/cosmos/cosmos-sdk/pull/10208) The `x/auth/signing.Tx` interface now also includes a new `GetTip() *tx.Tip` method for verifying tipped transactions. The `x/auth/types` expected BankKeeper interface now expects the `SendCoins` method too.
|
||||
|
||||
### Client Breaking Changes
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ type (
|
||||
SetGasLimit(limit uint64)
|
||||
SetTip(tip *tx.Tip)
|
||||
SetTimeoutHeight(height uint64)
|
||||
SetFeePayer(feeGranter sdk.AccAddress)
|
||||
SetFeePayer(feePayer sdk.AccAddress)
|
||||
SetFeeGranter(feeGranter sdk.AccAddress)
|
||||
AddAuxSignerData(tx.AuxSignerData) error
|
||||
}
|
||||
|
||||
@ -48,6 +48,23 @@ func (a *AuxSignerData) ValidateBasic() error {
|
||||
return a.GetSignDoc().ValidateBasic()
|
||||
}
|
||||
|
||||
// GetSignaturesV2 gets the SignatureV2 of the aux signer.
|
||||
func (a *AuxSignerData) GetSignatureV2() (signing.SignatureV2, error) {
|
||||
pk, ok := a.SignDoc.PublicKey.GetCachedValue().(cryptotypes.PubKey)
|
||||
if !ok {
|
||||
return signing.SignatureV2{}, sdkerrors.ErrInvalidType.Wrapf("expected %T, got %T", (cryptotypes.PubKey)(nil), pk)
|
||||
}
|
||||
|
||||
return signing.SignatureV2{
|
||||
PubKey: pk,
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: a.Mode,
|
||||
Signature: a.Sig,
|
||||
},
|
||||
Sequence: a.SignDoc.Sequence,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method
|
||||
func (a *AuxSignerData) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
|
||||
return a.GetSignDoc().UnpackInterfaces(unpacker)
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
@ -77,6 +78,12 @@ func TestAuxSignerData(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
sigV2, err := tc.sd.GetSignatureV2()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.sd.Mode, sigV2.Data.(*signing.SingleSignatureData).SignMode)
|
||||
require.Equal(t, tc.sd.Sig, sigV2.Data.(*signing.SingleSignatureData).Signature)
|
||||
require.Equal(t, tc.sd.SignDoc.Sequence, sigV2.Sequence)
|
||||
require.True(t, tc.sd.SignDoc.PublicKey.GetCachedValue().(cryptotypes.PubKey).Equals(sigV2.PubKey))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -93,6 +93,7 @@ func NewDefaultTxHandler(options TxHandlerOptions) (tx.Handler, error) {
|
||||
ValidateSigCountMiddleware(options.AccountKeeper),
|
||||
SigGasConsumeMiddleware(options.AccountKeeper, sigGasConsumer),
|
||||
SigVerificationMiddleware(options.AccountKeeper, options.SignModeHandler),
|
||||
NewTipMiddleware(options.BankKeeper),
|
||||
IncrementSequenceMiddleware(options.AccountKeeper),
|
||||
), nil
|
||||
}
|
||||
|
||||
@ -23,13 +23,15 @@ import (
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
var testCoins = sdk.Coins{sdk.NewInt64Coin("atom", 10000000)}
|
||||
|
||||
// Test that simulate transaction accurately estimates gas cost
|
||||
func (s *MWTestSuite) TestSimulateGasCost() {
|
||||
ctx := s.SetupTest(false) // reset
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 3)
|
||||
accounts := s.createTestAccounts(ctx, 3, testCoins)
|
||||
msgs := []sdk.Msg{
|
||||
testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress()),
|
||||
testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress()),
|
||||
@ -166,7 +168,7 @@ func (s *MWTestSuite) TestTxHandlerAccountNumbers() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 2)
|
||||
accounts := s.createTestAccounts(ctx, 2, testCoins)
|
||||
feeAmount := testdata.NewTestFeeAmount()
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
|
||||
@ -248,7 +250,7 @@ func (s *MWTestSuite) TestTxHandlerAccountNumbersAtBlockHeightZero() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 2)
|
||||
accounts := s.createTestAccounts(ctx, 2, testCoins)
|
||||
feeAmount := testdata.NewTestFeeAmount()
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
|
||||
@ -331,7 +333,7 @@ func (s *MWTestSuite) TestTxHandlerSequences() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 3)
|
||||
accounts := s.createTestAccounts(ctx, 3, testCoins)
|
||||
feeAmount := testdata.NewTestFeeAmount()
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
|
||||
@ -524,7 +526,7 @@ func (s *MWTestSuite) TestTxHandlerMemoGas() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 1)
|
||||
accounts := s.createTestAccounts(ctx, 1, testCoins)
|
||||
msgs := []sdk.Msg{testdata.NewTestMsg(accounts[0].acc.GetAddress())}
|
||||
privs, accNums, accSeqs := []cryptotypes.PrivKey{accounts[0].priv}, []uint64{0}, []uint64{0}
|
||||
|
||||
@ -594,7 +596,7 @@ func (s *MWTestSuite) TestTxHandlerMultiSigner() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 3)
|
||||
accounts := s.createTestAccounts(ctx, 3, testCoins)
|
||||
msg1 := testdata.NewTestMsg(accounts[0].acc.GetAddress(), accounts[1].acc.GetAddress())
|
||||
msg2 := testdata.NewTestMsg(accounts[2].acc.GetAddress(), accounts[0].acc.GetAddress())
|
||||
msg3 := testdata.NewTestMsg(accounts[1].acc.GetAddress(), accounts[2].acc.GetAddress())
|
||||
@ -667,7 +669,7 @@ func (s *MWTestSuite) TestTxHandlerBadSignBytes() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 2)
|
||||
accounts := s.createTestAccounts(ctx, 2, testCoins)
|
||||
msg0 := testdata.NewTestMsg(accounts[0].acc.GetAddress())
|
||||
|
||||
// Variable data per test case
|
||||
@ -793,7 +795,7 @@ func (s *MWTestSuite) TestTxHandlerSetPubKey() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 2)
|
||||
accounts := s.createTestAccounts(ctx, 2, testCoins)
|
||||
feeAmount := testdata.NewTestFeeAmount()
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
|
||||
@ -971,7 +973,7 @@ func (s *MWTestSuite) TestTxHandlerSigLimitExceeded() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 8)
|
||||
accounts := s.createTestAccounts(ctx, 8, testCoins)
|
||||
var addrs []sdk.AccAddress
|
||||
var privs []cryptotypes.PrivKey
|
||||
for i := 0; i < 8; i++ {
|
||||
@ -1029,7 +1031,7 @@ func (s *MWTestSuite) TestCustomSignatureVerificationGasConsumer() {
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 1)
|
||||
accounts := s.createTestAccounts(ctx, 1, testCoins)
|
||||
txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
|
||||
txBuilder.SetGasLimit(testdata.NewTestGasLimit())
|
||||
txBuilder.SetMsgs(testdata.NewTestMsg(accounts[0].acc.GetAddress()))
|
||||
@ -1073,7 +1075,7 @@ func (s *MWTestSuite) TestTxHandlerReCheck() {
|
||||
txBuilder := s.clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Same data for every test cases
|
||||
accounts := s.createTestAccounts(ctx, 1)
|
||||
accounts := s.createTestAccounts(ctx, 1, testCoins)
|
||||
|
||||
feeAmount := testdata.NewTestFeeAmount()
|
||||
gasLimit := testdata.NewTestGasLimit()
|
||||
|
||||
@ -26,8 +26,9 @@ import (
|
||||
|
||||
// testAccount represents an account used in the tests in x/auth/middleware.
|
||||
type testAccount struct {
|
||||
acc authtypes.AccountI
|
||||
priv cryptotypes.PrivKey
|
||||
acc authtypes.AccountI
|
||||
priv cryptotypes.PrivKey
|
||||
accNum uint64
|
||||
}
|
||||
|
||||
// MWTestSuite is a test suite to be used with middleware tests.
|
||||
@ -42,7 +43,7 @@ type MWTestSuite struct {
|
||||
// returns context and app with params set on account keeper
|
||||
func createTestApp(t *testing.T, isCheckTx bool) (*simapp.SimApp, sdk.Context) {
|
||||
app := simapp.Setup(t, isCheckTx)
|
||||
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{}).WithBlockGasMeter(sdk.NewInfiniteGasMeter())
|
||||
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{Height: app.LastBlockHeight() + 1}).WithBlockGasMeter(sdk.NewInfiniteGasMeter())
|
||||
app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams())
|
||||
|
||||
return app, ctx
|
||||
@ -52,7 +53,6 @@ func createTestApp(t *testing.T, isCheckTx bool) (*simapp.SimApp, sdk.Context) {
|
||||
func (s *MWTestSuite) SetupTest(isCheckTx bool) sdk.Context {
|
||||
var ctx sdk.Context
|
||||
s.app, ctx = createTestApp(s.T(), isCheckTx)
|
||||
ctx = ctx.WithBlockHeight(1)
|
||||
|
||||
// Set up TxConfig.
|
||||
encodingConfig := simapp.MakeTestEncodingConfig()
|
||||
@ -89,25 +89,23 @@ func (s *MWTestSuite) SetupTest(isCheckTx bool) sdk.Context {
|
||||
|
||||
// createTestAccounts creates `numAccs` accounts, and return all relevant
|
||||
// information about them including their private keys.
|
||||
func (s *MWTestSuite) createTestAccounts(ctx sdk.Context, numAccs int) []testAccount {
|
||||
func (s *MWTestSuite) createTestAccounts(ctx sdk.Context, numAccs int, coins sdk.Coins) []testAccount {
|
||||
var accounts []testAccount
|
||||
|
||||
for i := 0; i < numAccs; i++ {
|
||||
priv, _, addr := testdata.KeyTestPubAddr()
|
||||
acc := s.app.AccountKeeper.NewAccountWithAddress(ctx, addr)
|
||||
err := acc.SetAccountNumber(uint64(i))
|
||||
accNum := uint64(i)
|
||||
err := acc.SetAccountNumber(accNum)
|
||||
s.Require().NoError(err)
|
||||
s.app.AccountKeeper.SetAccount(ctx, acc)
|
||||
someCoins := sdk.Coins{
|
||||
sdk.NewInt64Coin("atom", 10000000),
|
||||
}
|
||||
err = s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, someCoins)
|
||||
err = s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, someCoins)
|
||||
err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, coins)
|
||||
s.Require().NoError(err)
|
||||
|
||||
accounts = append(accounts, testAccount{acc, priv})
|
||||
accounts = append(accounts, testAccount{acc, priv, accNum})
|
||||
}
|
||||
|
||||
return accounts
|
||||
|
||||
94
x/auth/middleware/tips.go
Normal file
94
x/auth/middleware/tips.go
Normal file
@ -0,0 +1,94 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
type tipsTxHandler struct {
|
||||
next tx.Handler
|
||||
bankKeeper types.BankKeeper
|
||||
}
|
||||
|
||||
// NewTipMiddleware returns a new middleware for handling transactions with
|
||||
// tips.
|
||||
func NewTipMiddleware(bankKeeper types.BankKeeper) tx.Middleware {
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return tipsTxHandler{txh, bankKeeper}
|
||||
}
|
||||
}
|
||||
|
||||
var _ tx.Handler = tipsTxHandler{}
|
||||
|
||||
// CheckTx implements tx.Handler.CheckTx.
|
||||
func (txh tipsTxHandler) CheckTx(ctx context.Context, sdkTx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error) {
|
||||
res, err := txh.next.CheckTx(ctx, sdkTx, req)
|
||||
if err != nil {
|
||||
return abci.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
tipTx, ok := sdkTx.(tx.TipTx)
|
||||
if !ok || tipTx.GetTip() == nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if err := txh.transferTip(ctx, tipTx); err != nil {
|
||||
return abci.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler.DeliverTx.
|
||||
func (txh tipsTxHandler) DeliverTx(ctx context.Context, sdkTx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error) {
|
||||
res, err := txh.next.DeliverTx(ctx, sdkTx, req)
|
||||
if err != nil {
|
||||
return abci.ResponseDeliverTx{}, err
|
||||
}
|
||||
|
||||
tipTx, ok := sdkTx.(tx.TipTx)
|
||||
if !ok || tipTx.GetTip() == nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if err := txh.transferTip(ctx, tipTx); err != nil {
|
||||
return abci.ResponseDeliverTx{}, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler.SimulateTx method.
|
||||
func (txh tipsTxHandler) SimulateTx(ctx context.Context, sdkTx sdk.Tx, req tx.RequestSimulateTx) (tx.ResponseSimulateTx, error) {
|
||||
res, err := txh.next.SimulateTx(ctx, sdkTx, req)
|
||||
if err != nil {
|
||||
return tx.ResponseSimulateTx{}, err
|
||||
}
|
||||
|
||||
tipTx, ok := sdkTx.(tx.TipTx)
|
||||
if !ok || tipTx.GetTip() == nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if err := txh.transferTip(ctx, tipTx); err != nil {
|
||||
return tx.ResponseSimulateTx{}, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// transferTip transfers the tip from the tipper to the fee payer.
|
||||
func (txh tipsTxHandler) transferTip(ctx context.Context, tipTx tx.TipTx) error {
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
tipper, err := sdk.AccAddressFromBech32(tipTx.GetTip().Tipper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txh.bankKeeper.SendCoins(sdkCtx, tipper, tipTx.FeePayer(), tipTx.GetTip().Amount)
|
||||
}
|
||||
205
x/auth/middleware/tips_test.go
Normal file
205
x/auth/middleware/tips_test.go
Normal file
@ -0,0 +1,205 @@
|
||||
package middleware_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||
)
|
||||
|
||||
var initialRegens = sdk.NewCoins(sdk.NewCoin("regen", sdk.NewInt(1000)))
|
||||
var initialAtoms = sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(1000)))
|
||||
|
||||
// setupAcctsForTips sets up 2 accounts:
|
||||
// - tipper has 1000 regens
|
||||
// - feePayer has 1000 atoms and 1000 regens
|
||||
func (s *MWTestSuite) setupAcctsForTips(ctx sdk.Context) (sdk.Context, []testAccount) {
|
||||
accts := s.createTestAccounts(ctx, 2, initialRegens)
|
||||
feePayer := accts[1]
|
||||
err := s.app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, initialAtoms)
|
||||
s.Require().NoError(err)
|
||||
err = s.app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, feePayer.acc.GetAddress(), initialAtoms)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create dummy proposal for tipper to vote on.
|
||||
prop, err := govtypes.NewProposal(govtypes.NewTextProposal("foo", "bar"), 1, time.Now(), time.Now().Add(time.Hour))
|
||||
s.Require().NoError(err)
|
||||
s.app.GovKeeper.SetProposal(ctx, prop)
|
||||
s.app.GovKeeper.ActivateVotingPeriod(ctx, prop)
|
||||
|
||||
// Move to next block to commit previous data to state.
|
||||
s.app.EndBlock(abci.RequestEndBlock{Height: ctx.BlockHeight()})
|
||||
s.app.Commit()
|
||||
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
|
||||
s.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: ctx.BlockHeight()}})
|
||||
|
||||
return ctx, accts
|
||||
}
|
||||
|
||||
func (s *MWTestSuite) TestTips() {
|
||||
var msg sdk.Msg
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
tip sdk.Coins
|
||||
fee sdk.Coins
|
||||
gasLimit uint64
|
||||
expErr bool
|
||||
expErrStr string
|
||||
}{
|
||||
{
|
||||
"wrong tip denom",
|
||||
sdk.NewCoins(sdk.NewCoin("foobar", sdk.NewInt(1000))), initialAtoms, 200000,
|
||||
true, "0foobar is smaller than 1000foobar: insufficient funds",
|
||||
},
|
||||
{
|
||||
"insufficient tip from tipper",
|
||||
sdk.NewCoins(sdk.NewCoin("regen", sdk.NewInt(5000))), initialAtoms, 200000,
|
||||
true, "1000regen is smaller than 5000regen: insufficient funds",
|
||||
},
|
||||
{
|
||||
"insufficient fees from feePayer",
|
||||
initialRegens, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(5000))), 200000,
|
||||
true, "1000atom is smaller than 5000atom: insufficient funds: insufficient funds",
|
||||
},
|
||||
{
|
||||
"insufficient gas",
|
||||
initialRegens, initialAtoms, 100,
|
||||
true, "out of gas in location: ReadFlat; gasWanted: 100, gasUsed: 1000: out of gas",
|
||||
},
|
||||
{
|
||||
"happy case",
|
||||
initialRegens, initialAtoms, 200000,
|
||||
false, "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
s.Run(tc.name, func() {
|
||||
ctx := s.SetupTest(false) // reset
|
||||
ctx, accts := s.setupAcctsForTips(ctx)
|
||||
tipper, feePayer := accts[0], accts[1]
|
||||
|
||||
msg = govtypes.NewMsgVote(tipper.acc.GetAddress(), 1, govtypes.OptionYes)
|
||||
|
||||
auxSignerData := s.mkTipperAuxSignerData(tipper.priv, msg, tc.tip, signing.SignMode_SIGN_MODE_DIRECT_AUX, tipper.accNum, 0, ctx.ChainID())
|
||||
feePayerTxBuilder := s.mkFeePayerTxBuilder(s.clientCtx, auxSignerData, feePayer.priv, signing.SignMode_SIGN_MODE_DIRECT, tx.Fee{Amount: tc.fee, GasLimit: tc.gasLimit}, feePayer.accNum, 0, ctx.ChainID())
|
||||
|
||||
_, res, err := s.app.SimDeliver(s.clientCtx.TxConfig.TxEncoder(), feePayerTxBuilder.GetTx())
|
||||
|
||||
if tc.expErr {
|
||||
s.Require().Error(err)
|
||||
s.Require().Contains(err.Error(), tc.expErrStr)
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(res)
|
||||
|
||||
// Move to next block to commit previous data to state.
|
||||
s.app.EndBlock(abci.RequestEndBlock{Height: ctx.BlockHeight()})
|
||||
s.app.Commit()
|
||||
|
||||
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
|
||||
s.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: ctx.BlockHeight()}})
|
||||
|
||||
// Make sure tip is correctly transferred to feepayer, and fee is paid.
|
||||
expTipperRegens := initialRegens.Sub(tc.tip)
|
||||
expFeePayerRegens := initialRegens.Add(tc.tip...)
|
||||
expFeePayerAtoms := initialAtoms.Sub(tc.fee)
|
||||
s.Require().True(expTipperRegens.AmountOf("regen").Equal(s.app.BankKeeper.GetBalance(ctx, tipper.acc.GetAddress(), "regen").Amount))
|
||||
s.Require().True(expFeePayerRegens.AmountOf("regen").Equal(s.app.BankKeeper.GetBalance(ctx, feePayer.acc.GetAddress(), "regen").Amount))
|
||||
s.Require().True(expFeePayerAtoms.AmountOf("atom").Equal(s.app.BankKeeper.GetBalance(ctx, feePayer.acc.GetAddress(), "atom").Amount))
|
||||
// Make sure MsgVote has been submitted by tipper.
|
||||
votes := s.app.GovKeeper.GetAllVotes(ctx)
|
||||
s.Require().Len(votes, 1)
|
||||
s.Require().Equal(tipper.acc.GetAddress().String(), votes[0].Voter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MWTestSuite) mkTipperAuxSignerData(
|
||||
tipperPriv cryptotypes.PrivKey, msg sdk.Msg, tip sdk.Coins,
|
||||
signMode signing.SignMode, accNum, accSeq uint64, chainID string,
|
||||
) tx.AuxSignerData {
|
||||
tipperAddr := sdk.AccAddress(tipperPriv.PubKey().Address()).String()
|
||||
b := clienttx.NewAuxTxBuilder()
|
||||
b.SetAddress(tipperAddr)
|
||||
b.SetAccountNumber(accNum)
|
||||
b.SetSequence(accSeq)
|
||||
err := b.SetMsgs(msg)
|
||||
s.Require().NoError(err)
|
||||
b.SetTip(&tx.Tip{Amount: tip, Tipper: tipperAddr})
|
||||
err = b.SetSignMode(signMode)
|
||||
s.Require().NoError(err)
|
||||
b.SetSequence(accSeq)
|
||||
err = b.SetPubKey(tipperPriv.PubKey())
|
||||
s.Require().NoError(err)
|
||||
b.SetChainID(chainID)
|
||||
|
||||
signBz, err := b.GetSignBytes()
|
||||
s.Require().NoError(err)
|
||||
sig, err := tipperPriv.Sign(signBz)
|
||||
s.Require().NoError(err)
|
||||
b.SetSignature(sig)
|
||||
|
||||
auxSignerData, err := b.GetAuxSignerData()
|
||||
s.Require().NoError(err)
|
||||
|
||||
return auxSignerData
|
||||
}
|
||||
|
||||
func (s *MWTestSuite) mkFeePayerTxBuilder(
|
||||
clientCtx client.Context,
|
||||
auxSignerData tx.AuxSignerData,
|
||||
feePayerPriv cryptotypes.PrivKey, signMode signing.SignMode,
|
||||
fee tx.Fee, accNum, accSeq uint64, chainID string,
|
||||
) client.TxBuilder {
|
||||
txBuilder := clientCtx.TxConfig.NewTxBuilder()
|
||||
err := txBuilder.AddAuxSignerData(auxSignerData)
|
||||
s.Require().NoError(err)
|
||||
txBuilder.SetFeePayer(sdk.AccAddress(feePayerPriv.PubKey().Address()))
|
||||
txBuilder.SetFeeAmount(fee.Amount)
|
||||
txBuilder.SetGasLimit(fee.GasLimit)
|
||||
|
||||
// Calling SetSignatures with empty sig to populate AuthInfo.
|
||||
tipperSigsV2, err := auxSignerData.GetSignatureV2()
|
||||
s.Require().NoError(err)
|
||||
feePayerSigV2 := signing.SignatureV2{
|
||||
PubKey: feePayerPriv.PubKey(),
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signMode,
|
||||
Signature: nil,
|
||||
}}
|
||||
sigsV2 := append([]signing.SignatureV2{tipperSigsV2}, feePayerSigV2)
|
||||
txBuilder.SetSignatures(sigsV2...)
|
||||
|
||||
// Actually sign the data.
|
||||
signerData := authsigning.SignerData{
|
||||
Address: sdk.AccAddress(feePayerPriv.PubKey().Address()).String(),
|
||||
ChainID: chainID,
|
||||
AccountNumber: accNum,
|
||||
Sequence: accSeq,
|
||||
SignerIndex: 1,
|
||||
}
|
||||
feePayerSigV2, err = clienttx.SignWithPrivKey(
|
||||
signMode, signerData,
|
||||
txBuilder, feePayerPriv, clientCtx.TxConfig, accSeq)
|
||||
s.Require().NoError(err)
|
||||
sigsV2 = append([]signing.SignatureV2{tipperSigsV2}, feePayerSigV2)
|
||||
err = txBuilder.SetSignatures(sigsV2...)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return txBuilder
|
||||
}
|
||||
@ -6,6 +6,7 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
)
|
||||
@ -15,6 +16,7 @@ var (
|
||||
_ sdk.Tx = (*StdTx)(nil)
|
||||
_ sdk.TxWithMemo = (*StdTx)(nil)
|
||||
_ sdk.FeeTx = (*StdTx)(nil)
|
||||
_ tx.TipTx = (*StdTx)(nil)
|
||||
_ codectypes.UnpackInterfacesMessage = (*StdTx)(nil)
|
||||
|
||||
_ codectypes.UnpackInterfacesMessage = (*StdSignature)(nil)
|
||||
@ -232,6 +234,9 @@ func (tx StdTx) FeeGranter() sdk.AccAddress {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTip always returns nil for StdTx
|
||||
func (tx StdTx) GetTip() *tx.Tip { return nil }
|
||||
|
||||
func (tx StdTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
|
||||
for _, m := range tx.Msgs {
|
||||
err := codectypes.UnpackInterfaces(m, unpacker)
|
||||
|
||||
@ -3,6 +3,7 @@ package signing
|
||||
import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
)
|
||||
|
||||
@ -22,5 +23,6 @@ type Tx interface {
|
||||
|
||||
types.TxWithMemo
|
||||
types.FeeTx
|
||||
tx.TipTx
|
||||
types.TxWithTimeoutHeight
|
||||
}
|
||||
|
||||
@ -89,6 +89,7 @@ func (s *TxConfigTestSuite) TestTxBuilderSetSignatures() {
|
||||
msigAddr := sdk.AccAddress(multisigPk.Address())
|
||||
msg2 := testdata.NewTestMsg(msigAddr)
|
||||
err := txBuilder.SetMsgs(msg, msg2)
|
||||
txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// check that validation fails
|
||||
|
||||
@ -44,11 +44,24 @@ func (signModeDirectAuxHandler) GetSignBytes(
|
||||
return nil, sdkerrors.ErrInvalidRequest.Wrapf("got empty pubkey for signer #%d in %s handler", data.SignerIndex, signingtypes.SignMode_SIGN_MODE_DIRECT_AUX)
|
||||
}
|
||||
|
||||
addr := data.Address
|
||||
if addr == "" {
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "got empty address in %s handler", signingtypes.SignMode_SIGN_MODE_DIRECT_AUX)
|
||||
}
|
||||
|
||||
feePayer := protoTx.FeePayer().String()
|
||||
|
||||
// Fee payer cannot use SIGN_MODE_DIRECT_AUX, because SIGN_MODE_DIRECT_AUX
|
||||
// does not sign over fees, which would create malleability issues.
|
||||
if feePayer == data.Address {
|
||||
return nil, sdkerrors.ErrUnauthorized.Wrapf("fee payer %s cannot sign with %s", feePayer, signingtypes.SignMode_SIGN_MODE_DIRECT_AUX)
|
||||
}
|
||||
|
||||
signDocDirectAux := types.SignDocDirectAux{
|
||||
BodyBytes: protoTx.getBodyBytes(),
|
||||
ChainId: data.ChainID,
|
||||
AccountNumber: data.AccountNumber,
|
||||
Sequence: data.Sequence,
|
||||
Sequence: signerInfo.Sequence,
|
||||
Tip: protoTx.tx.AuthInfo.Tip,
|
||||
PublicKey: signerInfo.PublicKey,
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import (
|
||||
|
||||
func TestDirectAuxHandler(t *testing.T) {
|
||||
privKey, pubkey, addr := testdata.KeyTestPubAddr()
|
||||
_, feePayerPubKey, feePayerAddr := testdata.KeyTestPubAddr()
|
||||
interfaceRegistry := codectypes.NewInterfaceRegistry()
|
||||
interfaceRegistry.RegisterImplementations((*sdk.Msg)(nil), &testdata.TestMsg{})
|
||||
marshaler := codec.NewProtoCodec(interfaceRegistry)
|
||||
@ -24,9 +25,10 @@ func TestDirectAuxHandler(t *testing.T) {
|
||||
txConfig := NewTxConfig(marshaler, []signingtypes.SignMode{signingtypes.SignMode_SIGN_MODE_DIRECT_AUX})
|
||||
txBuilder := txConfig.NewTxBuilder()
|
||||
|
||||
chainID := "test-chain"
|
||||
memo := "sometestmemo"
|
||||
msgs := []sdk.Msg{testdata.NewTestMsg(addr)}
|
||||
accSeq := uint64(2) // Arbitrary account sequence
|
||||
accNum, accSeq := uint64(1), uint64(2) // Arbitrary account number/sequence
|
||||
|
||||
any, err := codectypes.NewAnyWithValue(pubkey)
|
||||
require.NoError(t, err)
|
||||
@ -40,26 +42,46 @@ func TestDirectAuxHandler(t *testing.T) {
|
||||
Data: sigData,
|
||||
Sequence: accSeq,
|
||||
}
|
||||
feePayerSig := signingtypes.SignatureV2{
|
||||
PubKey: feePayerPubKey,
|
||||
Data: sigData,
|
||||
Sequence: accSeq,
|
||||
}
|
||||
|
||||
fee := txtypes.Fee{Amount: sdk.NewCoins(sdk.NewInt64Coin("atom", 150)), GasLimit: 20000}
|
||||
tip := &txtypes.Tip{Amount: sdk.NewCoins(sdk.NewInt64Coin("tip-token", 10))}
|
||||
|
||||
err = txBuilder.SetMsgs(msgs...)
|
||||
require.NoError(t, err)
|
||||
txBuilder.SetMemo(memo)
|
||||
txBuilder.SetFeeAmount(fee.Amount)
|
||||
txBuilder.SetFeePayer(feePayerAddr)
|
||||
txBuilder.SetGasLimit(fee.GasLimit)
|
||||
txBuilder.SetTip(tip)
|
||||
|
||||
err = txBuilder.SetSignatures(sig)
|
||||
err = txBuilder.SetSignatures(sig, feePayerSig)
|
||||
require.NoError(t, err)
|
||||
|
||||
signingData := signing.SignerData{
|
||||
Address: addr.String(),
|
||||
ChainID: "test-chain",
|
||||
AccountNumber: 1,
|
||||
ChainID: chainID,
|
||||
AccountNumber: accNum,
|
||||
SignerIndex: 0,
|
||||
}
|
||||
feePayerSigningData := signing.SignerData{
|
||||
Address: feePayerAddr.String(),
|
||||
ChainID: chainID,
|
||||
AccountNumber: accNum,
|
||||
SignerIndex: 1,
|
||||
}
|
||||
|
||||
modeHandler := signModeDirectAuxHandler{}
|
||||
|
||||
t.Log("verify fee payer cannot use SIGN_MODE_DIRECT_AUX")
|
||||
_, err = modeHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_DIRECT_AUX, feePayerSigningData, txBuilder.GetTx())
|
||||
require.EqualError(t, err, fmt.Sprintf("fee payer %s cannot sign with %s: unauthorized", feePayerAddr.String(), signingtypes.SignMode_SIGN_MODE_DIRECT_AUX))
|
||||
|
||||
t.Log("verify GetSignBytes with generating sign bytes by marshaling signDocDirectAux")
|
||||
signBytes, err := modeHandler.GetSignBytes(signingtypes.SignMode_SIGN_MODE_DIRECT_AUX, signingData, txBuilder.GetTx())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, signBytes)
|
||||
@ -79,12 +101,13 @@ func TestDirectAuxHandler(t *testing.T) {
|
||||
}
|
||||
bodyBytes := marshaler.MustMarshal(txBody)
|
||||
|
||||
t.Log("verify GetSignBytes with generating sign bytes by marshaling signDocDirectAux")
|
||||
signDocDirectAux := txtypes.SignDocDirectAux{
|
||||
AccountNumber: 1,
|
||||
AccountNumber: accNum,
|
||||
BodyBytes: bodyBytes,
|
||||
ChainId: "test-chain",
|
||||
PublicKey: any,
|
||||
Sequence: accSeq,
|
||||
Tip: tip,
|
||||
}
|
||||
|
||||
expectedSignBytes, err := signDocDirectAux.Marshal()
|
||||
|
||||
@ -6,5 +6,6 @@ import (
|
||||
|
||||
// BankKeeper defines the contract needed for supply related APIs (noalias)
|
||||
type BankKeeper interface {
|
||||
SendCoins(ctx sdk.Context, from, to sdk.AccAddress, amt sdk.Coins) error
|
||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user