Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): Make post price txs gasless #817

Merged
merged 36 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6f63ea2
feat: Gasless txs for post price and liquidate
Jul 24, 2022
caa2add
fix: Fix anteHandler call to include keepers
Jul 25, 2022
e452d55
fix: Update changelog and lint fix
Jul 25, 2022
b2a32ae
Merge remote-tracking branch 'origin/master' into mat/make-oracle-liq…
Jul 27, 2022
21f1eed
fix: merge with master
Jul 27, 2022
471efa5
fix,docs: vpool gov cli test
Unique-Divine Jul 27, 2022
c7717db
docs(vpool,pricefeed): readadbility and cli commenets for the gov cli…
Unique-Divine Jul 27, 2022
b69a432
fix: check for error on chainedhandler
Jul 27, 2022
e816bad
wip: Update the fees to 0 in cli test for pricefeed
Jul 27, 2022
1351de1
Merge branch 'master' into mat/make-oracle-liquidate-gasless
AgentSmithMatrix Jul 28, 2022
6c6a5f6
feat: Fork fee deduct ante-handler
matthiasmatt Aug 10, 2022
9246d6b
Merge remote-tracking branch 'origin/master' into mat/make-oracle-liq…
matthiasmatt Aug 10, 2022
03f1bb8
fix: Remove unused function
matthiasmatt Aug 10, 2022
3a36e26
tests: Add test for gas or no gas in post price cli test
matthiasmatt Aug 10, 2022
9901125
feat: Update changelog
matthiasmatt Aug 10, 2022
a20fb30
fix: Remove perpkeeper from gasless ante
matthiasmatt Aug 10, 2022
903c44f
fix: Remove println from codebase
matthiasmatt Aug 10, 2022
4c9a9e4
test gasmeter infinite
AgentSmithMatrix Aug 10, 2022
1ab48a0
change test for gassless decorator
AgentSmithMatrix Aug 10, 2022
b56cd3a
update gasless test
AgentSmithMatrix Aug 10, 2022
06ab94a
remove unused code
AgentSmithMatrix Aug 10, 2022
3ef21a2
fix comment
AgentSmithMatrix Aug 10, 2022
681ab28
remove unused code
AgentSmithMatrix Aug 10, 2022
5614d37
linter
AgentSmithMatrix Aug 10, 2022
af767cd
linter 2
AgentSmithMatrix Aug 10, 2022
48511f1
final clean
AgentSmithMatrix Aug 10, 2022
1a5d14d
Merge remote-tracking branch 'origin/master' into mat/make-oracle-liq…
AgentSmithMatrix Aug 11, 2022
386d344
remove unused func
AgentSmithMatrix Aug 11, 2022
12594ab
remove comment
AgentSmithMatrix Aug 11, 2022
9eac5e1
Merge branch 'master' into mat/make-oracle-liquidate-gasless
AgentSmithMatrix Aug 11, 2022
598e5f3
Merge branch 'master' into mat/make-oracle-liquidate-gasless
matthiasmatt Aug 12, 2022
d9eade7
fix: Fix logic on pricefeed query tests for cli_test
matthiasmatt Aug 12, 2022
0da7c22
test: Add test for simulation run
matthiasmatt Aug 12, 2022
edfebca
fix: Remove nil check for address of variable
matthiasmatt Aug 12, 2022
74e36d2
Merge branch 'master' into mat/make-oracle-liquidate-gasless
AgentSmithMatrix Aug 15, 2022
40fba6c
Merge branch 'master' into mat/make-oracle-liquidate-gasless
matthiasmatt Aug 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#801](https://github.com/NibiruChain/nibiru/pull/801) - remove unused pair constants
* [#788](https://github.com/NibiruChain/nibiru/pull/788) - add --overwrite flag to the nibid init call of localnet.sh
* [#804](https://github.com/NibiruChain/nibiru/pull/804) - bump ibc-go to v3.1.1
* [#817](https://github.com/NibiruChain/nibiru/pull/817) - Make post prices transactions gasless for whitelisted oracles
* [#818](https://github.com/NibiruChain/nibiru/pull/818) - fix(localnet.sh): add max leverage to vpools in genesis to fix open-position
* [#819](https://github.com/NibiruChain/nibiru/pull/819) - add golangci-linter using docker in Makefile

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ PACKAGES_NOSIMULATION = $(shell go list ./... | grep -v '/simapp')
RUNSIM = $(BINDIR)/runsim

test-unit:
go test $(PACKAGES_NOSIMULATION) -short -cover
@go test $(PACKAGES_NOSIMULATION) -short -cover

test-integration:
go test -v $(PACKAGES_NOSIMULATION) -cover
@go test -v $(PACKAGES_NOSIMULATION) -cover

runsim: $(RUNSIM)
$(RUNSIM):
Expand Down
10 changes: 9 additions & 1 deletion app/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/ante"
ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante"
ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper"

gaslessante "github.com/NibiruChain/nibiru/app/antedecorators/gasless"

feeante "github.com/NibiruChain/nibiru/app/antedecorators/fee"
pricefeedkeeper "github.com/NibiruChain/nibiru/x/pricefeed/keeper"
)

type AnteHandlerOptions struct {
ante.HandlerOptions
IBCKeeper *ibckeeper.Keeper

PricefeedKeeper pricefeedkeeper.Keeper
}

/*
Expand Down Expand Up @@ -43,7 +50,8 @@ func NewAnteHandler(options AnteHandlerOptions) (sdk.AnteHandler, error) {
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper),
gaslessante.NewGaslessDecorator(options.PricefeedKeeper),
feeante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper), // Replace fee ante from cosmos auth with a custom one.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating our own custom DeductFeeDecorator, what if we over-write the t.AuthInfo.Fee.Amount inside of GaslessDecorator so that the default DeductFeeDecorator takes zero coins away from the oracle?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of that behavior in the fee.go ante:

	// Ensure that the provided fees meet a minimum threshold for the validator,
	// if this is a CheckTx. This is only for local mempool purposes, and thus
	// is only ran on check tx.
	if ctx.IsCheckTx() && !simulate {
		minGasPrices := ctx.MinGasPrices()
		if !minGasPrices.IsZero() {
			requiredFees := make(sdk.Coins, len(minGasPrices))

			// Determine the required fees by multiplying each required minimum gas
			// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
			glDec := sdk.NewDec(int64(gas))
			for i, gp := range minGasPrices {
				fee := gp.Amount.Mul(glDec)
				requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
			}

			if !feeCoins.IsAnyGTE(requiredFees) {
				return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
			}
		}
	}

if you provide 0 coins for required fees, it will fail with: insufficient fees; got: 0 required: 0

// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
Expand Down
84 changes: 84 additions & 0 deletions app/antedecorators/fee/fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package fee

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

// DeductFeeDecorator deducts fees from the first signer of the tx
// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error
// Call next AnteHandler if fees successfully deducted
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
type DeductFeeDecorator struct {
ak ante.AccountKeeper
bankKeeper types.BankKeeper
feegrantKeeper ante.FeegrantKeeper
}

func NewDeductFeeDecorator(ak ante.AccountKeeper, bk types.BankKeeper, fk ante.FeegrantKeeper) DeductFeeDecorator {
return DeductFeeDecorator{
ak: ak,
bankKeeper: bk,
feegrantKeeper: fk,
}
}

func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
}

if addr := dfd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil {
return ctx, fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
}

fee := feeTx.GetFee()
feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()

deductFeesFrom := feePayer

// if feegranter set deduct fee from feegranter account.
// this works with only when feegrant enabled.
if feeGranter != nil {
if dfd.feegrantKeeper == nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled")
} else if !feeGranter.Equals(feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs())

if err != nil {
return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer)
}
}

deductFeesFrom = feeGranter
}

deductFeesFromAcc := dfd.ak.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom)
}

// Gas meter is set to 1 for gasless transactions.
// Depends on GasLessDecorator for this to happen.
if ctx.GasMeter().GasConsumed() == 1 {
// do nothing
} else if !feeTx.GetFee().IsZero() {
err = ante.DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee())
if err != nil {
return ctx, err
}
}

events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()),
)}
ctx.EventManager().EmitEvents(events)

return next(ctx, tx, simulate)
}
54 changes: 54 additions & 0 deletions app/antedecorators/fee/fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package fee_test

import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/app/antedecorators/fee"
)

func (suite *AnteTestSuite) TestDeductFees() {
suite.SetupTest(false) // setup
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()

// keys and addresses
priv1, _, addr1 := testdata.KeyTestPubAddr()

// msg and signatures
msg := testdata.NewTestMsg(addr1)
feeAmount := sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 150))
gasLimit := testdata.NewTestGasLimit()
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
suite.txBuilder.SetFeeAmount(feeAmount)
suite.txBuilder.SetGasLimit(gasLimit)

privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
suite.Require().NoError(err)

// Set account with insufficient funds
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
coins := sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(10)))
err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, coins)
suite.Require().NoError(err)

dfd := fee.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, nil)
antehandler := sdk.ChainAnteDecorators(dfd)

_, err = antehandler(suite.ctx, tx, false)

suite.Require().NotNil(err, "Tx did not error when fee payer had insufficient funds")

// Set account with sufficient funds
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(200))))
suite.Require().NoError(err)

_, err = antehandler(suite.ctx, tx, false)

suite.Require().Nil(err, "Tx errored after account has been set with sufficient funds")
}
119 changes: 119 additions & 0 deletions app/antedecorators/fee/testutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package fee_test

import (
"testing"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/x/testutil/testapp"
)

// AnteTestSuite is a test suite to be used with ante handler tests.
type AnteTestSuite struct {
suite.Suite

app *app.NibiruApp
anteHandler sdk.AnteHandler
ctx sdk.Context
clientCtx client.Context
txBuilder client.TxBuilder
}

// returns context and app with params set on account keeper
func createTestApp(isCheckTx bool) (*app.NibiruApp, sdk.Context) {
app := testapp.NewNibiruApp(true)
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{})
app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams())

return app, ctx
}

// SetupTest setups a new test, with new app, context, and anteHandler.
func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
suite.app, suite.ctx = createTestApp(isCheckTx)
suite.ctx = suite.ctx.WithBlockHeight(1)

// Set up TxConfig.
encodingConfig := app.MakeTestEncodingConfig()
// We're using TestMsg encoding in some tests, so register it here.
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry)

suite.clientCtx = client.Context{}.
WithTxConfig(encodingConfig.TxConfig)

anteHandler, err := ante.NewAnteHandler(
ante.HandlerOptions{
AccountKeeper: suite.app.AccountKeeper,
BankKeeper: suite.app.BankKeeper,
FeegrantKeeper: suite.app.FeeGrantKeeper,
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
},
)

suite.Require().NoError(err)
suite.anteHandler = anteHandler
}

// CreateTestTx is a helper function to create a tx given multiple inputs.
func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) {
// First round: we gather all the signer infos. We use the "set empty
// signature" hack to do that.
var sigsV2 []signing.SignatureV2
for i, priv := range privs {
sigV2 := signing.SignatureV2{
PubKey: priv.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(),
Signature: nil,
},
Sequence: accSeqs[i],
}

sigsV2 = append(sigsV2, sigV2)
}
err := suite.txBuilder.SetSignatures(sigsV2...)
if err != nil {
return nil, err
}

// Second round: all signer infos are set, so each signer can sign.
sigsV2 = []signing.SignatureV2{}
for i, priv := range privs {
signerData := xauthsigning.SignerData{
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
}
sigV2, err := tx.SignWithPrivKey(
suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData,
suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i])
if err != nil {
return nil, err
}

sigsV2 = append(sigsV2, sigV2)
}
err = suite.txBuilder.SetSignatures(sigsV2...)
if err != nil {
return nil, err
}

return suite.txBuilder.GetTx(), nil
}

func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}
57 changes: 57 additions & 0 deletions app/antedecorators/gasless/gasless.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package gasless

import (
sdk "github.com/cosmos/cosmos-sdk/types"

types "github.com/NibiruChain/nibiru/app/antedecorators/types"
"github.com/NibiruChain/nibiru/x/common"
pricefeedkeeper "github.com/NibiruChain/nibiru/x/pricefeed/keeper"
pricefeedtypes "github.com/NibiruChain/nibiru/x/pricefeed/types"
)

type GaslessDecorator struct {
pricefeedKeeper pricefeedkeeper.Keeper
}

func NewGaslessDecorator(pricefeedKeeper pricefeedkeeper.Keeper) GaslessDecorator {
return GaslessDecorator{pricefeedKeeper: pricefeedKeeper}
}

func (gd GaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if simulate || !isTxGasless(tx, ctx, gd.pricefeedKeeper) {
return next(ctx, tx, simulate)
}

gaslessMeter := types.GasLessMeter()
return next(ctx.WithGasMeter(gaslessMeter), tx, simulate)
}

func isTxGasless(tx sdk.Tx, ctx sdk.Context, pricefeedKeeper pricefeedkeeper.Keeper) bool {
if len(tx.GetMsgs()) == 0 {
// empty TX shouldn't be gasless
return false
}
for _, msg := range tx.GetMsgs() {
switch m := msg.(type) {
case *pricefeedtypes.MsgPostPrice:
if pricefeedPostPriceIsGasless(m, ctx, pricefeedKeeper) {
continue
}
return false
default:
return false
}
}
return true
}

// Check if the sender is a whitelisted oracle
func pricefeedPostPriceIsGasless(msg *pricefeedtypes.MsgPostPrice, ctx sdk.Context, keeper pricefeedkeeper.Keeper) bool {
valAddr, err := sdk.AccAddressFromBech32(msg.Oracle)
if err != nil {
return false
}

pair := common.AssetPair{Token0: msg.Token0, Token1: msg.Token1}
return keeper.IsWhitelistedOracle(ctx, pair.String(), valAddr)
}
Loading