Skip to content

Commit

Permalink
test(tokenfactory)!: integration test core logic with a real smart co…
Browse files Browse the repository at this point in the history
…ntract using `nibiru-std` (#1638)

* test(tokenfactory): [epic] messy but working first version #wip

- Add cosmwasm_1_2 feature to app/keepers.go
- Add smart contract as test fixture
- Test CosmosMsg::Stargate happy paths using raw json inputs

* changelog + linter

* linter

* test: add another serde test case and remove DEBUG statements

* feat: add all features from wasmd to app + more tests

* more tests

* test: patch coverage
  • Loading branch information
Unique-Divine authored Oct 18, 2023
1 parent 4561f47 commit d0f5290
Show file tree
Hide file tree
Showing 13 changed files with 607 additions and 50 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#1614](https://github.com/NibiruChain/nibiru/pull/1614) - refactor(proto): Use explicit namespacing on proto imports for #1608
* [#1630](https://github.com/NibiruChain/nibiru/pull/1630) - refactor(wasm): clean up wasmbinding/ folder structure
* [#1631](https://github.com/NibiruChain/nibiru/pull/1631) - fix(.goreleaser.yml): Load version for wasmvm dynamically.
* [#1638](https://github.com/NibiruChain/nibiru/pull/1638) - test(tokenfactory): integration test core logic with a real smart contract using `nibiru-std`

### Dependencies
- Bump `github.com/prometheus/client_golang` from 1.16.0 to 1.17.0 ([#1605](https://github.com/NibiruChain/nibiru/pull/1605))
Expand Down Expand Up @@ -772,4 +773,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Testing

* [#695](https://github.com/NibiruChain/nibiru/pull/695) Add `OpenPosition` integration tests.
* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods.
* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods.
4 changes: 3 additions & 1 deletion app/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package app

import (
"path/filepath"
"strings"

wasmdapp "github.com/CosmWasm/wasmd/app"
"github.com/CosmWasm/wasmd/x/wasm"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
Expand Down Expand Up @@ -420,7 +422,7 @@ func (app *NibiruApp) InitKeepers(
// assigned.
// For example, if there are bindings for the x/perp module, then the app
// passed to GetWasmOpts must already have a non-nil PerpKeeper.
supportedFeatures := "iterator,staking,stargate,cosmwasm_1_1"
supportedFeatures := strings.Join(wasmdapp.AllCapabilities(), ",")
app.WasmKeeper = wasm.NewKeeper(
appCodec,
keys[wasm.StoreKey],
Expand Down
53 changes: 24 additions & 29 deletions x/common/testutil/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/cosmos/gogoproto/proto"

"github.com/NibiruChain/nibiru/x/common/set"

abci "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
Expand All @@ -33,18 +34,30 @@ func FilterNewEvents(beforeEvents, afterEvents sdk.Events) sdk.Events {
return newEvents
}

// AssertEventsPresent fails the test if the given eventsType are not present in the events
func AssertEventsPresent(t *testing.T, events sdk.Events, eventsType []string) {
for _, eventType := range eventsType {
for _, event := range events {
if event.Type == eventType {
return
}
// AssertEventsPresent: Errors if the given event type is not present in events
func AssertEventPresent(events sdk.Events, eventType string) error {
foundTypes := set.New[string]()
for _, event := range events {
if event.Type == eventType {
return nil
}
foundTypes.Add(event.Type)
}
return fmt.Errorf("event \"%s\" not found within set: %s", eventType, foundTypes.ToSlice())
}

// AssertEventsPresent: Errors if the given event types are not present in events
func AssertEventsPresent(events sdk.Events, eventTypes []string) (err error) {
for _, eventType := range eventTypes {
err := AssertEventPresent(events, eventType)
if err != nil {
return err
}
t.Errorf("event %s not found", eventType)
}
return
}

// RequireNotHasTypedEvent: Error if an event type matches the proto.Message name
func RequireNotHasTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Message) {
name := proto.MessageName(event)
for _, ev := range ctx.EventManager().Events() {
Expand All @@ -54,29 +67,11 @@ func RequireNotHasTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Me
}
}

func RequireHasTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Message) {
for _, abciEvent := range ctx.EventManager().Events() {
if abciEvent.Type != proto.MessageName(event) {
continue
}
typedEvent, err := sdk.ParseTypedEvent(abci.Event{
Type: abciEvent.Type,
Attributes: abciEvent.Attributes,
})
require.NoError(t, err)

require.EqualValues(t, event, typedEvent, "events do not match")
return
}

t.Errorf("event not found")
}

func RequireContainsTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Message) {
foundEvents := []proto.Message{}
for _, abciEvent := range ctx.EventManager().Events() {
eventName := proto.MessageName(event)
if abciEvent.Type != eventName {
eventType := proto.MessageName(event)
if abciEvent.Type != eventType {
continue
}
typedEvent, err := sdk.ParseTypedEvent(abci.Event{
Expand Down
75 changes: 75 additions & 0 deletions x/common/testutil/events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package testutil_test

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

"github.com/NibiruChain/nibiru/x/common/denoms"
"github.com/NibiruChain/nibiru/x/common/testutil"
"github.com/NibiruChain/nibiru/x/common/testutil/testapp"
)

func (s *TestSuite) TestEventsUtils() {
bapp, ctx := testapp.NewNibiruTestAppAndContext()

// Events on the ctx before we broadcast any txs
var beforeEvents sdk.Events = ctx.EventManager().Events()

newCoins := func(coinsStr string) sdk.Coins {
out, err := sdk.ParseCoinsNormalized(coinsStr)
if err != nil {
panic(err)
}
return out
}

funds := sdk.NewCoins(sdk.NewInt64Coin(denoms.NIBI, 5_000_000))
_, addrs := testutil.PrivKeyAddressPairs(2)
senderAddr, otherAddr := addrs[0], addrs[1]
err := testapp.FundAccount(bapp.BankKeeper, ctx, senderAddr, funds)
s.NoError(err)

s.NoError(
bapp.BankKeeper.SendCoins(ctx, senderAddr, otherAddr, newCoins("12unibi")),
)

// Events on the ctx after broadcasting tx
var sdkEvents sdk.Events = ctx.EventManager().Events()

s.Run("AssertEventsPresent", func() {
err = testutil.AssertEventsPresent(sdkEvents,
[]string{"transfer", "coin_received", "message", "coin_spent"},
)
s.NoError(err)
s.Error(
testutil.AssertEventsPresent(sdkEvents, []string{"foobar"}),
)
})

s.Run("EventHasAttributeValue", func() {
var transferEvent sdk.Event
for _, abciEvent := range sdkEvents {
if abciEvent.Type == "transfer" {
transferEvent = abciEvent
}
}
for _, err := range []error{
testutil.EventHasAttributeValue(transferEvent, "sender", senderAddr.String()),
testutil.EventHasAttributeValue(transferEvent, "recipient", otherAddr.String()),
testutil.EventHasAttributeValue(transferEvent, "amount", "12unibi"),
} {
s.NoError(err)
}
})

s.Run("FilterNewEvents", func() {
newEvents := testutil.FilterNewEvents(beforeEvents, sdkEvents)
lenBefore := len(beforeEvents)
lenAfter := len(sdkEvents)
lenNew := len(newEvents)
s.Equal(lenAfter-lenNew, lenBefore)

expectedNewEvents := sdkEvents[lenBefore:lenAfter]
s.Len(expectedNewEvents, lenNew)
s.ElementsMatch(newEvents, expectedNewEvents)
})
}
19 changes: 19 additions & 0 deletions x/common/testutil/path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package testutil

import (
"path"
"path/filepath"
"runtime"
)

// GetPackageDir: Returns the absolute path of the Golang package that
// calls this function.
func GetPackageDir() (string, error) {
// Get the import path of the current package
_, filename, _, _ := runtime.Caller(0)
pkgDir := path.Dir(filename)
pkgPath := path.Join(path.Base(pkgDir), "..")

// Get the directory path of the package
return filepath.Abs(pkgPath)
}
126 changes: 126 additions & 0 deletions x/common/testutil/testutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package testutil_test

import (
"context"
"os/exec"
"path"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/suite"

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

"github.com/NibiruChain/nibiru/x/common/set"
"github.com/NibiruChain/nibiru/x/common/testutil"
)

type TestSuite struct {
suite.Suite
}

func TestTestSuite(t *testing.T) {
suite.Run(t, new(TestSuite))
}

func (s *TestSuite) TestGetPackageDir() {
pkgDir, err := testutil.GetPackageDir()
s.NoError(err)
s.Equal("testutil", path.Base(pkgDir))
s.Equal("common", path.Base(path.Dir(pkgDir)))
}

// TestSampleFns: Tests functions that generate test data from sample.go
func (s *TestSuite) TestSampleFns() {
s.T().Log("consecutive calls give different addrs")
addrs := set.New[string]()
for times := 0; times < 16; times++ {
newAddr := testutil.AccAddress().String()
s.False(addrs.Has(newAddr))
addrs.Add(newAddr)
}
}

func (s *TestSuite) TestPrivKeyAddressPairs() {
s.T().Log("calls should be deterministic")
keysA, addrsA := testutil.PrivKeyAddressPairs(4)
keysB, addrsB := testutil.PrivKeyAddressPairs(4)
s.Equal(keysA, keysB)
s.Equal(addrsA, addrsB)
}

func (s *TestSuite) TestBlankContext() {
ctx := testutil.BlankContext("new-kv-store-key")
goCtx := sdk.WrapSDKContext(ctx)

freshGoCtx := context.Background()
s.Require().Panics(func() { sdk.UnwrapSDKContext(freshGoCtx) })

s.Require().NotPanics(func() { sdk.UnwrapSDKContext(goCtx) })
}

func (s *TestSuite) TestNullifyFill() {
for _, tc := range []struct {
name string
input any
want any
}{
{
name: "nullify fill slice",
input: []string{},
want: make([]string, 0),
},
{
name: "nullify fill struct with coins",
input: struct {
Coins sdk.Coins
Strings []string
}{},
want: struct {
Coins sdk.Coins
Strings []string
}{
Coins: sdk.Coins(nil),
Strings: []string(nil),
},
},
{
name: "nullify fill sdk.Coin struct",
input: struct {
Coin sdk.Coin
Ints []int
}{},
want: struct {
Coin sdk.Coin
Ints []int
}{
Coin: sdk.Coin{},
Ints: []int(nil),
},
},
{
name: "nullify fill pointer to null concrete",
input: new(sdk.Coin),
want: sdk.Coin{},
},
} {
s.Run(tc.name, func() {
got := testutil.Fill(tc.input)
s.EqualValues(tc.want, got)
})
}
}

func (s *TestSuite) TestSetupClientCtx() {
goCtx := testutil.SetupClientCtx(s.T())
trivialCobraCommand := &cobra.Command{
Use: "run-true",
Short: "Runs the Unix command, 'true'",
RunE: func(cmd *cobra.Command, args []string) error {
return exec.Command("true").Run()
},
}

err := trivialCobraCommand.ExecuteContext(goCtx)
s.NoError(err)
}
9 changes: 5 additions & 4 deletions x/perp/v2/module/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,11 @@ func TestEndBlocker(t *testing.T) {
runBlock(5 * time.Second)
afterEvents := ctx.EventManager().Events()

testutilevents.AssertEventsPresent(
t,
testutilevents.FilterNewEvents(beforeEvents, afterEvents),
[]string{"nibiru.perp.v2.AmmUpdatedEvent", "nibiru.perp.v2.MarketUpdatedEvent"},
require.NoError(t,
testutilevents.AssertEventsPresent(
testutilevents.FilterNewEvents(beforeEvents, afterEvents),
[]string{"nibiru.perp.v2.AmmUpdatedEvent", "nibiru.perp.v2.MarketUpdatedEvent"},
),
)

// add index price
Expand Down
6 changes: 6 additions & 0 deletions x/tokenfactory/fixture/fixture.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fixture

const (
// WASM_NIBI_STARGATE is a compiled version of: https://github.com/NibiruChain/cw-nibiru/blob/main/contracts/nibi-stargate/src/contract.rs
WASM_NIBI_STARGATE = "nibi_stargate.wasm"
)
Binary file added x/tokenfactory/fixture/nibi_stargate.wasm
Binary file not shown.
Loading

0 comments on commit d0f5290

Please sign in to comment.