Skip to content

Commit

Permalink
Implement gas-free WasmExecution for accounts with sufficient balance
Browse files Browse the repository at this point in the history
  • Loading branch information
teddyding committed Aug 23, 2024
1 parent 4b1c1dc commit 2a6c50f
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 0 deletions.
5 changes: 5 additions & 0 deletions x/auth/ante/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee
return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom)
}

// Skip deducting fees it's a qualified Wasm Execution transaction.
if IsSingleWasmExecTx(sdkTx) && WasmExecExemptFromGas(dfd.bankKeeper, ctx, deductFeesFromAcc) {
return nil
}

// deduct the fees
if !fee.IsZero() {
err := DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fee)
Expand Down
41 changes: 41 additions & 0 deletions x/auth/ante/wasm_execution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ante

import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

const (
// The IBC denom of `uusdc` (atomic unit of Noble USDC)
UusdcDenom = "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5"
UusdcBalanceForGasExemption = 2_000_000_000 // 2_000 Noble USDC
)

// Returns true if the transaction includes a single MsgExecuteContract message.
// False otherwise.
func IsSingleWasmExecTx(
sdkTx sdk.Tx,
) bool {
msgs := sdkTx.GetMsgs()
if len(msgs) > 1 {
return false
}

for _, msg := range msgs {
if sdk.MsgTypeURL(msg) == "/cosmwasm.wasm.v1.MsgExecuteContract" {
return true
}
}
return false
}

// Returns true if USDC balance is sufficient for gas exemption.
func WasmExecExemptFromGas(
bankKeeper types.BankKeeper,
ctx sdk.Context,
deductFeeFromAcc sdk.AccountI,
) bool {
balance := bankKeeper.GetBalance(ctx, deductFeeFromAcc.GetAddress(), UusdcDenom)
return balance.Amount.GTE(math.NewInt(UusdcBalanceForGasExemption))
}
82 changes: 82 additions & 0 deletions x/auth/ante/wasm_execution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ante_test

import (
"testing"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/golang/mock/gomock"

"github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/stretchr/testify/require"
)

// MockMsgExecuteContract is a mock message for testing purposes.
type MockMsgExecuteContract struct{}

func (m MockMsgExecuteContract) Reset() {}
func (m MockMsgExecuteContract) String() string { return "MockMsgExecuteContract" }
func (m MockMsgExecuteContract) ProtoMessage() {}

func (m MockMsgExecuteContract) XXX_MessageName() string {

Check failure on line 23 in x/auth/ante/wasm_execution_test.go

View workflow job for this annotation

GitHub Actions / Analyze

var-naming: don't use underscores in Go names; method XXX_MessageName should be XXXMessageName (revive)
return "cosmwasm.wasm.v1.MsgExecuteContract"
}

func TestIsSingleWasmExecTx(t *testing.T) {
// Create a mock message with the desired TypeURL
msg := &MockMsgExecuteContract{}

suite := SetupTestSuite(t, true)
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()

err := suite.txBuilder.SetMsgs(msg)
require.NoError(t, err)

// Create the transaction
sdkTx := suite.txBuilder.GetTx()

// Test the function
result := ante.IsSingleWasmExecTx(sdkTx)
require.True(t, result, "Expected IsSingleWasmExecTx to return true for a single MsgExecuteContract message")

// Test with multiple messages
err = suite.txBuilder.SetMsgs(msg, msg)
require.NoError(t, err)
sdkTx = suite.txBuilder.GetTx()
result = ante.IsSingleWasmExecTx(sdkTx)
require.False(t, result, "Expected IsSingleWasmExecTx to return false for multiple messages")
}

func TestWasmExecExemptFromGas(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

// Setup the test suite
suite := SetupTestSuite(t, false)

// Create a mock account
acc := &types.BaseAccount{}
acc.SetAddress(sdk.AccAddress([]byte("test_address")))

// Set up the mock bank keeper to return a specific balance
suite.bankKeeper.EXPECT().
GetBalance(gomock.Any(), acc.GetAddress(), ante.UusdcDenom).
Return(sdk.Coin{Denom: ante.UusdcDenom, Amount: sdkmath.NewInt(ante.UusdcBalanceForGasExemption)}).
Times(1)

// Test the function with sufficient balance
result := ante.WasmExecExemptFromGas(suite.bankKeeper, suite.ctx, acc)
require.True(t, result, "Expected WasmExecExemptFromGas to return true for sufficient balance")

// Set up the mock bank keeper to return an insufficient balance
suite.bankKeeper.EXPECT().
GetBalance(gomock.Any(), acc.GetAddress(), ante.UusdcDenom).
Return(sdk.Coin{Denom: ante.UusdcDenom, Amount: sdkmath.NewInt(ante.UusdcBalanceForGasExemption - 1)}).
Times(1)

// Test the function with insufficient balance
result = ante.WasmExecExemptFromGas(suite.bankKeeper, suite.ctx, acc)
require.False(t, result, "Expected WasmExecExemptFromGas to return false for insufficient balance")
}
14 changes: 14 additions & 0 deletions x/auth/testutil/expected_keepers_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x/auth/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ type BankKeeper interface {
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
SendCoins(ctx context.Context, from, to sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin
}

0 comments on commit 2a6c50f

Please sign in to comment.