Skip to content

Commit

Permalink
feat: add simulation app, implement e2e tests (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
boojamya authored Oct 31, 2024
1 parent 6a67d80 commit 495e921
Show file tree
Hide file tree
Showing 24 changed files with 7,505 additions and 36 deletions.
90 changes: 90 additions & 0 deletions .github/workflows/e2e-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: End to End Tests

on:
pull_request:

env:
TAR_PATH: docker-image.tar
ARTIFACT_NAME: tar-docker-image

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Build Docker image
uses: strangelove-ventures/heighliner-build-action@v1.0.3
with:
registry: "" # empty registry, image only shared for e2e testing
tag: local # emulate local environment for consistency in interchaintest cases
tar-export-path: ${{ env.TAR_PATH }} # export a tarball that can be uploaded as an artifact for the e2e jobs
platform: linux/amd64 # test runner architecture only
git-ref: ${{ github.head_ref }} # source code ref

# Heighliner chains.yaml config
chain: noble-globalfee-simd
dockerfile: cosmos
build-target: make build
build-dir: simapp
binaries: |
- simapp/build/simd
- name: Publish Tarball as Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.TAR_PATH }}

prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Generate Matrix
id: set-matrix
run: |
# Run the command and convert its output to a JSON array
TESTS=$(cd e2e && go test -list . | grep -v "^ok " | jq -R -s -c 'split("\n")[:-1]')
echo "matrix=${TESTS}" >> $GITHUB_OUTPUT
test:
needs:
- build
- prepare
runs-on: ubuntu-latest
strategy:
matrix:
# names of `make` commands to run tests
test: ${{fromJson(needs.prepare.outputs.matrix)}}
fail-fast: false

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Download Tarball Artifact
uses: actions/download-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}

- name: Load Docker Image
run: docker image load -i ${{ env.TAR_PATH }}

- name: run test
run: cd e2e && go test -race -v -timeout 15m -run ^${{ matrix.test }}$ .
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea
build
coverage.out
debug_container.*
23 changes: 21 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
.PHONY: proto-format proto-lint proto-gen license format lint test-unit
all: proto-all format lint test-unit
.PHONY: proto-format proto-lint proto-gen license format lint test-unit build local-image test-e2e
all: proto-all format lint test-unit build local-image test-e2e

###############################################################################
### Build ###
###############################################################################

build:
@echo "🤖 Building simd..."
@cd simapp && make build 1> /dev/null
@echo "✅ Completed build!"

###############################################################################
### Tooling ###
Expand Down Expand Up @@ -53,7 +62,17 @@ proto-lint:
### Testing ###
###############################################################################

local-image:
@echo "🤖 Building image..."
@heighliner build --file ./e2e/chains.yaml --chain noble-globalfee-simd --local
@echo "✅ Completed build!"

test-unit:
@echo "🤖 Running unit tests..."
@go test -cover -coverprofile=coverage.out -race -v ./keeper/...
@echo "✅ Completed unit tests!"

test-e2e:
@echo "🤖 Running e2e tests..."
@cd e2e && go test -timeout 15m -race -v ./...
@echo "✅ Completed e2e tests!"
52 changes: 28 additions & 24 deletions ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,36 +32,40 @@ func TxFeeChecker(keeper *keeper.Keeper) ante.TxFeeChecker {
if !ok {
return nil, 0, errors.Wrap(errorstypes.ErrTxDecode, "Tx must be a FeeTx")
}
fees := feeTx.GetFee()

allBypassMessages := true
for _, msg := range feeTx.GetMsgs() {
if has, _ := keeper.BypassMessages.Has(ctx, sdk.MsgTypeURL(msg)); !has {
allBypassMessages = false
if ctx.IsCheckTx() {
allBypassMessages := true
for _, msg := range feeTx.GetMsgs() {
if has, _ := keeper.BypassMessages.Has(ctx, sdk.MsgTypeURL(msg)); !has {
allBypassMessages = false
break
}
}
if allBypassMessages {
return sdk.Coins{}, 0, nil
}
}
if allBypassMessages {
return sdk.Coins{}, 0, nil
}

requiredFees, err := keeper.GetRequiredFees(ctx, feeTx)
if err != nil {
return nil, 0, err
}
if len(requiredFees) == 0 {
return sdk.Coins{}, 0, nil
}
requiredFees, err := keeper.GetRequiredFees(ctx, feeTx)
if err != nil {
return nil, 0, err
}
if len(requiredFees) == 0 {
return sdk.Coins{}, 0, nil
}

fees := feeTx.GetFee()
sufficientFees := false
for _, fee := range fees {
found, requiredFee := requiredFees.Find(fee.Denom)
if found && fee.Amount.GTE(requiredFee.Amount) {
sufficientFees = true
sufficientFees := false
for _, fee := range fees {
found, requiredFee := requiredFees.Find(fee.Denom)
if found && fee.Amount.GTE(requiredFee.Amount) {
sufficientFees = true
break
}
}
}

if !sufficientFees {
return nil, 0, errors.Wrapf(errorstypes.ErrInsufficientFee, "expected at least one of %s", requiredFees)
if !sufficientFees {
return nil, 0, errors.Wrapf(errorstypes.ErrInsufficientFee, "expected at least one of %s", requiredFees)
}
}

return fees, getTxPriority(fees, int64(feeTx.GetGas())), nil
Expand Down
76 changes: 76 additions & 0 deletions client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 NASD Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cli

import (
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/noble-assets/globalfee/types"
"github.com/spf13/cobra"
)

func GetTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Transactions commands for the %s module", types.ModuleName),
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

cmd.AddCommand(TxUpdateGasPrices())

return cmd
}

func TxUpdateGasPrices() *cobra.Command {
cmd := &cobra.Command{
Use: "update-gas-prices [gas-prices ...]",
Short: "Update the minimum required gas prices for non-bypassed messages",
Example: "update-gas-prices 0.1uusdc 0.09ueure",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

var gasPrices sdk.DecCoins
for _, arg := range args {
gasPrice, err := sdk.ParseDecCoin(arg)
if err != nil {
return err
}

gasPrices = append(gasPrices, gasPrice)
}

msg := &types.MsgUpdateGasPrices{
Signer: clientCtx.GetFromAddress().String(),
GasPrices: gasPrices,
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
6 changes: 6 additions & 0 deletions e2e/chains.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: noble-globalfee-simd
dockerfile: cosmos
build-dir: simapp
build-target: make build
binaries:
- simapp/build/simd
79 changes: 79 additions & 0 deletions e2e/globalfee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2024 NASD Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
"encoding/json"
"fmt"
"testing"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/noble-assets/globalfee/types"
"github.com/strangelove-ventures/interchaintest/v8"
"github.com/stretchr/testify/require"
)

func TestGlobalFee(t *testing.T) {
ctx, wrapper := GlobalFeeSuite(t)
validator := wrapper.Chain.Validators[0]

var (
gasPrice = sdk.DecCoin{Denom: "ustake", Amount: math.LegacyNewDec(10)}
gas = math.NewInt(200_000) // default gas
fee = fmt.Sprintf("%sustake", gasPrice.Amount.MulInt(gas).String())
)

// ARRANGE: Generate a sender and recipient wallet.
sender := interchaintest.GetAndFundTestUsers(t, ctx, "sender", math.NewInt(100_000_000_000), wrapper.Chain)[0]
recipient, err := wrapper.Chain.BuildRelayerWallet(ctx, "recipient")
require.NoError(t, err)

// ACT: Attempt a transaction with no fees.
err = bankSendWithFees(ctx, validator, sender, recipient, "1ustake", "0ustake")
// ASSERT: The transaction was successful due to no required fees.
require.NoError(t, err)

// ACT: Set required gas prices to 10ustake.
_, err = validator.ExecTx(ctx, wrapper.Authority.KeyName(), "globalfee", "update-gas-prices", gasPrice.String())
// ASSERT: The transaction was successful and updated the required gas prices.
require.NoError(t, err)
gasPrices, err := GasPrices(ctx, validator)
require.NoError(t, err)
require.Equal(t, sdk.NewDecCoins(gasPrice), gasPrices)

// ACT: Attempt a transaction with no fees.
err = bankSendWithFees(ctx, validator, sender, recipient, "1ustake", "0ustake")
// ASSERT: The transaction failed due to insufficient fees.
require.ErrorContains(t, err, "insufficient fee")

// ACT: Set x/bank MsgSend as a bypassed message.
bankSendType := sdk.MsgTypeURL(&banktypes.MsgSend{})
_, err = validator.ExecTx(ctx, wrapper.Authority.KeyName(), "globalfee", "update-bypass-messages", bankSendType, "--fees", fee)
// ASSERT: The transaction was successful and updated the bypass messages.
require.NoError(t, err)
raw, _, err := validator.ExecQuery(ctx, "globalfee", "bypass-messages")
require.NoError(t, err)
var res types.QueryBypassMessagesResponse
require.NoError(t, json.Unmarshal(raw, &res))
require.Len(t, res.BypassMessages, 1)
require.Contains(t, res.BypassMessages, bankSendType)

// ACT: Attempt a transaction with no fees.
err = bankSendWithFees(ctx, validator, sender, recipient, "1ustake", "0ustake")
// ASSERT: The transaction was successful due to bypassed message.
require.NoError(t, err)
}
Loading

0 comments on commit 495e921

Please sign in to comment.