Skip to content

Commit

Permalink
Merge pull request #57 from Lorenzo-Protocol/sheldon/testutils
Browse files Browse the repository at this point in the history
feat(test): add test framework for lorenzo
  • Loading branch information
taramakage authored Jun 25, 2024
2 parents 01140e8 + 8feaf4b commit 78aeff3
Show file tree
Hide file tree
Showing 17 changed files with 2,629 additions and 0 deletions.
257 changes: 257 additions & 0 deletions testutil/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package testutil

import (
"time"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"

abci "github.com/cometbft/cometbft/abci/types"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
tmtypes "github.com/cometbft/cometbft/types"

"github.com/Lorenzo-Protocol/lorenzo/app"
"github.com/Lorenzo-Protocol/lorenzo/testutil/tx"
)

// Commit commits a block at a given time. Reminder: At the end of each
// Tendermint Consensus round the following methods are run
// 1. BeginBlock
// 2. DeliverTx
// 3. EndBlock
// 4. Commit
func Commit(ctx sdk.Context, app *app.LorenzoApp, t time.Duration, vs *tmtypes.ValidatorSet) (sdk.Context, error) {
header, err := commit(ctx, app, t, vs)
if err != nil {
return ctx, err
}

return ctx.WithBlockHeader(header), nil
}

// CommitAndCreateNewCtx commits a block at a given time creating a ctx with the current settings
// This is useful to keep test settings that could be affected by EndBlockers, e.g.
// setting a baseFee == 0 and expecting this condition to continue after commit
func CommitAndCreateNewCtx(ctx sdk.Context, app *app.LorenzoApp, t time.Duration, vs *tmtypes.ValidatorSet) (sdk.Context, error) {
header, err := commit(ctx, app, t, vs)
if err != nil {
return ctx, err
}

// NewContext function keeps the multistore
// but resets other context fields
// GasMeter is set as InfiniteGasMeter
newCtx := app.BaseApp.NewContext(false, header)
// set the reseted fields to keep the current ctx settings
newCtx = newCtx.WithMinGasPrices(ctx.MinGasPrices())
newCtx = newCtx.WithEventManager(ctx.EventManager())
newCtx = newCtx.WithKVGasConfig(ctx.KVGasConfig())
newCtx = newCtx.WithTransientKVGasConfig(ctx.TransientKVGasConfig())

return newCtx, nil
}

// DeliverTx delivers a cosmos tx for a given set of msgs
func DeliverTx(
ctx sdk.Context,
appLorenzo *app.LorenzoApp,
priv cryptotypes.PrivKey,
gasPrice *sdkmath.Int,
msgs ...sdk.Msg,
) (abci.ResponseDeliverTx, error) {
txConfig := app.MakeEncodingConfig().TxConfig
tx, err := tx.PrepareCosmosTx(
ctx,
appLorenzo,
tx.CosmosTxArgs{
TxCfg: txConfig,
Priv: priv,
ChainID: ctx.ChainID(),
Gas: 10_000_000,
GasPrice: gasPrice,
Msgs: msgs,
},
)
if err != nil {
return abci.ResponseDeliverTx{}, err
}
return BroadcastTxBytes(appLorenzo, txConfig.TxEncoder(), tx)
}

// DeliverEthTx generates and broadcasts a Cosmos Tx populated with MsgEthereumTx messages.
// If a private key is provided, it will attempt to sign all messages with the given private key,
// otherwise, it will assume the messages have already been signed.
func DeliverEthTx(
appLorenzo *app.LorenzoApp,
priv cryptotypes.PrivKey,
msgs ...sdk.Msg,
) (abci.ResponseDeliverTx, error) {
txConfig := app.MakeEncodingConfig().TxConfig

tx, err := tx.PrepareEthTx(txConfig, appLorenzo, priv, msgs...)
if err != nil {
return abci.ResponseDeliverTx{}, err
}
res, err := BroadcastTxBytes(appLorenzo, txConfig.TxEncoder(), tx)
if err != nil {
return abci.ResponseDeliverTx{}, err
}

codec := app.MakeEncodingConfig().Codec
if _, err := CheckEthTxResponse(res, codec); err != nil {
return abci.ResponseDeliverTx{}, err
}
return res, nil
}

// DeliverEthTxWithoutCheck generates and broadcasts a Cosmos Tx populated with MsgEthereumTx messages.
// If a private key is provided, it will attempt to sign all messages with the given private key,
// otherwise, it will assume the messages have already been signed. It does not check if the Eth tx is
// successful or not.
func DeliverEthTxWithoutCheck(
appLorenzo *app.LorenzoApp,
priv cryptotypes.PrivKey,
msgs ...sdk.Msg,
) (abci.ResponseDeliverTx, error) {
txConfig := app.MakeEncodingConfig().TxConfig

tx, err := tx.PrepareEthTx(txConfig, appLorenzo, priv, msgs...)
if err != nil {
return abci.ResponseDeliverTx{}, err
}

res, err := BroadcastTxBytes(appLorenzo, txConfig.TxEncoder(), tx)
if err != nil {
return abci.ResponseDeliverTx{}, err
}

return res, nil
}

// CheckTx checks a cosmos tx for a given set of msgs
func CheckTx(
ctx sdk.Context,
appLorenzo *app.LorenzoApp,
priv cryptotypes.PrivKey,
gasPrice *sdkmath.Int,
msgs ...sdk.Msg,
) (abci.ResponseCheckTx, error) {
txConfig := app.MakeEncodingConfig().TxConfig

tx, err := tx.PrepareCosmosTx(
ctx,
appLorenzo,
tx.CosmosTxArgs{
TxCfg: txConfig,
Priv: priv,
ChainID: ctx.ChainID(),
GasPrice: gasPrice,
Gas: 10_000_000,
Msgs: msgs,
},
)
if err != nil {
return abci.ResponseCheckTx{}, err
}
return checkTxBytes(appLorenzo, txConfig.TxEncoder(), tx)
}

// CheckEthTx checks a Ethereum tx for a given set of msgs
func CheckEthTx(
appLorenzo *app.LorenzoApp,
priv cryptotypes.PrivKey,
msgs ...sdk.Msg,
) (abci.ResponseCheckTx, error) {
txConfig := app.MakeEncodingConfig().TxConfig

tx, err := tx.PrepareEthTx(txConfig, appLorenzo, priv, msgs...)
if err != nil {
return abci.ResponseCheckTx{}, err
}
return checkTxBytes(appLorenzo, txConfig.TxEncoder(), tx)
}

// BroadcastTxBytes encodes a transaction and calls DeliverTx on the app.
func BroadcastTxBytes(app *app.LorenzoApp, txEncoder sdk.TxEncoder, tx sdk.Tx) (abci.ResponseDeliverTx, error) {
// bz are bytes to be broadcasted over the network
bz, err := txEncoder(tx)
if err != nil {
return abci.ResponseDeliverTx{}, err
}

req := abci.RequestDeliverTx{Tx: bz}
res := app.BaseApp.DeliverTx(req)
if res.Code != 0 {
return abci.ResponseDeliverTx{}, errorsmod.Wrapf(errortypes.ErrInvalidRequest, res.Log)
}

return res, nil
}

// commit is a private helper function that runs the EndBlocker logic, commits the changes,
// updates the header, runs the BeginBlocker function and returns the updated header
func commit(ctx sdk.Context, app *app.LorenzoApp, t time.Duration, vs *tmtypes.ValidatorSet) (tmproto.Header, error) {
header := ctx.BlockHeader()

if vs != nil {
res := app.EndBlock(abci.RequestEndBlock{Height: header.Height})

nextVals, err := applyValSetChanges(vs, res.ValidatorUpdates)
if err != nil {
return header, err
}
header.ValidatorsHash = vs.Hash()
header.NextValidatorsHash = nextVals.Hash()
} else {
app.EndBlocker(ctx, abci.RequestEndBlock{Height: header.Height})
}

_ = app.Commit()

header.Height++
header.Time = header.Time.Add(t)
header.AppHash = app.LastCommitID().Hash

app.BeginBlock(abci.RequestBeginBlock{
Header: header,
})

return header, nil
}

// checkTxBytes encodes a transaction and calls checkTx on the app.
func checkTxBytes(app *app.LorenzoApp, txEncoder sdk.TxEncoder, tx sdk.Tx) (abci.ResponseCheckTx, error) {
bz, err := txEncoder(tx)
if err != nil {
return abci.ResponseCheckTx{}, err
}

req := abci.RequestCheckTx{Tx: bz}
res := app.BaseApp.CheckTx(req)
if res.Code != 0 {
return abci.ResponseCheckTx{}, errorsmod.Wrapf(errortypes.ErrInvalidRequest, res.Log)
}

return res, nil
}

// applyValSetChanges takes in tmtypes.ValidatorSet and []abci.ValidatorUpdate and will return a new tmtypes.ValidatorSet which has the
// provided validator updates applied to the provided validator set.
func applyValSetChanges(valSet *tmtypes.ValidatorSet, valUpdates []abci.ValidatorUpdate) (*tmtypes.ValidatorSet, error) {
updates, err := tmtypes.PB2TM.ValidatorUpdates(valUpdates)
if err != nil {
return nil, err
}

// must copy since validator set will mutate with UpdateWithChangeSet
newVals := valSet.Copy()
err = newVals.UpdateWithChangeSet(updates)
if err != nil {
return nil, err
}

return newVals, nil
}
33 changes: 33 additions & 0 deletions testutil/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package testutil

import (
"github.com/Lorenzo-Protocol/lorenzo/app"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// NextFn is a no-op function that returns the context and no error in order to mock
// the next function in the AnteHandler chain.
//
// It can be used in unit tests when calling a decorator's AnteHandle method, e.g.
// `dec.AnteHandle(ctx, tx, false, NextFn)`
func NextFn(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
return ctx, nil
}

// ValidateAnteForMsgs is a helper function, which takes in an AnteDecorator as well as 1 or
// more messages, builds a transaction containing these messages, and returns any error that
// the AnteHandler might return.
func ValidateAnteForMsgs(ctx sdk.Context, dec sdk.AnteDecorator, msgs ...sdk.Msg) error {
encodingConfig := app.MakeEncodingConfig()
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
err := txBuilder.SetMsgs(msgs...)
if err != nil {
return err
}

tx := txBuilder.GetTx()

// Call Ante decorator
_, err = dec.AnteHandle(ctx, tx, false, NextFn)
return err
}
Loading

0 comments on commit 78aeff3

Please sign in to comment.