Skip to content

Commit

Permalink
Merge pull request #264 from terra-money/feat/pre-transaction-hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
freeelancer authored Feb 22, 2024
2 parents 4de7aaf + 777132c commit 965500f
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 59 deletions.
1 change: 1 addition & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
// ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
// ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
smartaccountante.NewSmartAccountAuthDecorator(*options.SmartAccountKeeper, options.WasmKeeper, options.AccountKeeper, sigGasConsumer, options.SignModeHandler),
smartaccountante.NewPreTransactionHookDecorator(*options.SmartAccountKeeper, options.WasmKeeper),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCkeeper),
}
Expand Down
3 changes: 0 additions & 3 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package keepers

import (

// #nosec G702

"path/filepath"

"github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router"
Expand Down
5 changes: 2 additions & 3 deletions app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/terra-money/core/v2/x/smartaccount"
smartaccounttypes "github.com/terra-money/core/v2/x/smartaccount/types"

"github.com/cosmos/cosmos-sdk/x/gov"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
Expand Down Expand Up @@ -77,9 +79,6 @@ import (
"github.com/terra-money/alliance/x/alliance"
feeshare "github.com/terra-money/core/v2/x/feeshare"

"github.com/terra-money/core/v2/x/smartaccount"
smartaccounttypes "github.com/terra-money/core/v2/x/smartaccount/types"

ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
terrappsparams "github.com/terra-money/core/v2/app/params"

Expand Down
17 changes: 12 additions & 5 deletions app/test_helpers/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/crypto/ed25519"
"github.com/cometbft/cometbft/libs/log"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/stretchr/testify/suite"
Expand All @@ -16,6 +15,8 @@ import (
tokenfactorytypes "github.com/terra-money/core/v2/x/tokenfactory/types"

"github.com/cosmos/cosmos-sdk/baseapp"
secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
Expand All @@ -32,6 +33,7 @@ type AppTestSuite struct {
Ctx sdk.Context
QueryHelper *baseapp.QueryServiceTestHelper
TestAccs []sdk.AccAddress
TestAccPrivs []cryptotypes.PrivKey
EncodingConfig appparams.EncodingConfig
}

Expand Down Expand Up @@ -80,7 +82,9 @@ func (s *AppTestSuite) Setup() {

s.App.Keepers.DistrKeeper.SetFeePool(s.Ctx, distrtypes.InitialFeePool())

s.TestAccs = s.CreateRandomAccounts(3)
testAccounts, privKeys := s.CreateRandomAccounts(3)
s.TestAccs = testAccounts
s.TestAccPrivs = privKeys
}

func (s *AppTestSuite) AssertEventEmitted(ctx sdk.Context, eventTypeExpected string, numEventsExpected int) {
Expand All @@ -96,17 +100,20 @@ func (s *AppTestSuite) AssertEventEmitted(ctx sdk.Context, eventTypeExpected str
}

// CreateRandomAccounts is a function return a list of randomly generated AccAddresses
func (s *AppTestSuite) CreateRandomAccounts(numAccts int) []sdk.AccAddress {
func (s *AppTestSuite) CreateRandomAccounts(numAccts int) ([]sdk.AccAddress, []cryptotypes.PrivKey) {
testAddrs := make([]sdk.AccAddress, numAccts)
testPrivKeys := make([]cryptotypes.PrivKey, numAccts)
for i := 0; i < numAccts; i++ {
pk := ed25519.GenPrivKey().PubKey()
priv := secp256k1.GenPrivKey()
pk := priv.PubKey()
testAddrs[i] = sdk.AccAddress(pk.Address())
testPrivKeys[i] = priv

err := s.FundAcc(testAddrs[i], sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)))
s.Require().NoError(err)
}

return testAddrs
return testAddrs, testPrivKeys
}

// FundAcc funds target address with specified amount.
Expand Down
104 changes: 104 additions & 0 deletions x/smartaccount/ante/pretransaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ante

import (
"encoding/json"

sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrortypes "github.com/cosmos/cosmos-sdk/types/errors"
tx2 "github.com/cosmos/cosmos-sdk/types/tx"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"

wasmvmtypes "github.com/CosmWasm/wasmvm/types"
smartaccounttypes "github.com/terra-money/core/v2/x/smartaccount/types"
)

// SmartAccountCheckDecorator does authentication for smart accounts
type PreTransactionHookDecorator struct {
smartaccountKeeper SmartAccountKeeper
wasmKeeper WasmKeeper
}

func NewPreTransactionHookDecorator(sak SmartAccountKeeper, wk WasmKeeper) PreTransactionHookDecorator {
return PreTransactionHookDecorator{
smartaccountKeeper: sak,
wasmKeeper: wk,
}
}

// AnteHandle checks if the tx provides sufficient fee to cover the required fee from the fee market.
func (pth PreTransactionHookDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
setting, ok := ctx.Value(smartaccounttypes.ModuleName).(smartaccounttypes.Setting)
if !ok {
return next(ctx, tx, simulate)
}

if setting.PreTransaction != nil && len(setting.PreTransaction) > 0 {
for _, preTx := range setting.PreTransaction {
contractAddr, err := sdk.AccAddressFromBech32(preTx)
if err != nil {
return ctx, err
}
data, err := BuildPreTransactionHookMsg(tx)
if err != nil {
return ctx, err
}
_, err = pth.wasmKeeper.Sudo(ctx, contractAddr, data)
if err != nil {
return ctx, err
}
}
}

return next(ctx, tx, simulate)
}

// TODO: to refactor
func BuildPreTransactionHookMsg(tx sdk.Tx) ([]byte, error) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return nil, sdkerrors.Wrap(sdkerrortypes.ErrInvalidType, "expected SigVerifiableTx")
}

// Signer here is the account that the state transition is affecting
// e.g. Account that is transferring some Coins
signers := sigTx.GetSigners()
// Current only supports one signer (TODO review in the future)
if len(signers) != 1 {
return nil, sdkerrors.Wrap(sdkerrortypes.ErrorInvalidSigner, "only one signer is supported")
}

// Sender here is the account that signed the transaction
// Could be different from the account above (confusingly named signer)
signatures, _ := sigTx.GetSignaturesV2()
if len(signatures) == 0 {
return nil, sdkerrors.Wrap(sdkerrortypes.ErrNoSignatures, "no signatures found")
}
senderAddr, err := sdk.AccAddressFromHexUnsafe(signatures[0].PubKey.Address().String())
if err != nil {
return nil, err
}

msgs := sigTx.GetMsgs()
anyMsgs, err := tx2.SetMsgs(msgs)
if err != nil {
return nil, err
}
var stargateMsgs []wasmvmtypes.CosmosMsg
for _, msg := range anyMsgs {
stargateMsg := wasmvmtypes.StargateMsg{
TypeURL: msg.TypeUrl,
Value: msg.Value,
}
stargateMsgs = append(stargateMsgs, wasmvmtypes.CosmosMsg{
Stargate: &stargateMsg,
})
}
preTx := smartaccounttypes.PreTransaction{
Sender: senderAddr.String(),
Account: signers[0].String(),
Messages: stargateMsgs,
}
msg := smartaccounttypes.SudoMsg{PreTransaction: &preTx}
return json.Marshal(msg)
}
1 change: 1 addition & 0 deletions x/smartaccount/ante/smartaccount_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func (sad SmartAccountAuthDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
return ctx, err
}

ctx = ctx.WithValue(types.ModuleName, setting)
if setting.Authorization != nil && len(setting.Authorization) > 0 {
for _, auth := range setting.Authorization {
// TODO: add code that calls authorization on contracts
Expand Down
42 changes: 0 additions & 42 deletions x/smartaccount/ante/smartaccount_pretx.go

This file was deleted.

55 changes: 55 additions & 0 deletions x/smartaccount/ante/tests/ante_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package tests

import (
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/terra-money/core/v2/x/smartaccount/ante"
"github.com/terra-money/core/v2/x/smartaccount/test_helpers"
"testing"
)

type AnteTestSuite struct {
test_helpers.SmartAccountTestSuite

Decorator ante.PreTransactionHookDecorator
WasmKeeper *wasmkeeper.PermissionedKeeper
}

func TestAnteSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}

func (s *AnteTestSuite) Setup() {
s.SmartAccountTestSuite.Setup()
s.WasmKeeper = wasmkeeper.NewDefaultPermissionKeeper(s.App.Keepers.WasmKeeper)
s.Decorator = ante.NewPreTransactionHookDecorator(s.SmartAccountKeeper, s.WasmKeeper)
}

func (s *AnteTestSuite) BuildDefaultMsgTx(accountIndex int, msgs ...sdk.Msg) client.TxBuilder {
pk := s.TestAccPrivs[accountIndex]
txBuilder := s.EncodingConfig.TxConfig.NewTxBuilder()
err := txBuilder.SetMsgs(
msgs...,
)
require.NoError(s.T(), err)
signer := authsigning.SignerData{
ChainID: "test",
AccountNumber: 0,
Sequence: 0,
}
sig, err := tx.SignWithPrivKey(
s.EncodingConfig.TxConfig.SignModeHandler().DefaultMode(),
signer,
txBuilder,
pk,
s.EncodingConfig.TxConfig,
0,
)
txBuilder.SetSignatures(sig)
return txBuilder
}
89 changes: 89 additions & 0 deletions x/smartaccount/ante/tests/pretransaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package tests

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"
"github.com/terra-money/core/v2/x/smartaccount/test_helpers"
smartaccounttypes "github.com/terra-money/core/v2/x/smartaccount/types"
)

func (s *AnteTestSuite) TestPreTransactionHookWithoutSmartAccount() {
s.Setup()
txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
FromAddress: s.TestAccs[0].String(),
ToAddress: s.TestAccs[1].String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)),
})
_, err := s.Decorator.AnteHandle(s.Ctx, txBuilder.GetTx(), false, sdk.ChainAnteDecorators(sdk.Terminator{}))
require.NoError(s.T(), err)
}

func (s *AnteTestSuite) TestPreTransactionHookWithEmptySmartAccount() {
s.Setup()
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{})
txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
FromAddress: s.TestAccs[0].String(),
ToAddress: s.TestAccs[1].String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)),
})
_, err := s.Decorator.AnteHandle(s.Ctx, txBuilder.GetTx(), false, sdk.ChainAnteDecorators(sdk.Terminator{}))
require.NoError(s.T(), err)
}

func (s *AnteTestSuite) TestInvalidContractAddress() {
s.Setup()
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
PreTransaction: []string{s.TestAccs[0].String()},
})
txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
FromAddress: s.TestAccs[0].String(),
ToAddress: s.TestAccs[1].String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)),
})
_, err := s.Decorator.AnteHandle(s.Ctx, txBuilder.GetTx(), false, sdk.ChainAnteDecorators(sdk.Terminator{}))
require.ErrorContainsf(s.T(), err, "no such contract", "error message: %s", err)
}

func (s *AnteTestSuite) TestSendCoinsWithLimitSendHook() {
s.Setup()

acc := s.TestAccs[0]
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitSendOnlyHookWasm, nil)
require.NoError(s.T(), err)
contractAddr, _, err := s.WasmKeeper.Instantiate(s.Ctx, codeId, acc, acc, []byte("{}"), "limit send", sdk.NewCoins())
require.NoError(s.T(), err)

s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
PreTransaction: []string{contractAddr.String()},
})
txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
FromAddress: acc.String(),
ToAddress: acc.String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)),
})
_, err = s.Decorator.AnteHandle(s.Ctx, txBuilder.GetTx(), false, sdk.ChainAnteDecorators(sdk.Terminator{}))
require.NoError(s.T(), err)
}

func (s *AnteTestSuite) TestStakingWithLimitSendHook() {
s.Setup()

acc := s.TestAccs[0]
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitSendOnlyHookWasm, nil)
require.NoError(s.T(), err)
contractAddr, _, err := s.WasmKeeper.Instantiate(s.Ctx, codeId, acc, acc, []byte("{}"), "limit send", sdk.NewCoins())
require.NoError(s.T(), err)

s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
PreTransaction: []string{contractAddr.String()},
})
txBuilder := s.BuildDefaultMsgTx(0, &stakingtypes.MsgDelegate{
DelegatorAddress: acc.String(),
ValidatorAddress: acc.String(),
Amount: sdk.NewInt64Coin("uluna", 100000000),
})
_, err = s.Decorator.AnteHandle(s.Ctx, txBuilder.GetTx(), false, sdk.ChainAnteDecorators(sdk.Terminator{}))
require.ErrorContainsf(s.T(), err, "Unauthorized message type", "error message: %s", err)
}
Loading

0 comments on commit 965500f

Please sign in to comment.