Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ RUN make build
FROM base AS integration
RUN make lint
# Unit tests
RUN make test-poa
RUN make test-unit
# Integration tests
RUN make test-integration
# Simulation tests
Expand Down
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ lint-fix:
### Testing ###
###############################################################################
EXCLUDED_POA_PACKAGES=$(shell go list ./x/poa/... | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types)
EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v testutil | grep -v tools | grep -v app | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types)
EXCLUDED_UNIT_PACKAGES=$(shell go list ./... | grep -v tests | grep -v legacy | grep -v testutil | grep -v tools | grep -v docs | grep -v cmd | grep -v /x/poa/testutil | grep -v /x/poa/client | grep -v /x/poa/simulation | grep -v /x/poa/types)

mocks:
@echo "--> Installing mockgen"
Expand All @@ -146,9 +146,9 @@ test-integration:
@echo "--> Running integration testsuite"
@go test -mod=readonly -tags=test -v ./tests/integration

test-poa:
@echo "--> Running POA tests"
@go test $(EXCLUDED_POA_PACKAGES)
test-unit:
@echo "--> Running unit tests"
@go test $(EXCLUDED_UNIT_PACKAGES)

test-sim-benchmark-simulation:
@echo "Running simulation invariant benchmarks..."
Expand Down
100 changes: 100 additions & 0 deletions app/ante/dynamic_discount_fee_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ante

import (
"fmt"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/evm/ante/evm"
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
evmtypes "github.com/cosmos/evm/x/vm/types"
)

const (
MinDiscountValue int64 = 0
MaxDiscountValue int64 = 100
)

type Discount int64

func (d Discount) Int64() int64 {
return int64(d)
}

func (d Discount) Int() sdkmath.Int {
return sdkmath.NewInt(int64(d))
}

func (d Discount) IsZero() bool {
return d.Int64() == 0
}

func (d Discount) IsValid() bool {
intD := d.Int64()
return intD >= MinDiscountValue && intD <= MaxDiscountValue
}

func NewDynamicDiscountTxFeeChecker(keeper anteinterfaces.FeeMarketKeeper, discount Discount) ante.TxFeeChecker {
return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
feeTx, ok := tx.(sdk.FeeTx)

denom := evmtypes.GetEVMCoinDenom()
ethCfg := evmtypes.GetEthChainConfig()

if !ok {
return nil, 0, fmt.Errorf(" Tx must be a FeeTx")
}

fee, priority, err := evm.FeeChecker(ctx, keeper, denom, ethCfg, feeTx)
if err != nil {
return nil, 0, err
}

if !IsDiscountApplicable(tx) {
return fee, priority, nil
}

return ApplyFeeDiscount(fee, priority, discount, denom)
}
}

func IsDiscountApplicable(tx sdk.Tx) bool {
for _, msg := range tx.GetMsgs() {
if _, ok := msg.(*banktypes.MsgSend); ok {
return true
}
}
return false
}

func ApplyFeeDiscount(fee sdk.Coins, priority int64, discount Discount, denom string) (sdk.Coins, int64, error) {
found, feeCoin := fee.Find(denom)
if !found {
return fee, priority, fmt.Errorf("fee not found for denom: %s", denom)
}

if !discount.IsValid() || discount.IsZero() {
return fee, priority, fmt.Errorf("invalid discount: %d", discount)
}

if fee.IsZero() {
return fee, priority, nil
}

discountAmt := feeCoin.Amount.Mul(discount.Int()).Quo(sdkmath.NewInt(100))
discountedFeeAmt := feeCoin.Amount.Sub(discountAmt)

priorityInt := sdkmath.NewInt(priority)
discountedPriority := priorityInt.Add(priorityInt.Mul(discount.Int()).Quo(sdkmath.NewInt(100)))

discountedFee := sdk.Coins{
{
Denom: denom,
Amount: discountedFeeAmt,
},
}

return discountedFee, discountedPriority.Int64(), nil
}
182 changes: 182 additions & 0 deletions app/ante/dynamic_discount_fee_checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package ante

import (
"strings"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
poatypes "github.com/xrplevm/node/v9/x/poa/types"
"google.golang.org/protobuf/proto"
)

type mockTx struct {
msgs []sdk.Msg
}

// Implement sdk.Tx interface
func (m mockTx) GetMsgs() []sdk.Msg { return m.msgs }
func (m mockTx) GetMsgsV2() ([]proto.Message, error) { return nil, nil }
func (m mockTx) ValidateBasic() error { return nil }

func TestIsDiscountApplicable(t *testing.T) {
// Helper type to mock sdk.Tx

tt := []struct {
name string
tx sdk.Tx
expectedResult bool
}{
{
name: "should return true if a discountable message is found",
tx: mockTx{msgs: []sdk.Msg{&banktypes.MsgSend{}}},
expectedResult: true,
},
{
name: "should return false if no messages",
tx: mockTx{msgs: []sdk.Msg{}},
expectedResult: false,
},
{
name: "should return false if no discountable message is found",
tx: mockTx{msgs: []sdk.Msg{&poatypes.MsgAddValidator{}}},
expectedResult: false,
},
{
name: "should return true if at least one discountable message is found among others",
tx: mockTx{msgs: []sdk.Msg{&poatypes.MsgAddValidator{}, &banktypes.MsgSend{}}},
expectedResult: true,
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
result := IsDiscountApplicable(tc.tx)
if result != tc.expectedResult {
t.Errorf("expected %v, got %v", tc.expectedResult, result)
}
})
}
}

func TestApplyFeeDiscount(t *testing.T) {
denom := "token"

discount := Discount(10)
zeroFeeCoins := sdk.Coins{sdk.NewInt64Coin(denom, 0)}
feeCoins := sdk.Coins{sdk.NewInt64Coin(denom, 100)}

tt := []struct {
name string
fee sdk.Coins
priority int64
discount Discount
denom string
expectedFee sdk.Coins
expectedPriority int64
expectedErr bool
errContains string
}{
{
name: "should return an error if no fee denom is found",
fee: feeCoins,
denom: "unknown",
expectedErr: true,
errContains: "fee not found for denom",
},
{
name: "should return an error if the discount is not valid (lower boundaries)",
fee: feeCoins,
denom: denom,
discount: Discount(-1),
expectedErr: true,
errContains: "invalid discount",
},
{
name: "should return an error if the discount is not valid (upper boundaries)",
fee: feeCoins,
denom: denom,
discount: Discount(101),
expectedErr: true,
errContains: "invalid discount",
},
{
name: "should return an error if the discount is zero",
fee: zeroFeeCoins,
denom: denom,
discount: Discount(0),
expectedErr: true,
errContains: "invalid discount",
},
{
name: "should return the same fee and priority if fee is zero",
fee: zeroFeeCoins,
priority: 10,
denom: denom,
discount: discount,
expectedFee: sdk.Coins{sdk.NewInt64Coin("token", 0)},
expectedPriority: 10,
},
{
name: "should return the discounted fee and priority (fee=5, priority=5, discount=10)",
fee: sdk.Coins{sdk.NewInt64Coin(denom, 5)},
priority: 5,
denom: denom,
discount: discount,
expectedFee: sdk.Coins{sdk.NewInt64Coin(denom, 5)},
expectedPriority: 5,
},
{
name: "should return the discounted fee and priority (fee=10, priority=10, discount=10)",
fee: sdk.Coins{sdk.NewInt64Coin("token", 10)},
priority: 10,
denom: denom,
discount: discount,
expectedFee: sdk.Coins{sdk.NewInt64Coin("token", 9)},
expectedPriority: 11,
},
{
name: "should return the discounted fee and priority (fee=100, priority=100, discount=10)",
fee: feeCoins,
priority: 100,
denom: denom,
discount: discount,
expectedFee: sdk.Coins{sdk.NewInt64Coin("token", 90)},
expectedPriority: 110,
},
{
name: "should return the discounted fee and priority (fee=100, priority=0, discount=10)",
fee: feeCoins,
priority: 0,
denom: denom,
discount: discount,
expectedFee: sdk.Coins{sdk.NewInt64Coin("token", 90)},
expectedPriority: 0,
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
fee, priority, err := ApplyFeeDiscount(tc.fee, tc.priority, tc.discount, tc.denom)

if tc.expectedErr {
if err == nil {
t.Errorf("expected error, got nil")
}
if !strings.Contains(err.Error(), tc.errContains) {
t.Errorf("expected %s, got %s", tc.errContains, err.Error())
}
} else {
if !tc.expectedErr && err != nil {
t.Errorf("expected nil error, got %v", err)
}
if tc.expectedPriority != priority {
t.Errorf("expected %d, got %d", tc.expectedPriority, priority)
}
if !tc.expectedFee.Equal(fee) {
t.Errorf("expected %v, got %v", tc.expectedFee, fee)
}
}
})
}
}
3 changes: 1 addition & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal"
ethante "github.com/cosmos/evm/ante/evm"
"github.com/xrplevm/node/v9/app/ante"

evmante "github.com/cosmos/evm/ante"
Expand Down Expand Up @@ -888,7 +887,7 @@ func (app *App) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) {
SignModeHandler: txConfig.SignModeHandler(),
SigGasConsumer: ante.SigVerificationGasConsumer,
MaxTxGasWanted: maxGasWanted,
TxFeeChecker: ethante.NewDynamicFeeChecker(app.FeeMarketKeeper),
TxFeeChecker: ante.NewDynamicDiscountTxFeeChecker(app.FeeMarketKeeper, 50),
StakingKeeper: app.StakingKeeper,
DistributionKeeper: app.DistrKeeper,
ExtraDecorator: poaante.NewPoaDecorator(),
Expand Down
Loading