From 813bd5e4a50015489bfac32dfaa1fc9e3366a22e Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 16:49:01 +0700 Subject: [PATCH 01/13] add simualtion test for oracle module --- Makefile | 26 +- app/app.go | 9 +- contrib/devtools/Dockerfile | 36 ++ contrib/devtools/Makefile | 76 +++ contrib/devtools/README.md | 6 + testing/testapp/setup.go | 5 +- .../{testapp => testdata}/wasm_1_simple.go | 2 +- .../wasm_2_return_in_prepare.go | 2 +- .../wasm_3_do_nothing.go | 2 +- .../{testapp => testdata}/wasm_4_complex.go | 2 +- .../wasm_56_computation.go | 2 +- .../wasm_78_large_calldata.go | 2 +- .../wasm_9_set_data_several_times.go | 2 +- testing/{testapp => testdata}/wasm_extras.go | 6 +- testing/{testapp => testdata}/wasm_util.go | 13 +- x/oracle/abci.go | 7 +- x/oracle/handler_test.go | 19 +- x/oracle/ibc_test.go | 7 +- x/oracle/module.go | 50 +- x/oracle/simulation/decoder.go | 51 ++ x/oracle/simulation/decoder_test.go | 118 +++++ x/oracle/simulation/genesis.go | 164 ++++++ x/oracle/simulation/genesis_test.go | 82 +++ x/oracle/simulation/operations.go | 496 ++++++++++++++++++ x/oracle/simulation/operations_test.go | 364 +++++++++++++ 25 files changed, 1513 insertions(+), 36 deletions(-) create mode 100644 contrib/devtools/Dockerfile create mode 100644 contrib/devtools/Makefile create mode 100644 contrib/devtools/README.md rename testing/{testapp => testdata}/wasm_1_simple.go (98%) rename testing/{testapp => testdata}/wasm_2_return_in_prepare.go (96%) rename testing/{testapp => testdata}/wasm_3_do_nothing.go (97%) rename testing/{testapp => testdata}/wasm_4_complex.go (99%) rename testing/{testapp => testdata}/wasm_56_computation.go (98%) rename testing/{testapp => testdata}/wasm_78_large_calldata.go (98%) rename testing/{testapp => testdata}/wasm_9_set_data_several_times.go (97%) rename testing/{testapp => testdata}/wasm_extras.go (89%) rename testing/{testapp => testdata}/wasm_util.go (74%) create mode 100644 x/oracle/simulation/decoder.go create mode 100644 x/oracle/simulation/decoder_test.go create mode 100644 x/oracle/simulation/genesis.go create mode 100644 x/oracle/simulation/genesis_test.go create mode 100644 x/oracle/simulation/operations.go create mode 100644 x/oracle/simulation/operations_test.go diff --git a/Makefile b/Makefile index 30a0fe36b..3f1d2de05 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') COMMIT := $(shell git log -1 --format='%H') LEDGER_ENABLED ?= true BINDIR ?= $(GOPATH)/bin +APP = ./app DOCKER := $(shell which docker) DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf @@ -36,6 +37,8 @@ ldflags := $(strip $(ldflags)) BUILD_FLAGS := -tags "$(build_tags_comma_sep)" -ldflags '$(ldflags)' +include contrib/devtools/Makefile + all: install install: go.sum @@ -92,4 +95,25 @@ proto-lint: proto-check-breaking: @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main -.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking +############################################################################### +### Simulation ### +############################################################################### + +test-sim-import-export: runsim + @echo "Running application import/export simulation. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(APP) -ExitOnFail 50 5 TestAppImportExport + +test-sim-multi-seed-short: runsim + @echo "Running short multi-seed application simulation. This may take awhile!" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(APP) -ExitOnFail 50 5 TestFullAppSimulation + +test-sim-after-import: runsim + @echo "Running application simulation-after-import. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(APP) -ExitOnFail 50 5 TestAppSimulationAfterImport + +test-sim-deterministic: runsim + @echo "Running application deterministic simulation. This may take awhile!" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(APP) -ExitOnFail 1 1 TestAppStateDeterminism + +.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking \ + test-sim-import-export test-sim-multi-seed-short test-sim-after-import test-sim-deterministic diff --git a/app/app.go b/app/app.go index 1f4657fcf..d4b269cf5 100644 --- a/app/app.go +++ b/app/app.go @@ -506,7 +506,14 @@ func NewBandApp( owasmVM, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - oracleModule := oracle.NewAppModule(app.OracleKeeper, app.GetSubspace(oracletypes.ModuleName)) + oracleModule := oracle.NewAppModule( + appCodec, + app.OracleKeeper, + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + app.GetSubspace(oracletypes.ModuleName), + ) oracleIBCModule := oracle.NewIBCModule(app.OracleKeeper) // Create static IBC router, add transfer route, then set and seal it diff --git a/contrib/devtools/Dockerfile b/contrib/devtools/Dockerfile new file mode 100644 index 000000000..53592ff6a --- /dev/null +++ b/contrib/devtools/Dockerfile @@ -0,0 +1,36 @@ +## To test locally: +# docker build --pull --rm -f "contrib/devtools/Dockerfile" -t cosmossdk-proto:latest "contrib/devtools" +# docker run --rm -v $(pwd):/workspace --workdir /workspace cosmossdk-proto sh ./scripts/protocgen.sh + +FROM bufbuild/buf:1.9.0 as BUILDER +FROM golang:1.19-alpine + +RUN apk add --no-cache \ + nodejs \ + npm \ + git \ + make \ + clang-extra-tools + +RUN npm install -g swagger-combine + +ARG UNAME=protobuild +ARG UID=1000 +RUN adduser -u $UID -s /bin/sh $UNAME -D +USER $UNAME + +ENV GOLANG_PROTOBUF_VERSION=1.28.1 \ + GRPC_GATEWAY_VERSION=1.16.0 + +RUN go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest && \ + go install google.golang.org/protobuf/cmd/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} && \ + go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} \ + github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} + +# install all gogo protobuf binaries +RUN git clone https://github.com/cosmos/gogoproto.git; \ + cd gogoproto; \ + go mod download; \ + make install + +COPY --from=BUILDER /usr/local/bin /usr/local/bin \ No newline at end of file diff --git a/contrib/devtools/Makefile b/contrib/devtools/Makefile new file mode 100644 index 000000000..f8a5de4ed --- /dev/null +++ b/contrib/devtools/Makefile @@ -0,0 +1,76 @@ +### +# Find OS and Go environment +# GO contains the Go binary +# FS contains the OS file separator +### +ifeq ($(OS),Windows_NT) + GO := $(shell where go.exe 2> NUL) + FS := "\\" +else + GO := $(shell command -v go 2> /dev/null) + FS := "/" +endif + +ifeq ($(GO),) + $(error could not find go. Is it in PATH? $(GO)) +endif + +############################################################################### +### Functions ### +############################################################################### + +go_get = $(if $(findstring Windows_NT,$(OS)),\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\ +,\ +mkdir -p $(GITHUBDIR)$(FS)$(1) &&\ +(test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\ +)\ +cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3) + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +mkfile_dir := $(shell cd $(shell dirname $(mkfile_path)); pwd) + + +############################################################################### +### Tools ### +############################################################################### + +PREFIX ?= /usr/local +BIN ?= $(PREFIX)/bin +UNAME_S ?= $(shell uname -s) +UNAME_M ?= $(shell uname -m) + +GOPATH ?= $(shell $(GO) env GOPATH) +GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com + +BUF_VERSION ?= 0.11.0 + +TOOLS_DESTDIR ?= $(GOPATH)/bin +STATIK = $(TOOLS_DESTDIR)/statik +RUNSIM = $(TOOLS_DESTDIR)/runsim + +tools: tools-stamp +tools-stamp: statik runsim + # Create dummy file to satisfy dependency and avoid + # rebuilding when this Makefile target is hit twice + # in a row. + touch $@ + +# Install the runsim binary +statik: $(STATIK) +$(STATIK): + @echo "Installing statik..." + @go install github.com/rakyll/statik@v0.1.6 + +# Install the runsim binary +runsim: $(RUNSIM) +$(RUNSIM): + @echo "Installing runsim..." + @go install github.com/cosmos/tools/cmd/runsim@v1.0.0 + +tools-clean: + rm -f $(STATIK) $(GOLANGCI_LINT) $(RUNSIM) + rm -f tools-stamp + +.PHONY: tools-clean statik runsim \ No newline at end of file diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md new file mode 100644 index 000000000..e5415df6c --- /dev/null +++ b/contrib/devtools/README.md @@ -0,0 +1,6 @@ +# Contributors + +Thanks to the entire Cosmos SDK team and the contributors who put their efforts into making simulation testing +easier to implement. 🤗 + + diff --git a/testing/testapp/setup.go b/testing/testapp/setup.go index 9811dd0db..d0a779dd2 100644 --- a/testing/testapp/setup.go +++ b/testing/testapp/setup.go @@ -44,6 +44,7 @@ import ( owasm "github.com/bandprotocol/go-owasm/api" bandapp "github.com/bandprotocol/chain/v2/app" + "github.com/bandprotocol/chain/v2/testing/testdata" "github.com/bandprotocol/chain/v2/x/oracle/keeper" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -193,11 +194,11 @@ func getGenesisOracleScripts(homePath string) []types.OracleScript { fc := filecache.New(dir) OracleScripts = []types.OracleScript{{}} // 0th index should be ignored wasms := [][]byte{ - Wasm1, Wasm2, Wasm3, Wasm4, Wasm56(10), Wasm56(10000000), Wasm78(10), Wasm78(2000), Wasm9, + testdata.Wasm1, testdata.Wasm2, testdata.Wasm3, testdata.Wasm4, testdata.Wasm56(10), testdata.Wasm56(10000000), testdata.Wasm78(10), testdata.Wasm78(2000), testdata.Wasm9, } for idx := 0; idx < len(wasms); idx++ { idxStr := fmt.Sprintf("%d", idx+1) - hash := fc.AddFile(compile(wasms[idx])) + hash := fc.AddFile(testdata.Compile(wasms[idx])) OracleScripts = append(OracleScripts, types.NewOracleScript( Owner.Address, "name"+idxStr, "desc"+idxStr, hash, "schema"+idxStr, "url"+idxStr, )) diff --git a/testing/testapp/wasm_1_simple.go b/testing/testdata/wasm_1_simple.go similarity index 98% rename from testing/testapp/wasm_1_simple.go rename to testing/testdata/wasm_1_simple.go index c7e822917..e6a4829f2 100644 --- a/testing/testapp/wasm_1_simple.go +++ b/testing/testdata/wasm_1_simple.go @@ -1,4 +1,4 @@ -package testapp +package testdata // A simple Owasm script with the following specification: // diff --git a/testing/testapp/wasm_2_return_in_prepare.go b/testing/testdata/wasm_2_return_in_prepare.go similarity index 96% rename from testing/testapp/wasm_2_return_in_prepare.go rename to testing/testdata/wasm_2_return_in_prepare.go index 96f736e51..4814fbd59 100644 --- a/testing/testapp/wasm_2_return_in_prepare.go +++ b/testing/testdata/wasm_2_return_in_prepare.go @@ -1,4 +1,4 @@ -package testapp +package testdata // A bad Owasm script with the following specification: // diff --git a/testing/testapp/wasm_3_do_nothing.go b/testing/testdata/wasm_3_do_nothing.go similarity index 97% rename from testing/testapp/wasm_3_do_nothing.go rename to testing/testdata/wasm_3_do_nothing.go index 43f9ca30f..39d03c327 100644 --- a/testing/testapp/wasm_3_do_nothing.go +++ b/testing/testdata/wasm_3_do_nothing.go @@ -1,4 +1,4 @@ -package testapp +package testdata // A silly oracle script, primarily to test that you must make at least one raw request: // diff --git a/testing/testapp/wasm_4_complex.go b/testing/testdata/wasm_4_complex.go similarity index 99% rename from testing/testapp/wasm_4_complex.go rename to testing/testdata/wasm_4_complex.go index 8320036e1..680df10b9 100644 --- a/testing/testapp/wasm_4_complex.go +++ b/testing/testdata/wasm_4_complex.go @@ -1,4 +1,4 @@ -package testapp +package testdata import ( "encoding/hex" diff --git a/testing/testapp/wasm_56_computation.go b/testing/testdata/wasm_56_computation.go similarity index 98% rename from testing/testapp/wasm_56_computation.go rename to testing/testdata/wasm_56_computation.go index b00cd5185..fbd06538e 100644 --- a/testing/testapp/wasm_56_computation.go +++ b/testing/testdata/wasm_56_computation.go @@ -1,4 +1,4 @@ -package testapp +package testdata import ( "fmt" diff --git a/testing/testapp/wasm_78_large_calldata.go b/testing/testdata/wasm_78_large_calldata.go similarity index 98% rename from testing/testapp/wasm_78_large_calldata.go rename to testing/testdata/wasm_78_large_calldata.go index 012dfa121..95736ffb8 100644 --- a/testing/testapp/wasm_78_large_calldata.go +++ b/testing/testdata/wasm_78_large_calldata.go @@ -1,4 +1,4 @@ -package testapp +package testdata import ( "fmt" diff --git a/testing/testapp/wasm_9_set_data_several_times.go b/testing/testdata/wasm_9_set_data_several_times.go similarity index 97% rename from testing/testapp/wasm_9_set_data_several_times.go rename to testing/testdata/wasm_9_set_data_several_times.go index f235f2117..8f7d9f3dc 100644 --- a/testing/testapp/wasm_9_set_data_several_times.go +++ b/testing/testdata/wasm_9_set_data_several_times.go @@ -1,4 +1,4 @@ -package testapp +package testdata var Wasm9 []byte = wat2wasm([]byte(` (module diff --git a/testing/testapp/wasm_extras.go b/testing/testdata/wasm_extras.go similarity index 89% rename from testing/testapp/wasm_extras.go rename to testing/testdata/wasm_extras.go index 7de803843..bca7a2599 100644 --- a/testing/testapp/wasm_extras.go +++ b/testing/testdata/wasm_extras.go @@ -1,4 +1,4 @@ -package testapp +package testdata import ( "crypto/sha256" @@ -34,8 +34,8 @@ var WasmExtra1FileName string var WasmExtra2FileName string func init() { - wasm1CompiledHash := sha256.Sum256(compile(WasmExtra1)) - wasm2CompiledHash := sha256.Sum256(compile(WasmExtra2)) + wasm1CompiledHash := sha256.Sum256(Compile(WasmExtra1)) + wasm2CompiledHash := sha256.Sum256(Compile(WasmExtra2)) WasmExtra1FileName = hex.EncodeToString(wasm1CompiledHash[:]) WasmExtra2FileName = hex.EncodeToString(wasm2CompiledHash[:]) } diff --git a/testing/testapp/wasm_util.go b/testing/testdata/wasm_util.go similarity index 74% rename from testing/testapp/wasm_util.go rename to testing/testdata/wasm_util.go index f894b967d..cc3c62153 100644 --- a/testing/testapp/wasm_util.go +++ b/testing/testdata/wasm_util.go @@ -1,14 +1,21 @@ -package testapp +package testdata import ( "os" "os/exec" + owasm "github.com/bandprotocol/go-owasm/api" + "github.com/bandprotocol/chain/v2/x/oracle/types" ) -func compile(code []byte) []byte { - compiled, err := OwasmVM.Compile(code, types.MaxCompiledWasmCodeSize) +func Compile(code []byte) []byte { + owasmVM, err := owasm.NewVm(10) + if err != nil { + panic(err) + } + + compiled, err := owasmVM.Compile(code, types.MaxCompiledWasmCodeSize) if err != nil { panic(err) } diff --git a/x/oracle/abci.go b/x/oracle/abci.go index 2b796bd46..1074308d7 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -11,8 +11,11 @@ import ( // handleBeginBlock re-calculates and saves the rolling seed value based on block hashes. func handleBeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { // Update rolling seed used for pseudorandom oracle provider selection. - rollingSeed := k.GetRollingSeed(ctx) - k.SetRollingSeed(ctx, append(rollingSeed[1:], req.GetHash()[0])) + hash := req.GetHash() + if len(hash) > 0 { + rollingSeed := k.GetRollingSeed(ctx) + k.SetRollingSeed(ctx, append(rollingSeed[1:], req.GetHash()[0])) + } // Reward a portion of block rewards (inflation + tx fee) to active oracle validators. k.AllocateTokens(ctx, req.LastCommitInfo.GetVotes()) } diff --git a/x/oracle/handler_test.go b/x/oracle/handler_test.go index 8009d5a8d..dd757a94a 100644 --- a/x/oracle/handler_test.go +++ b/x/oracle/handler_test.go @@ -18,6 +18,7 @@ import ( "github.com/bandprotocol/go-owasm/api" "github.com/bandprotocol/chain/v2/testing/testapp" + "github.com/bandprotocol/chain/v2/testing/testdata" "github.com/bandprotocol/chain/v2/x/oracle" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -183,7 +184,7 @@ func TestCreateOracleScriptSuccess(t *testing.T) { osCount := k.GetOracleScriptCount(ctx) name := "os_1" description := "beeb" - code := testapp.WasmExtra1 + code := testdata.WasmExtra1 schema := "schema" url := "url" msg := types.NewMsgCreateOracleScript( @@ -201,7 +202,7 @@ func TestCreateOracleScriptSuccess(t *testing.T) { require.NoError(t, err) require.Equal( t, - types.NewOracleScript(testapp.Owner.Address, name, description, testapp.WasmExtra1FileName, schema, url), + types.NewOracleScript(testapp.Owner.Address, name, description, testdata.WasmExtra1FileName, schema, url), os, ) @@ -223,7 +224,7 @@ func TestCreateGzippedOracleScriptSuccess(t *testing.T) { url := "url" var buf bytes.Buffer zw := gz.NewWriter(&buf) - zw.Write(testapp.WasmExtra1) + zw.Write(testdata.WasmExtra1) zw.Close() msg := types.NewMsgCreateOracleScript( name, @@ -240,7 +241,7 @@ func TestCreateGzippedOracleScriptSuccess(t *testing.T) { require.NoError(t, err) require.Equal( t, - types.NewOracleScript(testapp.Owner.Address, name, description, testapp.WasmExtra1FileName, schema, url), + types.NewOracleScript(testapp.Owner.Address, name, description, testdata.WasmExtra1FileName, schema, url), os, ) @@ -275,7 +276,7 @@ func TestCreateOracleScriptFail(t *testing.T) { // Bad Gzip var buf bytes.Buffer zw := gz.NewWriter(&buf) - zw.Write(testapp.WasmExtra1) + zw.Write(testdata.WasmExtra1) zw.Close() msg = types.NewMsgCreateOracleScript( name, @@ -295,7 +296,7 @@ func TestEditOracleScriptSuccess(t *testing.T) { _, ctx, k := testapp.CreateTestInput(false) newName := "os_2" newDescription := "beebbeeb" - newCode := testapp.WasmExtra2 + newCode := testdata.WasmExtra2 newSchema := "new_schema" newURL := "new_url" msg := types.NewMsgEditOracleScript( @@ -318,7 +319,7 @@ func TestEditOracleScriptSuccess(t *testing.T) { testapp.Alice.Address, newName, newDescription, - testapp.WasmExtra2FileName, + testdata.WasmExtra2FileName, newSchema, newURL, ), @@ -336,7 +337,7 @@ func TestEditOracleScriptFail(t *testing.T) { _, ctx, k := testapp.CreateTestInput(false) newName := "os_2" newDescription := "beebbeeb" - newCode := testapp.WasmExtra2 + newCode := testdata.WasmExtra2 newSchema := "new_schema" newURL := "new_url" // Bad ID @@ -384,7 +385,7 @@ func TestEditOracleScriptFail(t *testing.T) { // Bad Gzip var buf bytes.Buffer zw := gz.NewWriter(&buf) - zw.Write(testapp.WasmExtra2) + zw.Write(testdata.WasmExtra2) zw.Close() msg = types.NewMsgEditOracleScript( 1, diff --git a/x/oracle/ibc_test.go b/x/oracle/ibc_test.go index d31f212f2..51f48843e 100644 --- a/x/oracle/ibc_test.go +++ b/x/oracle/ibc_test.go @@ -15,6 +15,7 @@ import ( ibctesting "github.com/bandprotocol/chain/v2/testing" "github.com/bandprotocol/chain/v2/testing/testapp" + "github.com/bandprotocol/chain/v2/testing/testdata" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -511,7 +512,7 @@ func (suite *OracleTestSuite) TestIBCPrepareRequestInvalidDataSourceCount() { oracleRequestPacket := types.NewOracleRequestPacketData( path.EndpointA.ClientID, 4, - obi.MustEncode(testapp.Wasm4Input{ + obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2, 3, 4}, Calldata: "beeb", }), @@ -636,7 +637,7 @@ func (suite *OracleTestSuite) TestIBCResolveReadNilExternalData() { oracleRequestPacket := types.NewOracleRequestPacketData( path.EndpointA.ClientID, 4, - obi.MustEncode(testapp.Wasm4Input{IDs: []int64{1, 2}, Calldata: string("beeb")}), + obi.MustEncode(testdata.Wasm4Input{IDs: []int64{1, 2}, Calldata: string("beeb")}), 2, 2, sdk.NewCoins(sdk.NewCoin("uband", sdk.NewInt(4000000))), @@ -671,7 +672,7 @@ func (suite *OracleTestSuite) TestIBCResolveReadNilExternalData() { 1577923380, 1577923405, types.RESOLVE_STATUS_SUCCESS, - obi.MustEncode(testapp.Wasm4Output{Ret: "beebd1v2beebd2v1"}), + obi.MustEncode(testdata.Wasm4Output{Ret: "beebd1v2beebd2v1"}), ) responsePacket := channeltypes.NewPacket( oracleResponsePacket.GetBytes(), diff --git a/x/oracle/module.go b/x/oracle/module.go index 48c5986ef..d32e036b8 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -14,11 +14,13 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/bandprotocol/chain/v2/x/oracle/client/cli" "github.com/bandprotocol/chain/v2/x/oracle/exported" "github.com/bandprotocol/chain/v2/x/oracle/keeper" + "github.com/bandprotocol/chain/v2/x/oracle/simulation" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -26,13 +28,16 @@ import ( const ConsensusVersion = 2 var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - _ porttypes.IBCModule = IBCModule{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} + _ porttypes.IBCModule = IBCModule{} ) // AppModuleBasic is Band Oracle's module basic object. -type AppModuleBasic struct{} +type AppModuleBasic struct { + cdc codec.Codec +} // Name returns this module's name - "oracle" (SDK AppModuleBasic interface). func (AppModuleBasic) Name() string { @@ -86,12 +91,28 @@ type AppModule struct { // legacySubspace is used solely for migration of x/params managed parameters legacySubspace exported.Subspace + + // for simulation + accountKeeper types.AccountKeeper + bankKeeper simulation.BankKeeper + stakingKeeper types.StakingKeeper } // NewAppModule creates a new AppModule object. -func NewAppModule(k keeper.Keeper, ss exported.Subspace) AppModule { +func NewAppModule( + cdc codec.Codec, + k keeper.Keeper, + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + ss exported.Subspace, +) AppModule { return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, keeper: k, + accountKeeper: ak, + bankKeeper: bk, + stakingKeeper: sk, legacySubspace: ss, } } @@ -140,3 +161,22 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val handleEndBlock(ctx, am.keeper) return []abci.ValidatorUpdate{} } + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the feegrant module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RegisterStoreDecoder registers a decoder for feegrant module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns all the oracle module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.stakingKeeper, am.keeper, + ) +} diff --git a/x/oracle/simulation/decoder.go b/x/oracle/simulation/decoder.go new file mode 100644 index 000000000..dd614cc42 --- /dev/null +++ b/x/oracle/simulation/decoder.go @@ -0,0 +1,51 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/bandprotocol/chain/v2/x/oracle/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding oracle type. +func NewDecodeStore(cdc codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.RequestStoreKeyPrefix): + var rA, rB types.Request + cdc.MustUnmarshal(kvA.Value, &rA) + cdc.MustUnmarshal(kvB.Value, &rB) + return fmt.Sprintf("%v\n%v", rA, rB) + case bytes.Equal(kvA.Key[:1], types.ReportStoreKeyPrefix): + var rA, rB types.Report + cdc.MustUnmarshal(kvA.Value, &rA) + cdc.MustUnmarshal(kvB.Value, &rB) + return fmt.Sprintf("%v\n%v", rA, rB) + case bytes.Equal(kvA.Key[:1], types.DataSourceStoreKeyPrefix): + var dsA, dsB types.DataSource + cdc.MustUnmarshal(kvA.Value, &dsA) + cdc.MustUnmarshal(kvB.Value, &dsB) + return fmt.Sprintf("%v\n%v", dsA, dsB) + case bytes.Equal(kvA.Key[:1], types.OracleScriptStoreKeyPrefix): + var osA, osB types.OracleScript + cdc.MustUnmarshal(kvA.Value, &osA) + cdc.MustUnmarshal(kvB.Value, &osB) + return fmt.Sprintf("%v\n%v", osA, osB) + case bytes.Equal(kvA.Key[:1], types.ValidatorStatusKeyPrefix): + var vsA, vsB types.ValidatorStatus + cdc.MustUnmarshal(kvA.Value, &vsA) + cdc.MustUnmarshal(kvB.Value, &vsB) + return fmt.Sprintf("%v\n%v", vsA, vsB) + case bytes.Equal(kvA.Key[:1], types.ResultStoreKeyPrefix): + var rA, rB types.Result + cdc.MustUnmarshal(kvA.Value, &rA) + cdc.MustUnmarshal(kvB.Value, &rB) + return fmt.Sprintf("%v\n%v", rA, rB) + default: + panic(fmt.Sprintf("invalid oracle key %X", kvA.Key)) + } + } +} diff --git a/x/oracle/simulation/decoder_test.go b/x/oracle/simulation/decoder_test.go new file mode 100644 index 000000000..36fef761c --- /dev/null +++ b/x/oracle/simulation/decoder_test.go @@ -0,0 +1,118 @@ +package simulation_test + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/stretchr/testify/require" + + "github.com/bandprotocol/chain/v2/x/oracle" + "github.com/bandprotocol/chain/v2/x/oracle/simulation" + "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +var ( + accAddr = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + valAddr1 = sdk.ValAddress(ed25519.GenPrivKey().PubKey().Address()) + valAddr2 = sdk.ValAddress(ed25519.GenPrivKey().PubKey().Address()) + valAddr3 = sdk.ValAddress(ed25519.GenPrivKey().PubKey().Address()) + treaAddr = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) +) + +func TestDecodeStore(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(oracle.AppModuleBasic{}).Codec + dec := simulation.NewDecodeStore(cdc) + + rawRequest := types.NewRawRequest(1, 1, []byte("calldata")) + request := types.NewRequest( + 1, + []byte("calldata"), + []sdk.ValAddress{valAddr1, valAddr2, valAddr3}, + 2, + 1, + time.Now().UTC(), + "client", + []types.RawRequest{rawRequest}, + nil, + 100000, + ) + + rawReport := types.NewRawReport(1, 0, []byte("data")) + report := types.NewReport(valAddr1, true, []types.RawReport{rawReport}) + + dataSource := types.NewDataSource( + accAddr, + "name", + "description", + "filename", + sdk.NewCoins(sdk.NewInt64Coin("band", 1000)), + treaAddr, + ) + + oracleScript := types.NewOracleScript( + accAddr, + "name", + "description", + "filename", + "{symbols:[string],multiplier:u64}/{rates:[u64]}", + "https://url.com", + ) + + status := types.NewValidatorStatus(true, time.Now().UTC()) + + result := types.NewResult( + "client", + 1, + []byte("calldata"), + 3, + 2, + 1, + 1, + 1000, + 1000, + types.RESOLVE_STATUS_SUCCESS, + []byte("result"), + ) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: types.RequestStoreKey(1), Value: cdc.MustMarshal(&request)}, + {Key: types.ReportStoreKey(1), Value: cdc.MustMarshal(&report)}, + {Key: types.DataSourceStoreKey(1), Value: cdc.MustMarshal(&dataSource)}, + {Key: types.OracleScriptStoreKey(1), Value: cdc.MustMarshal(&oracleScript)}, + {Key: types.ValidatorStatusStoreKey(valAddr1), Value: cdc.MustMarshal(&status)}, + {Key: types.ResultStoreKey(1), Value: cdc.MustMarshal(&result)}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Request", fmt.Sprintf("%v\n%v", request, request)}, + {"Report", fmt.Sprintf("%v\n%v", report, report)}, + {"DataSource", fmt.Sprintf("%v\n%v", dataSource, dataSource)}, + {"OracleScript", fmt.Sprintf("%v\n%v", oracleScript, oracleScript)}, + {"ValidatorStatus", fmt.Sprintf("%v\n%v", status, status)}, + {"Result", fmt.Sprintf("%v\n%v", result, result)}, + {"other", ""}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/oracle/simulation/genesis.go b/x/oracle/simulation/genesis.go new file mode 100644 index 000000000..a90393e20 --- /dev/null +++ b/x/oracle/simulation/genesis.go @@ -0,0 +1,164 @@ +package simulation + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +// GenMaxRawRequestCount returns randomize MaxRawRequestCount +func GenMaxRawRequestCount(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 1, 100)) +} + +// GenMaxAskCount returns randomize MaxAskCount +func GenMaxAskCount(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 10, 50)) +} + +// GenMaxCalldataSize returns randomize MaxCalldataSize +func GenMaxCalldataSize(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 100, 1000)) +} + +// GenMaxReportDataSize returns randomize MaxReportDataSize +func GenMaxReportDataSize(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 100, 1000)) +} + +// GenExpirationBlockCount returns randomize ExpirationBlockCount +func GenExpirationBlockCount(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 10, 1000)) +} + +// GenBaseOwasmGas returns randomize BaseOwasmGas +func GenBaseOwasmGas(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 0, 300000)) +} + +// GenPerValidatorRequestGas returns randomize PerValidatorRequestGas +func GenPerValidatorRequestGas(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 0, 10000)) +} + +// GenSamplingTryCount returns randomize SamplingTryCount +func GenSamplingTryCount(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 1, 10)) +} + +// GenOracleRewardPercentage returns randomize OracleRewardPercentage +func GenOracleRewardPercentage(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 0, 100)) +} + +// GenInactivePenaltyDuration returns randomize InactivePenaltyDuration +func GenInactivePenaltyDuration(r *rand.Rand) uint64 { + return uint64(simulation.RandIntBetween(r, 10000000000, 1000000000000)) +} + +// GenIBCRequestEnabled returns randomized IBCRequestEnabled +func GenIBCRequestEnabled(r *rand.Rand) bool { + return r.Int63n(100) < 50 +} + +// RandomizedGenState generates a random GenesisState for oracle +func RandomizedGenState(simState *module.SimulationState) { + var maxRawRequestCount uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyMaxRawRequestCount), &maxRawRequestCount, simState.Rand, + func(r *rand.Rand) { maxRawRequestCount = GenMaxRawRequestCount(r) }, + ) + + var maxAskCount uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyMaxAskCount), &maxAskCount, simState.Rand, + func(r *rand.Rand) { maxAskCount = GenMaxAskCount(r) }, + ) + + var maxCalldataSize uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyMaxCalldataSize), &maxCalldataSize, simState.Rand, + func(r *rand.Rand) { maxCalldataSize = GenMaxCalldataSize(r) }, + ) + + var maxReportDataSize uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyMaxReportDataSize), &maxReportDataSize, simState.Rand, + func(r *rand.Rand) { maxReportDataSize = GenMaxReportDataSize(r) }, + ) + + var expirationBlockCount uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyExpirationBlockCount), &expirationBlockCount, simState.Rand, + func(r *rand.Rand) { expirationBlockCount = GenExpirationBlockCount(r) }, + ) + + var baseOwasmGas uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyBaseOwasmGas), &baseOwasmGas, simState.Rand, + func(r *rand.Rand) { baseOwasmGas = GenBaseOwasmGas(r) }, + ) + + var perValidatorRequestGas uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyPerValidatorRequestGas), &perValidatorRequestGas, simState.Rand, + func(r *rand.Rand) { perValidatorRequestGas = GenPerValidatorRequestGas(r) }, + ) + + var samplingTryCount uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeySamplingTryCount), &samplingTryCount, simState.Rand, + func(r *rand.Rand) { samplingTryCount = GenSamplingTryCount(r) }, + ) + + var oracleRewardPercentage uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyOracleRewardPercentage), &oracleRewardPercentage, simState.Rand, + func(r *rand.Rand) { oracleRewardPercentage = GenOracleRewardPercentage(r) }, + ) + + var inactivePenaltyDuration uint64 + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyInactivePenaltyDuration), &inactivePenaltyDuration, simState.Rand, + func(r *rand.Rand) { inactivePenaltyDuration = GenInactivePenaltyDuration(r) }, + ) + + var ibcRequestEnabled bool + simState.AppParams.GetOrGenerate( + simState.Cdc, string(types.KeyIBCRequestEnabled), &ibcRequestEnabled, simState.Rand, + func(r *rand.Rand) { ibcRequestEnabled = GenIBCRequestEnabled(r) }, + ) + + oracleGenesis := types.NewGenesisState( + types.NewParams( + maxRawRequestCount, + maxAskCount, + maxCalldataSize, + maxReportDataSize, + expirationBlockCount, + baseOwasmGas, + perValidatorRequestGas, + samplingTryCount, + oracleRewardPercentage, + inactivePenaltyDuration, + ibcRequestEnabled, + ), + []types.DataSource{}, + []types.OracleScript{}, + ) + + bz, err := json.MarshalIndent(&oracleGenesis, "", " ") + if err != nil { + panic(err) + } + + fmt.Printf("Selected randomly generated oracle parameters:\n%s\n", bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(oracleGenesis) +} diff --git a/x/oracle/simulation/genesis_test.go b/x/oracle/simulation/genesis_test.go new file mode 100644 index 000000000..9654586f1 --- /dev/null +++ b/x/oracle/simulation/genesis_test.go @@ -0,0 +1,82 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/bandprotocol/chain/v2/x/oracle" + "github.com/bandprotocol/chain/v2/x/oracle/simulation" + "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +func TestRandomizedGenState(t *testing.T) { + cdc := moduletestutil.MakeTestEncodingConfig(oracle.AppModuleBasic{}).Codec + s := rand.NewSource(1) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var oracleGenesis types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &oracleGenesis) + + require.Equal(t, uint64(18), oracleGenesis.Params.MaxRawRequestCount) + require.Equal(t, uint64(12), oracleGenesis.Params.MaxAskCount) + require.Equal(t, uint64(700), oracleGenesis.Params.MaxCalldataSize) + require.Equal(t, uint64(294), oracleGenesis.Params.MaxReportDataSize) + require.Equal(t, uint64(791), oracleGenesis.Params.ExpirationBlockCount) + require.Equal(t, uint64(228162), oracleGenesis.Params.BaseOwasmGas) + require.Equal(t, uint64(5089), oracleGenesis.Params.PerValidatorRequestGas) + require.Equal(t, uint64(5), oracleGenesis.Params.SamplingTryCount) + require.Equal(t, uint64(74), oracleGenesis.Params.OracleRewardPercentage) + require.Equal(t, uint64(265472644968), oracleGenesis.Params.InactivePenaltyDuration) + require.Equal(t, false, oracleGenesis.Params.IBCRequestEnabled) + require.Equal(t, []types.DataSource{}, oracleGenesis.DataSources) + require.Equal(t, []types.OracleScript{}, oracleGenesis.OracleScripts) +} + +// TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. +func TestRandomizedGenState1(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + // all these tests will panic + tests := []struct { + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + module.SimulationState{}, "invalid memory address or nil pointer dereference"}, + { // panic => reason: incomplete initialization of the simState + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, "assignment to entry in nil map"}, + } + + for _, tt := range tests { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + } +} diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go new file mode 100644 index 000000000..791c16c61 --- /dev/null +++ b/x/oracle/simulation/operations.go @@ -0,0 +1,496 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/cosmos/cosmos-sdk/x/simulation" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/bandprotocol/chain/v2/testing/testdata" + "github.com/bandprotocol/chain/v2/x/oracle/keeper" + "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +// Simulation operation weights constants +const ( + OpWeightMsgRequestData = "op_weight_msg_request_data" + OpWeightMsgReportData = "op_weight_msg_report_data" + OpWeightMsgCreateDataSource = "op_weight_msg_create_data_source" + OpWeightMsgEditDataSource = "op_weight_msg_edit_data_source" + OpWeightMsgCreateOracleScript = "op_weight_msg_create_oracle_script" + OpWeightMsgEditOracleScript = "op_weight_msg_edit_oracle_script" + OpWeightMsgActivate = "op_weight_msg_activate" + + DefaultWeightMsgRequestData int = 100 + DefaultWeightMsgReportData int = 100 + DefaultWeightMsgCreateDataSource int = 100 + DefaultWeightMsgEditDataSource int = 100 + DefaultWeightMsgCreateOracleScript int = 100 + DefaultWeightMsgEditOracleScript int = 100 + DefaultWeightMsgActivate int = 100 +) + +type BankKeeper interface { + simulation.BankKeeper + IsSendEnabledCoin(ctx sdk.Context, coin sdk.Coin) bool +} + +func WeightedOperations( + appParams simtypes.AppParams, + cdc codec.JSONCodec, + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgRequestData int + weightMsgReportData int + weightMsgCreateDataSource int + weightMsgEditDataSource int + weightMsgCreateOracleScript int + weightMsgEditOracleScript int + weightMsgActivate int + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgRequestData, &weightMsgRequestData, nil, + func(_ *rand.Rand) { + weightMsgRequestData = DefaultWeightMsgRequestData + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgReportData, &weightMsgReportData, nil, + func(_ *rand.Rand) { + weightMsgReportData = DefaultWeightMsgReportData + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgCreateDataSource, &weightMsgCreateDataSource, nil, + func(_ *rand.Rand) { + weightMsgCreateDataSource = DefaultWeightMsgCreateDataSource + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgEditDataSource, &weightMsgEditDataSource, nil, + func(_ *rand.Rand) { + weightMsgEditDataSource = DefaultWeightMsgEditDataSource + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgCreateOracleScript, &weightMsgCreateOracleScript, nil, + func(_ *rand.Rand) { + weightMsgCreateOracleScript = DefaultWeightMsgCreateOracleScript + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgEditOracleScript, &weightMsgEditOracleScript, nil, + func(_ *rand.Rand) { + weightMsgEditOracleScript = DefaultWeightMsgEditOracleScript + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgActivate, &weightMsgActivate, nil, + func(_ *rand.Rand) { + weightMsgActivate = DefaultWeightMsgActivate + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgRequestData, + SimulateMsgRequestData(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgReportData, + SimulateMsgReportData(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgCreateDataSource, + SimulateMsgCreateDataSource(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgEditDataSource, + SimulateMsgEditDataSource(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgCreateOracleScript, + SimulateMsgCreateOracleScript(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgEditOracleScript, + SimulateMsgEditOracleScript(ak, bk, sk, k), + ), + simulation.NewWeightedOperation( + weightMsgActivate, + SimulateMsgActivate(ak, bk, sk, k), + ), + } +} + +// SimulateMsgRequestData generates a MsgRequestData with random values +func SimulateMsgRequestData( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + + oCount := keeper.GetOracleScriptCount(ctx) + oid := types.OracleScriptID(0) + for i := uint64(1); i <= oCount; i++ { + os, _ := keeper.GetOracleScript(ctx, types.OracleScriptID(i)) + _, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(os.Owner)) + if ok { + oid = types.OracleScriptID(i) + break + } + } + if oid == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgRequestData{}.Type(), + "no oracle script available", + ), nil, nil + } + + did := keeper.GetDataSourceCount(ctx) + if did < 3 { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgRequestData{}.Type(), + "data sources are not enough", + ), nil, nil + } + + maxAskCount := 0 + sk.IterateBondedValidatorsByPower(ctx, + func(idx int64, val stakingtypes.ValidatorI) (stop bool) { + if keeper.GetValidatorStatus(ctx, val.GetOperator()).IsActive { + maxAskCount++ + } + + return false + }, + ) + if maxAskCount == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgRequestData{}.Type(), + "active validators are not enough", + ), nil, nil + } + + if maxAskCount > 10 { + maxAskCount = 10 + } + askCount := simtypes.RandIntBetween(r, 1, maxAskCount+1) + + msg := types.MsgRequestData{ + Sender: simAccount.Address.String(), + OracleScriptID: types.OracleScriptID(oid), + Calldata: []byte(simtypes.RandStringOfLength(r, 100)), + AskCount: uint64(askCount), + MinCount: uint64(simtypes.RandIntBetween(r, 1, askCount+1)), + ClientID: simtypes.RandStringOfLength(r, 100), + FeeLimit: sdk.NewCoins(sdk.NewInt64Coin("uband", 0)), + PrepareGas: uint64(simtypes.RandIntBetween(r, 100000, 200000)), + ExecuteGas: uint64(simtypes.RandIntBetween(r, 100000, 200000)), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgReportData generates a MsgReportData with random values +func SimulateMsgReportData( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + var simAccount simtypes.Account + + rCount := keeper.GetRequestCount(ctx) + rid := types.RequestID(0) + for i := uint64(1); i <= rCount; i++ { + req, _ := keeper.GetRequest(ctx, types.RequestID(i)) + + fmt.Printf("req %+v\n", req) + + for _, val := range req.RequestedValidators { + valAddr, _ := sdk.ValAddressFromBech32(val) + acc, ok := simtypes.FindAccount(accs, sdk.AccAddress(valAddr)) + + if ok && !keeper.HasReport(ctx, types.RequestID(i), valAddr) { + simAccount = acc + rid = types.RequestID(i) + break + } + } + + if rid != 0 { + break + } + } + + if rid == 0 { + fmt.Printf("as") + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgReportData{}.Type(), + "no request available", + ), nil, nil + } + + var rawReports []types.RawReport + for i := 1; i <= 3; i++ { + rawReports = append(rawReports, types.RawReport{ + ExternalID: types.ExternalID(i), + ExitCode: uint32(simtypes.RandIntBetween(r, 0, 255)), + Data: []byte(simtypes.RandStringOfLength(r, 100)), + }) + } + + msg := types.MsgReportData{ + RequestID: types.RequestID(rid), + RawReports: rawReports, + Validator: sdk.ValAddress(simAccount.Address).String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgCreateDataSource generates a MsgCreateDataSource with random values +func SimulateMsgCreateDataSource( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + ownerAccount, _ := simtypes.RandomAcc(r, accs) + treaAccount, _ := simtypes.RandomAcc(r, accs) + + msg := types.MsgCreateDataSource{ + Sender: simAccount.Address.String(), + Name: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Executable: []byte(simtypes.RandStringOfLength(r, 100)), + Fee: sdk.NewCoins(sdk.NewInt64Coin("uband", 0)), + Treasury: treaAccount.Address.String(), + Owner: ownerAccount.Address.String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgEditDataSource generates a MsgEditDataSource with random values +func SimulateMsgEditDataSource( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + ownerAccount, _ := simtypes.RandomAcc(r, accs) + treaAccount, _ := simtypes.RandomAcc(r, accs) + + dCount := keeper.GetDataSourceCount(ctx) + did := types.DataSourceID(0) + for i := uint64(1); i <= dCount; i++ { + os, _ := keeper.GetDataSource(ctx, types.DataSourceID(i)) + _, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(os.Owner)) + if ok { + did = types.DataSourceID(i) + break + } + } + + if did == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgEditDataSource{}.Type(), + "no data source available", + ), nil, nil + } + + ds, _ := keeper.GetDataSource(ctx, types.DataSourceID(did)) + simAccount, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(ds.Owner)) + if !ok { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgEditDataSource{}.Type(), + "unknown owner", + ), nil, nil + } + + msg := types.MsgEditDataSource{ + Sender: simAccount.Address.String(), + DataSourceID: types.DataSourceID(did), + Name: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Executable: []byte(simtypes.RandStringOfLength(r, 100)), + Fee: sdk.NewCoins(sdk.NewInt64Coin("uband", 0)), + Treasury: treaAccount.Address.String(), + Owner: ownerAccount.Address.String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgCreateOracleScript generates a MsgCreateOracleScript with random values +func SimulateMsgCreateOracleScript( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + ownerAccount, _ := simtypes.RandomAcc(r, accs) + + msg := types.MsgCreateOracleScript{ + Sender: simAccount.Address.String(), + Name: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Schema: simtypes.RandStringOfLength(r, 100), + SourceCodeURL: simtypes.RandStringOfLength(r, 100), + Code: testdata.Wasm1, + Owner: ownerAccount.Address.String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgEditOracleScript generates a MsgEditOracleScript with random values +func SimulateMsgEditOracleScript( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + var simAccount simtypes.Account + + oCount := keeper.GetOracleScriptCount(ctx) + oid := types.OracleScriptID(0) + for i := uint64(1); i <= oCount; i++ { + os, _ := keeper.GetOracleScript(ctx, types.OracleScriptID(i)) + acc, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(os.Owner)) + if ok { + simAccount = acc + oid = types.OracleScriptID(i) + break + } + } + + if oid == 0 { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgEditOracleScript{}.Type(), + "no oracle script available", + ), nil, nil + } + + msg := types.MsgEditOracleScript{ + Sender: simAccount.Address.String(), + OracleScriptID: types.OracleScriptID(oid), + Name: simtypes.RandStringOfLength(r, 10), + Description: simtypes.RandStringOfLength(r, 100), + Schema: simtypes.RandStringOfLength(r, 100), + SourceCodeURL: simtypes.RandStringOfLength(r, 100), + Code: testdata.Wasm1, + Owner: simAccount.Address.String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgActivate generates a MsgActivate with random values +func SimulateMsgActivate( + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + keeper keeper.Keeper, +) simtypes.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + simAccount, _ := simtypes.RandomAcc(r, accs) + + if keeper.GetValidatorStatus(ctx, sdk.ValAddress(simAccount.Address)).IsActive { + return simtypes.NoOpMsg( + types.ModuleName, + types.MsgActivate{}.Type(), + "already activate", + ), nil, nil + } + + msg := types.MsgActivate{ + Validator: sdk.ValAddress(simAccount.Address).String(), + } + + txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, sk, nil) + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// BuildOperationInput helper to build object +func BuildOperationInput( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + msg interface { + sdk.Msg + Type() string + }, + simAccount simtypes.Account, + ak types.AccountKeeper, + bk simulation.BankKeeper, + sk types.StakingKeeper, + deposit sdk.Coins, +) simulation.OperationInput { + interfaceRegistry := codectypes.NewInterfaceRegistry() + txConfig := tx.NewTxConfig(codec.NewProtoCodec(interfaceRegistry), tx.DefaultSignModes) + return simulation.OperationInput{ + R: r, + App: app, + TxGen: txConfig, + Cdc: nil, + Msg: msg, + MsgType: msg.Type(), + Context: ctx, + SimAccount: simAccount, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: deposit, + } +} diff --git a/x/oracle/simulation/operations_test.go b/x/oracle/simulation/operations_test.go new file mode 100644 index 000000000..a19b0953e --- /dev/null +++ b/x/oracle/simulation/operations_test.go @@ -0,0 +1,364 @@ +package simulation_test + +import ( + "encoding/hex" + "math/rand" + "testing" + "time" + + abci "github.com/cometbft/cometbft/abci/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/bank/testutil" + "github.com/stretchr/testify/suite" + + "github.com/bandprotocol/chain/v2/testing/testapp" + "github.com/bandprotocol/chain/v2/x/oracle/simulation" + "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +type SimTestSuite struct { + suite.Suite + + ctx sdk.Context + app *testapp.TestingApp + r *rand.Rand + accs []simtypes.Account +} + +func (suite *SimTestSuite) SetupTest() { + app, _, _ := testapp.CreateTestInput(true) + suite.app = app + suite.ctx = app.BaseApp.NewContext(false, tmproto.Header{ChainID: "BANDCHAIN"}) + s := rand.NewSource(1) + suite.r = rand.New(s) + suite.accs = suite.getTestingAccounts(suite.r, 10) + + // begin a new block + suite.app.BeginBlock( + abci.RequestBeginBlock{ + Header: tmproto.Header{ + ChainID: "BANDCHAIN", + Height: suite.app.LastBlockHeight() + 1, + AppHash: suite.app.LastCommitID().Hash, + }, + }, + ) + +} + +// TestWeightedOperations tests the weights of the operations. +func (suite *SimTestSuite) TestWeightedOperations() { + cdc := suite.app.AppCodec() + appParams := make(simtypes.AppParams) + + weightesOps := simulation.WeightedOperations( + appParams, + cdc, + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + {simulation.DefaultWeightMsgRequestData, types.ModuleName, types.TypeMsgRequestData}, + {simulation.DefaultWeightMsgReportData, types.ModuleName, types.TypeMsgReportData}, + {simulation.DefaultWeightMsgCreateDataSource, types.ModuleName, types.TypeMsgCreateDataSource}, + {simulation.DefaultWeightMsgEditDataSource, types.ModuleName, types.TypeMsgEditDataSource}, + {simulation.DefaultWeightMsgCreateOracleScript, types.ModuleName, types.TypeMsgCreateOracleScript}, + {simulation.DefaultWeightMsgEditOracleScript, types.ModuleName, types.TypeMsgEditOracleScript}, + {simulation.DefaultWeightMsgActivate, types.ModuleName, types.TypeMsgActivate}, + } + + for i, w := range weightesOps { + operationMsg, _, _ := w.Op()(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + // the following checks are very much dependent from the ordering of the output given + // by WeightedOperations. if the ordering in WeightedOperations changes some tests + // will fail + suite.Require().Equal(expected[i].weight, w.Weight(), "weight should be the same") + suite.Require().Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") + suite.Require().Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") + } +} + +// TestSimulateMsgRequestData tests the normal scenario of a valid message of type TypeMsgRequestData +func (suite *SimTestSuite) TestSimulateMsgRequestData() { + suite.TestSimulateMsgCreateOracleScript() + for i := 1; i <= 3; i++ { + ds, _ := suite.app.OracleKeeper.GetDataSource(suite.ctx, types.DataSourceID(i)) + ds.Fee = sdk.NewCoins() + suite.app.OracleKeeper.SetDataSource(suite.ctx, types.DataSourceID(i), ds) + } + + op := simulation.SimulateMsgRequestData( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgRequestData + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal(types.OracleScriptID(10), msg.OracleScriptID) + suite.Require(). + Equal("6f7857727a526e54566a5374506164687345536c45526e4b68704550736644784e767871634f7949756c61436b6d5064616d624c48764768545a7a7973767146617545676b4652497450667669736568466d6f426851716d6b6662485673676648584450", hex.EncodeToString(msg.Calldata)) + suite.Require().Equal(uint64(3), msg.AskCount) + suite.Require().Equal(uint64(3), msg.MinCount) + suite.Require(). + Equal("RTRnuwdBeuOGgFbJLbDksHVapaRayWzwoYBEpmrlAxrUxYMUekKbpjPNfjUCjhbdMAnJmYQVZBQZkFVweHDAlaqJjRqoQPoOMLhy", msg.ClientID) + suite.Require().Equal(sdk.Coins(nil), msg.FeeLimit) + suite.Require().Equal(uint64(169271), msg.PrepareGas) + suite.Require().Equal(uint64(115894), msg.ExecuteGas) + suite.Require().Equal("band1ghekyjucln7y67ntx7cf27m9dpuxxemnvh82dt", msg.Sender) + suite.Require().Equal(types.TypeMsgRequestData, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgReportData tests the normal scenario of a valid message of type TypeMsgReportData +func (suite *SimTestSuite) TestSimulateMsgReportData() { + suite.app.OracleKeeper.AddRequest( + suite.ctx, + types.NewRequest(types.OracleScriptID(1), + []byte("calldata"), + []sdk.ValAddress{sdk.ValAddress(suite.accs[0].Address)}, + 1, + 1, + time.Now().UTC(), + "clientID", + []types.RawRequest{ + types.NewRawRequest(types.ExternalID(1), types.DataSourceID(1), []byte("data")), + types.NewRawRequest(types.ExternalID(2), types.DataSourceID(2), []byte("data")), + types.NewRawRequest(types.ExternalID(3), types.DataSourceID(3), []byte("data")), + }, + nil, + 300000, + ), + ) + + op := simulation.SimulateMsgReportData( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgReportData + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal(types.RequestID(1), msg.RequestID) + suite.Require().Equal(3, len(msg.RawReports)) + suite.Require().Equal("bandvaloper1tnh2q55v8wyygtt9srz5safamzdengsn4qqe0j", msg.Validator) + suite.Require().Equal(types.TypeMsgReportData, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgCreateDataSource tests the normal scenario of a valid message of type TypeMsgCreateDataSource +func (suite *SimTestSuite) TestSimulateMsgCreateDataSource() { + op := simulation.SimulateMsgCreateDataSource( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgCreateDataSource + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Sender) + suite.Require().Equal("OygZsTxPjf", msg.Name) + suite.Require(). + Equal("lDameIuqVAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdI", msg.Description) + suite.Require(). + Equal("4f6a4346754976547968584b4c79685553634f587659746852587050664b774d68707458617849786771426f55717a725762616f4c545670516f6f74745a795046664e4f6f4d696f5848527546774d525955694b766357506b72617979544c4f43464a6c", hex.EncodeToString(msg.Executable)) + suite.Require().Equal(sdk.Coins(nil), msg.Fee) + suite.Require().Equal("band13rmqzzysyz4qh3yg6rvknd6u9rvrd98qvy9azu", msg.Treasury) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Owner) + suite.Require().Equal(types.TypeMsgCreateDataSource, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgEditDataSource tests the normal scenario of a valid message of type TypeMsgEditDataSource +func (suite *SimTestSuite) TestSimulateMsgEditDataSource() { + suite.app.OracleKeeper.SetDataSource( + suite.ctx, + 1, + types.NewDataSource( + suite.accs[0].Address, + "name", + "description", + "filename", + sdk.NewCoins(), + suite.accs[0].Address, + ), + ) + + op := simulation.SimulateMsgEditDataSource( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgEditDataSource + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal(types.DataSourceID(1), msg.DataSourceID) + suite.Require().Equal("band1tnh2q55v8wyygtt9srz5safamzdengsneky62e", msg.Sender) + suite.Require().Equal("PjfweXhSUk", msg.Name) + suite.Require(). + Equal("VAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjO", msg.Description) + suite.Require(). + Equal("7968584b4c79685553634f587659746852587050664b774d68707458617849786771426f55717a725762616f4c545670516f6f74745a795046664e4f6f4d696f5848527546774d525955694b766357506b72617979544c4f43464a6c4179736c44616d65", hex.EncodeToString(msg.Executable)) + suite.Require().Equal(sdk.Coins(nil), msg.Fee) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Treasury) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Owner) + suite.Require().Equal(types.TypeMsgEditDataSource, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgCreateOracleScript tests the normal scenario of a valid message of type TypeMsgCreateOracleScript +func (suite *SimTestSuite) TestSimulateMsgCreateOracleScript() { + op := simulation.SimulateMsgCreateOracleScript( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgCreateOracleScript + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Sender) + suite.Require().Equal("PjfweXhSUk", msg.Name) + suite.Require(). + Equal("VAuxErqFPEWIScKpBORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjO", msg.Description) + suite.Require(). + Equal("yhXKLyhUScOXvYthRXpPfKwMhptXaxIxgqBoUqzrWbaoLTVpQoottZyPFfNOoMioXHRuFwMRYUiKvcWPkrayyTLOCFJlAyslDame", msg.Schema) + suite.Require(). + Equal("nDQfwRLGIWozYaOAilMBcObErwgTDNGWnwQMUgFFSKtPDMEoEQCTKVREqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvT", msg.SourceCodeURL) + suite.Require(). + Equal("0061736d0100000001100360000060047e7e7e7e0060027e7e00022f0203656e761161736b5f65787465726e616c5f64617461000103656e760f7365745f72657475726e5f6461746100020303020000040501700101010503010011071e030770726570617265000207657865637574650003066d656d6f727902000a4e022601017e42014201418008ad22004204100042024202200042041000420342032000420410000b2501017f4100210002400340200041016a2100200041e400490d000b0b418008ad420410010b0b0b01004180080b0462656562", hex.EncodeToString(msg.Code)) + suite.Require().Equal("band1n5sqxutsmk6eews5z9z673wv7n9wah8hjlxyuf", msg.Owner) + suite.Require().Equal(types.TypeMsgCreateOracleScript, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgEditOracleScript tests the normal scenario of a valid message of type TypeMsgEditOracleScript +func (suite *SimTestSuite) TestSimulateMsgEditOracleScript() { + suite.app.OracleKeeper.SetOracleScript( + suite.ctx, + 1, + types.NewOracleScript( + suite.accs[0].Address, + "name", + "description", + "filename", + "schema", + "sourceCodeURL", + ), + ) + + op := simulation.SimulateMsgEditOracleScript( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgEditOracleScript + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal(types.OracleScriptID(1), msg.OracleScriptID) + suite.Require().Equal("band1tnh2q55v8wyygtt9srz5safamzdengsneky62e", msg.Sender) + suite.Require().Equal("MaxKlMIJMO", msg.Name) + suite.Require(). + Equal("BORIuZqoXlZuTvAjEdlEWDODFRregDTqGNoFBIHxvimmIZwLfFyKUfEWAnNBdtdzDmTPXtpHRGdIbuucfTjOygZsTxPjfweXhSUk", msg.Description) + suite.Require(). + Equal("YthRXpPfKwMhptXaxIxgqBoUqzrWbaoLTVpQoottZyPFfNOoMioXHRuFwMRYUiKvcWPkrayyTLOCFJlAyslDameIuqVAuxErqFPE", msg.Schema) + suite.Require(). + Equal("WozYaOAilMBcObErwgTDNGWnwQMUgFFSKtPDMEoEQCTKVREqrXZSGLqwTMcxHfWotDllNkIJPMbXzjDVjPOOjCFuIvTyhXKLyhUS", msg.SourceCodeURL) + suite.Require(). + Equal("0061736d0100000001100360000060047e7e7e7e0060027e7e00022f0203656e761161736b5f65787465726e616c5f64617461000103656e760f7365745f72657475726e5f6461746100020303020000040501700101010503010011071e030770726570617265000207657865637574650003066d656d6f727902000a4e022601017e42014201418008ad22004204100042024202200042041000420342032000420410000b2501017f4100210002400340200041016a2100200041e400490d000b0b418008ad420410010b0b0b01004180080b0462656562", hex.EncodeToString(msg.Code)) + suite.Require().Equal("band1tnh2q55v8wyygtt9srz5safamzdengsneky62e", msg.Owner) + suite.Require().Equal(types.TypeMsgEditOracleScript, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +// TestSimulateMsgActivate tests the normal scenario of a valid message of type TypeMsgActivate +func (suite *SimTestSuite) TestSimulateMsgActivate() { + op := simulation.SimulateMsgActivate( + suite.app.AccountKeeper, + suite.app.BankKeeper, + suite.app.StakingKeeper, + suite.app.OracleKeeper, + ) + operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") + suite.Require().NoError(err) + + var msg types.MsgActivate + err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) + suite.Require().NoError(err) + + suite.Require().True(operationMsg.OK) + suite.Require().Equal("bandvaloper1n5sqxutsmk6eews5z9z673wv7n9wah8h7fz8ez", msg.Validator) + suite.Require().Equal(types.TypeMsgActivate, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + +func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := suite.app.StakingKeeper.TokensFromConsensusPower(suite.ctx, 200) + initCoins := sdk.NewCoins(sdk.NewCoin("uband", initAmt)) + + // add coins to the accounts + for _, account := range accounts { + acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, account.Address) + suite.app.AccountKeeper.SetAccount(suite.ctx, acc) + suite.Require().NoError(testutil.FundAccount(suite.app.BankKeeper, suite.ctx, account.Address, initCoins)) + } + + return accounts +} + +func TestSimTestSuite(t *testing.T) { + suite.Run(t, new(SimTestSuite)) +} From 278fb32498fedbdd10de4e0a3a6b220c541c77db Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 16:52:30 +0700 Subject: [PATCH 02/13] add github action --- .github/workflows/simulation.yml | 91 ++++++ app/sim_test.go | 510 ++++++++++++++++++++++++++++++ x/oracle/simulation/operations.go | 4 - 3 files changed, 601 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/simulation.yml create mode 100644 app/sim_test.go diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml new file mode 100644 index 000000000..e0edfedbf --- /dev/null +++ b/.github/workflows/simulation.yml @@ -0,0 +1,91 @@ +name: Simulation +on: pull_request + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + check-latest: true + - run: make build + - name: Install runsim + run: go install github.com/cosmos/tools/cmd/runsim@v1.0.0 + - uses: actions/cache@v3 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + + test-sim-import-export: + runs-on: ubuntu-latest + needs: [build] + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + check-latest: true + - uses: actions/cache@v3 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + - name: test-sim-import-export + run: | + make test-sim-import-export + + test-sim-after-import: + runs-on: ubuntu-latest + needs: [build] + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + check-latest: true + - uses: actions/cache@v3 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + - name: test-sim-after-import + run: | + make test-sim-after-import + + test-sim-multi-seed-short: + runs-on: ubuntu-latest + needs: [build] + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + check-latest: true + - uses: actions/cache@v3 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + - name: test-sim-multi-seed-short + run: | + make test-sim-multi-seed-short + + test-sim-deterministic: + runs-on: ubuntu-latest + needs: [build] + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + check-latest: true + - uses: actions/cache@v3 + with: + path: ~/go/bin + key: ${{ runner.os }}-go-runsim-binary + - name: test-sim-deterministic + run: | + make test-sim-deterministic diff --git a/app/sim_test.go b/app/sim_test.go new file mode 100644 index 000000000..372ad08f4 --- /dev/null +++ b/app/sim_test.go @@ -0,0 +1,510 @@ +package band + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "runtime/debug" + "strings" + "testing" + + dbm "github.com/cometbft/cometbft-db" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + + oracletypes "github.com/bandprotocol/chain/v2/x/oracle/types" +) + +// BandAppChainID hardcoded chainID for simulation +const BandAppChainID = "simulation-app" + +// Get flags every time the simulator is run +func init() { + simcli.GetSimulatorFlags() +} + +type StoreKeysPrefixes struct { + A storetypes.StoreKey + B storetypes.StoreKey + Prefixes [][]byte +} + +// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of +// an IAVLStore for faster simulation speed. +func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { + bapp.SetFauxMerkleMode() +} + +// interBlockCacheOpt returns a BaseApp option function that sets the persistent +// inter-block write-through cache. +func interBlockCacheOpt() func(*baseapp.BaseApp) { + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) +} + +func TestFullAppSimulation(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = BandAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation( + config, + "leveldb-app-sim", + "Simulation", + simcli.FlagVerboseValue, + simcli.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + app := NewBandApp( + logger, + db, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + fauxMerkleModeOpt, + baseapp.SetChainID(BandAppChainID), + ) + require.Equal(t, "BandApp", app.Name()) + + // run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), NewDefaultGenesisState()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } +} + +func TestAppImportExport(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = BandAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation( + config, + "leveldb-app-sim", + "Simulation", + simcli.FlagVerboseValue, + simcli.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application import/export simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + app := NewBandApp( + logger, + db, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + fauxMerkleModeOpt, + baseapp.SetChainID(BandAppChainID), + ) + require.Equal(t, "BandApp", app.Name()) + + // Run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), NewDefaultGenesisState()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } + + fmt.Printf("exporting genesis...\n") + + exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + newDB, newDir, _, _, err := simtestutil.SetupSimulation( + config, + "leveldb-app-sim-2", + "Simulation-2", + simcli.FlagVerboseValue, + simcli.FlagEnabledValue, + ) + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, newDB.Close()) + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := NewBandApp( + log.NewNopLogger(), + newDB, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + fauxMerkleModeOpt, + baseapp.SetChainID(BandAppChainID), + ) + require.Equal(t, "BandApp", newApp.Name()) + + var genesisState GenesisState + err = json.Unmarshal(exported.AppState, &genesisState) + require.NoError(t, err) + + defer func() { + if r := recover(); r != nil { + err := fmt.Sprintf("%v", r) + if !strings.Contains(err, "validator set is empty after InitGenesis") { + panic(r) + } + logger.Info("Skipping simulation as all validators have been unbonded") + logger.Info("err", err, "stacktrace", string(debug.Stack())) + } + }() + + ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) + newApp.mm.InitGenesis(ctxB, app.AppCodec(), genesisState) + newApp.StoreConsensusParams(ctxB, exported.ConsensusParams) + + fmt.Printf("comparing stores...\n") + + storeKeysPrefixes := []StoreKeysPrefixes{ + {app.GetKey(authtypes.StoreKey), newApp.GetKey(authtypes.StoreKey), [][]byte{}}, + { + app.GetKey(stakingtypes.StoreKey), newApp.GetKey(stakingtypes.StoreKey), + [][]byte{ + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, + stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, + }, + }, // ordering may change but it doesn't matter + {app.GetKey(slashingtypes.StoreKey), newApp.GetKey(slashingtypes.StoreKey), [][]byte{}}, + {app.GetKey(minttypes.StoreKey), newApp.GetKey(minttypes.StoreKey), [][]byte{}}, + {app.GetKey(distrtypes.StoreKey), newApp.GetKey(distrtypes.StoreKey), [][]byte{}}, + {app.GetKey(banktypes.StoreKey), newApp.GetKey(banktypes.StoreKey), [][]byte{banktypes.BalancesPrefix}}, + {app.GetKey(paramtypes.StoreKey), newApp.GetKey(paramtypes.StoreKey), [][]byte{}}, + {app.GetKey(govtypes.StoreKey), newApp.GetKey(govtypes.StoreKey), [][]byte{}}, + {app.GetKey(evidencetypes.StoreKey), newApp.GetKey(evidencetypes.StoreKey), [][]byte{}}, + {app.GetKey(capabilitytypes.StoreKey), newApp.GetKey(capabilitytypes.StoreKey), [][]byte{}}, + { + app.GetKey(authzkeeper.StoreKey), + newApp.GetKey(authzkeeper.StoreKey), + [][]byte{authzkeeper.GrantKey, authzkeeper.GrantQueuePrefix}, + }, + {app.GetKey(oracletypes.StoreKey), newApp.GetKey(oracletypes.StoreKey), [][]byte{ + oracletypes.RequestCountStoreKey, + oracletypes.RequestLastExpiredStoreKey, + oracletypes.PendingResolveListStoreKey, + oracletypes.RequestStoreKeyPrefix, + oracletypes.ReportStoreKeyPrefix, + oracletypes.ValidatorStatusKeyPrefix, + oracletypes.ResultStoreKeyPrefix, + }}, + } + + for _, skp := range storeKeysPrefixes { + storeA := ctxA.KVStore(skp.A) + storeB := ctxB.KVStore(skp.B) + + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal( + t, + 0, + len(failedKVAs), + simtestutil.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs), + ) + } +} + +func TestAppSimulationAfterImport(t *testing.T) { + config := simcli.NewConfigFromFlags() + config.ChainID = BandAppChainID + + db, dir, logger, skip, err := simtestutil.SetupSimulation( + config, + "leveldb-app-sim", + "Simulation", + simcli.FlagVerboseValue, + simcli.FlagEnabledValue, + ) + if skip { + t.Skip("skipping application simulation after import") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, db.Close()) + require.NoError(t, os.RemoveAll(dir)) + }() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + app := NewBandApp( + logger, + db, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + fauxMerkleModeOpt, + baseapp.SetChainID(BandAppChainID), + ) + require.Equal(t, "BandApp", app.Name()) + + // Run randomized simulation + stopEarly, simParams, simErr := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), NewDefaultGenesisState()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + + // export state and simParams before the simulation error is checked + err = simtestutil.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simtestutil.PrintStats(db) + } + + if stopEarly { + fmt.Println("can't export or import a zero-validator genesis, exiting test...") + return + } + + fmt.Printf("exporting genesis...\n") + + exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{}) + require.NoError(t, err) + + fmt.Printf("importing genesis...\n") + + newDB, newDir, _, _, err := simtestutil.SetupSimulation( + config, + "leveldb-app-sim-2", + "Simulation-2", + simcli.FlagVerboseValue, + simcli.FlagEnabledValue, + ) + require.NoError(t, err, "simulation setup failed") + + defer func() { + require.NoError(t, newDB.Close()) + require.NoError(t, os.RemoveAll(newDir)) + }() + + newApp := NewBandApp( + log.NewNopLogger(), + newDB, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + fauxMerkleModeOpt, + baseapp.SetChainID(BandAppChainID), + ) + + require.Equal(t, "BandApp", newApp.Name()) + + newApp.InitChain(abci.RequestInitChain{ + ChainId: BandAppChainID, + AppStateBytes: exported.AppState, + }) + + _, _, err = simulation.SimulateFromSeed( + t, + os.Stdout, + newApp.BaseApp, + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), NewDefaultGenesisState()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(newApp, newApp.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + require.NoError(t, err) +} + +// TODO: Make another test for the fuzzer itself, which just has noOp txs +// and doesn't depend on the application. +func TestAppStateDeterminism(t *testing.T) { + if !simcli.FlagEnabledValue { + t.Skip("skipping application simulation") + } + + config := simcli.NewConfigFromFlags() + config.InitialBlockHeight = 1 + config.ExportParamsPath = "" + config.OnOperation = false + config.AllInvariants = false + config.ChainID = BandAppChainID + + numSeeds := 3 + numTimesToRunPerSeed := 5 + + // We will be overriding the random seed and just run a single simulation on the provided seed value + if config.Seed != simcli.DefaultSeedValue { + numSeeds = 1 + } + + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue + + for i := 0; i < numSeeds; i++ { + if config.Seed == simcli.DefaultSeedValue { + config.Seed = rand.Int63() + } + + fmt.Println("config.Seed: ", config.Seed) + + for j := 0; j < numTimesToRunPerSeed; j++ { + var logger log.Logger + if simcli.FlagVerboseValue { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + + db := dbm.NewMemDB() + app := NewBandApp( + logger, + db, + nil, + true, + map[int64]bool{}, + appOptions, + 100, + interBlockCacheOpt(), + baseapp.SetChainID(BandAppChainID), + ) + require.Equal(t, "BandApp", app.Name()) + + fmt.Printf( + "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + ) + + _, _, err := simulation.SimulateFromSeed( + t, + os.Stdout, + app.BaseApp, + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), NewDefaultGenesisState()), + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 + simtestutil.SimulationOperations(app, app.AppCodec(), config), + app.ModuleAccountAddrs(), + config, + app.AppCodec(), + ) + require.NoError(t, err) + + if config.Commit { + simtestutil.PrintStats(db) + } + + appHash := app.LastCommitID().Hash + appHashList[j] = appHash + + if j != 0 { + require.Equal( + t, + string(appHashList[0]), + string(appHashList[j]), + "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, + i+1, + numSeeds, + j+1, + numTimesToRunPerSeed, + ) + } + } + } +} diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go index 791c16c61..05b84080f 100644 --- a/x/oracle/simulation/operations.go +++ b/x/oracle/simulation/operations.go @@ -1,7 +1,6 @@ package simulation import ( - "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" @@ -227,8 +226,6 @@ func SimulateMsgReportData( for i := uint64(1); i <= rCount; i++ { req, _ := keeper.GetRequest(ctx, types.RequestID(i)) - fmt.Printf("req %+v\n", req) - for _, val := range req.RequestedValidators { valAddr, _ := sdk.ValAddressFromBech32(val) acc, ok := simtypes.FindAccount(accs, sdk.AccAddress(valAddr)) @@ -246,7 +243,6 @@ func SimulateMsgReportData( } if rid == 0 { - fmt.Printf("as") return simtypes.NoOpMsg( types.ModuleName, types.MsgReportData{}.Type(), From e397f7c09095ffcb3543bf23fc569cf9a8579264 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 16:54:24 +0700 Subject: [PATCH 03/13] fix github action --- .github/workflows/simulation.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml index e0edfedbf..d48a0e05d 100644 --- a/.github/workflows/simulation.yml +++ b/.github/workflows/simulation.yml @@ -10,7 +10,6 @@ jobs: with: go-version: "1.19" check-latest: true - - run: make build - name: Install runsim run: go install github.com/cosmos/tools/cmd/runsim@v1.0.0 - uses: actions/cache@v3 From 39f68ea4d8191e870d1e40a5061d21ea4f002ceb Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 17:14:29 +0700 Subject: [PATCH 04/13] fix test --- contrib/devtools/Dockerfile | 8 ++++---- contrib/devtools/Makefile | 2 +- x/oracle/abci.go | 2 +- x/oracle/keeper/oracle_script_test.go | 5 +++-- x/oracle/keeper/owasm_test.go | 19 ++++++++++--------- x/oracle/simulation/genesis_test.go | 2 +- x/oracle/simulation/operations_test.go | 1 - 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/contrib/devtools/Dockerfile b/contrib/devtools/Dockerfile index 53592ff6a..5c7dd8278 100644 --- a/contrib/devtools/Dockerfile +++ b/contrib/devtools/Dockerfile @@ -23,9 +23,9 @@ ENV GOLANG_PROTOBUF_VERSION=1.28.1 \ GRPC_GATEWAY_VERSION=1.16.0 RUN go install github.com/cosmos/cosmos-proto/cmd/protoc-gen-go-pulsar@latest && \ - go install google.golang.org/protobuf/cmd/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} && \ - go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} \ - github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} + go install google.golang.org/protobuf/cmd/protoc-gen-go@v${GOLANG_PROTOBUF_VERSION} && \ + go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION} \ + github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v${GRPC_GATEWAY_VERSION} # install all gogo protobuf binaries RUN git clone https://github.com/cosmos/gogoproto.git; \ @@ -33,4 +33,4 @@ RUN git clone https://github.com/cosmos/gogoproto.git; \ go mod download; \ make install -COPY --from=BUILDER /usr/local/bin /usr/local/bin \ No newline at end of file +COPY --from=BUILDER /usr/local/bin /usr/local/bin diff --git a/contrib/devtools/Makefile b/contrib/devtools/Makefile index f8a5de4ed..dc9eb0c8e 100644 --- a/contrib/devtools/Makefile +++ b/contrib/devtools/Makefile @@ -73,4 +73,4 @@ tools-clean: rm -f $(STATIK) $(GOLANGCI_LINT) $(RUNSIM) rm -f tools-stamp -.PHONY: tools-clean statik runsim \ No newline at end of file +.PHONY: tools-clean statik runsim diff --git a/x/oracle/abci.go b/x/oracle/abci.go index 1074308d7..1bb4c1b1e 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -14,7 +14,7 @@ func handleBeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keep hash := req.GetHash() if len(hash) > 0 { rollingSeed := k.GetRollingSeed(ctx) - k.SetRollingSeed(ctx, append(rollingSeed[1:], req.GetHash()[0])) + k.SetRollingSeed(ctx, append(rollingSeed[1:], hash[0])) } // Reward a portion of block rewards (inflation + tx fee) to active oracle validators. k.AllocateTokens(ctx, req.LastCommitInfo.GetVotes()) diff --git a/x/oracle/keeper/oracle_script_test.go b/x/oracle/keeper/oracle_script_test.go index 80bdeaa0d..cd4322742 100644 --- a/x/oracle/keeper/oracle_script_test.go +++ b/x/oracle/keeper/oracle_script_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/bandprotocol/chain/v2/testing/testapp" + "github.com/bandprotocol/chain/v2/testing/testdata" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -143,10 +144,10 @@ func TestGetAllOracleScripts(t *testing.T) { func TestAddOracleScriptFile(t *testing.T) { _, _, k := testapp.CreateTestInput(true) // Code should be perferctly compilable. - compiledCode, err := testapp.OwasmVM.Compile(testapp.WasmExtra1, types.MaxCompiledWasmCodeSize) + compiledCode, err := testapp.OwasmVM.Compile(testdata.WasmExtra1, types.MaxCompiledWasmCodeSize) require.NoError(t, err) // We start by adding the Owasm content to the storage. - filename, err := k.AddOracleScriptFile(testapp.WasmExtra1) + filename, err := k.AddOracleScriptFile(testdata.WasmExtra1) require.NoError(t, err) // If we get by file name, we should get the compiled content back. require.Equal(t, compiledCode, k.GetFile(filename)) diff --git a/x/oracle/keeper/owasm_test.go b/x/oracle/keeper/owasm_test.go index 841aeef18..5a8438421 100644 --- a/x/oracle/keeper/owasm_test.go +++ b/x/oracle/keeper/owasm_test.go @@ -15,6 +15,7 @@ import ( "github.com/bandprotocol/chain/v2/pkg/obi" "github.com/bandprotocol/chain/v2/testing/testapp" + "github.com/bandprotocol/chain/v2/testing/testdata" "github.com/bandprotocol/chain/v2/x/oracle/keeper" "github.com/bandprotocol/chain/v2/x/oracle/types" ) @@ -586,7 +587,7 @@ func TestPrepareRequestWithEmptyRawRequest(t *testing.T) { func TestPrepareRequestUnknownDataSource(t *testing.T) { _, ctx, k := testapp.CreateTestInput(true) - m := types.NewMsgRequestData(4, obi.MustEncode(testapp.Wasm4Input{ + m := types.NewMsgRequestData(4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2, 99}, Calldata: "beeb", }), 1, 1, BasicClientID, testapp.Coins100000000uband, testapp.TestDefaultPrepareGas, testapp.TestDefaultExecuteGas, testapp.Alice.Address) @@ -599,13 +600,13 @@ func TestPrepareRequestInvalidDataSourceCount(t *testing.T) { params := k.GetParams(ctx) params.MaxRawRequestCount = 3 k.SetParams(ctx, params) - m := types.NewMsgRequestData(4, obi.MustEncode(testapp.Wasm4Input{ + m := types.NewMsgRequestData(4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2, 3, 4}, Calldata: "beeb", }), 1, 1, BasicClientID, testapp.Coins100000000uband, testapp.TestDefaultPrepareGas, testapp.TestDefaultExecuteGas, testapp.Alice.Address) _, err := k.PrepareRequest(ctx, m, testapp.FeePayer.Address, nil) require.ErrorIs(t, err, types.ErrBadWasmExecution) - m = types.NewMsgRequestData(4, obi.MustEncode(testapp.Wasm4Input{ + m = types.NewMsgRequestData(4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2, 3}, Calldata: "beeb", }), 1, 1, BasicClientID, testapp.Coins100000000uband, testapp.TestDefaultPrepareGas, testapp.TestDefaultExecuteGas, testapp.Alice.Address) @@ -714,7 +715,7 @@ func TestResolveRequestSuccessComplex(t *testing.T) { ctx = ctx.WithBlockTime(testapp.ParseTime(1581589890)) k.SetRequest(ctx, 42, types.NewRequest( // 4th Wasm. Append all reports from all validators. - 4, obi.MustEncode(testapp.Wasm4Input{ + 4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2}, Calldata: string(BasicCalldata), }), []sdk.ValAddress{testapp.Validators[0].ValAddress, testapp.Validators[1].ValAddress}, 1, @@ -737,13 +738,13 @@ func TestResolveRequestSuccessComplex(t *testing.T) { )) k.ResolveRequest(ctx, 42) result := types.NewResult( - BasicClientID, 4, obi.MustEncode(testapp.Wasm4Input{ + BasicClientID, 4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2}, Calldata: string(BasicCalldata), }), 2, 1, 42, 2, testapp.ParseTime(1581589790).Unix(), testapp.ParseTime(1581589890).Unix(), types.RESOLVE_STATUS_SUCCESS, - obi.MustEncode(testapp.Wasm4Output{Ret: "beebd1v1beebd1v2beebd2v1beebd2v2"}), + obi.MustEncode(testdata.Wasm4Output{Ret: "beebd1v1beebd1v2beebd2v1beebd2v2"}), ) require.Equal(t, result, k.MustGetResult(ctx, 42)) require.Equal(t, sdk.Events{sdk.NewEvent( @@ -787,7 +788,7 @@ func TestResolveReadNilExternalData(t *testing.T) { ctx = ctx.WithBlockTime(testapp.ParseTime(1581589890)) k.SetRequest(ctx, 42, types.NewRequest( // 4th Wasm. Append all reports from all validators. - 4, obi.MustEncode(testapp.Wasm4Input{ + 4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2}, Calldata: string(BasicCalldata), }), []sdk.ValAddress{testapp.Validators[0].ValAddress, testapp.Validators[1].ValAddress}, 1, @@ -810,13 +811,13 @@ func TestResolveReadNilExternalData(t *testing.T) { )) k.ResolveRequest(ctx, 42) result := types.NewResult( - BasicClientID, 4, obi.MustEncode(testapp.Wasm4Input{ + BasicClientID, 4, obi.MustEncode(testdata.Wasm4Input{ IDs: []int64{1, 2}, Calldata: string(BasicCalldata), }), 2, 1, 42, 2, testapp.ParseTime(1581589790).Unix(), testapp.ParseTime(1581589890).Unix(), types.RESOLVE_STATUS_SUCCESS, - obi.MustEncode(testapp.Wasm4Output{Ret: "beebd1v2beebd2v1"}), + obi.MustEncode(testdata.Wasm4Output{Ret: "beebd1v2beebd2v1"}), ) require.Equal(t, result, k.MustGetResult(ctx, 42)) require.Equal(t, sdk.Events{sdk.NewEvent( diff --git a/x/oracle/simulation/genesis_test.go b/x/oracle/simulation/genesis_test.go index 9654586f1..af5a96c45 100644 --- a/x/oracle/simulation/genesis_test.go +++ b/x/oracle/simulation/genesis_test.go @@ -40,7 +40,7 @@ func TestRandomizedGenState(t *testing.T) { simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &oracleGenesis) require.Equal(t, uint64(18), oracleGenesis.Params.MaxRawRequestCount) - require.Equal(t, uint64(12), oracleGenesis.Params.MaxAskCount) + require.Equal(t, uint64(26), oracleGenesis.Params.MaxAskCount) require.Equal(t, uint64(700), oracleGenesis.Params.MaxCalldataSize) require.Equal(t, uint64(294), oracleGenesis.Params.MaxReportDataSize) require.Equal(t, uint64(791), oracleGenesis.Params.ExpirationBlockCount) diff --git a/x/oracle/simulation/operations_test.go b/x/oracle/simulation/operations_test.go index a19b0953e..b41729630 100644 --- a/x/oracle/simulation/operations_test.go +++ b/x/oracle/simulation/operations_test.go @@ -45,7 +45,6 @@ func (suite *SimTestSuite) SetupTest() { }, }, ) - } // TestWeightedOperations tests the weights of the operations. From ca4dfede64594faa3bdd1ebd83ba9cf9687e024b Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 17:56:10 +0700 Subject: [PATCH 05/13] add wat2wasm in sim --- .github/workflows/simulation.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml index d48a0e05d..6c389d0c3 100644 --- a/.github/workflows/simulation.yml +++ b/.github/workflows/simulation.yml @@ -10,6 +10,11 @@ jobs: with: go-version: "1.19" check-latest: true + - name: Install Wabt (wat2wasm) + run: | + wget https://github.com/WebAssembly/wabt/releases/download/1.0.17/wabt-1.0.17-ubuntu.tar.gz + tar -zxf wabt-1.0.17-ubuntu.tar.gz + sudo cp wabt-1.0.17/bin/wat2wasm /usr/local/bin - name: Install runsim run: go install github.com/cosmos/tools/cmd/runsim@v1.0.0 - uses: actions/cache@v3 From 714025995280efb341b2013ac91e050befa6b974 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sat, 12 Aug 2023 18:19:21 +0700 Subject: [PATCH 06/13] add wat2wasm to go binary --- .github/workflows/simulation.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml index 6c389d0c3..56fdadb46 100644 --- a/.github/workflows/simulation.yml +++ b/.github/workflows/simulation.yml @@ -14,13 +14,13 @@ jobs: run: | wget https://github.com/WebAssembly/wabt/releases/download/1.0.17/wabt-1.0.17-ubuntu.tar.gz tar -zxf wabt-1.0.17-ubuntu.tar.gz - sudo cp wabt-1.0.17/bin/wat2wasm /usr/local/bin + sudo cp wabt-1.0.17/bin/wat2wasm ~/go/bin - name: Install runsim run: go install github.com/cosmos/tools/cmd/runsim@v1.0.0 - uses: actions/cache@v3 with: path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary + key: ${{ runner.os }}-go-binary test-sim-import-export: runs-on: ubuntu-latest @@ -35,7 +35,7 @@ jobs: - uses: actions/cache@v3 with: path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary + key: ${{ runner.os }}-go-binary - name: test-sim-import-export run: | make test-sim-import-export @@ -53,7 +53,7 @@ jobs: - uses: actions/cache@v3 with: path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary + key: ${{ runner.os }}-go-binary - name: test-sim-after-import run: | make test-sim-after-import @@ -71,7 +71,7 @@ jobs: - uses: actions/cache@v3 with: path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary + key: ${{ runner.os }}-go-binary - name: test-sim-multi-seed-short run: | make test-sim-multi-seed-short @@ -89,7 +89,7 @@ jobs: - uses: actions/cache@v3 with: path: ~/go/bin - key: ${{ runner.os }}-go-runsim-binary + key: ${{ runner.os }}-go-binary - name: test-sim-deterministic run: | make test-sim-deterministic From 9dd262ea869bcff9b41fadb81f8a444617cba035 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Fri, 25 Aug 2023 13:49:29 +0700 Subject: [PATCH 07/13] adjust timeout --- .github/workflows/simulation.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml index 56fdadb46..c0af94ebf 100644 --- a/.github/workflows/simulation.yml +++ b/.github/workflows/simulation.yml @@ -25,7 +25,7 @@ jobs: test-sim-import-export: runs-on: ubuntu-latest needs: [build] - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 @@ -43,7 +43,7 @@ jobs: test-sim-after-import: runs-on: ubuntu-latest needs: [build] - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 @@ -61,7 +61,7 @@ jobs: test-sim-multi-seed-short: runs-on: ubuntu-latest needs: [build] - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 @@ -79,7 +79,7 @@ jobs: test-sim-deterministic: runs-on: ubuntu-latest needs: [build] - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 From 681045ba5ef419a4cd4bb542d3ac1f97e4c20732 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Mon, 4 Sep 2023 11:03:23 +0700 Subject: [PATCH 08/13] add comment --- x/oracle/simulation/operations.go | 32 ++++++++++++++++---------- x/oracle/simulation/operations_test.go | 19 +++++++++++++++ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go index 05b84080f..a147ec47c 100644 --- a/x/oracle/simulation/operations.go +++ b/x/oracle/simulation/operations.go @@ -143,6 +143,7 @@ func SimulateMsgRequestData( return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { simAccount, _ := simtypes.RandomAcc(r, accs) + // Get deployed oracle script from one of random accounts for sending request to. oCount := keeper.GetOracleScriptCount(ctx) oid := types.OracleScriptID(0) for i := uint64(1); i <= oCount; i++ { @@ -161,6 +162,8 @@ func SimulateMsgRequestData( ), nil, nil } + // Check if the number of available data sources is more than 3 + // As our test oracle script requires at least 3 data sources for getting result. did := keeper.GetDataSourceCount(ctx) if did < 3 { return simtypes.NoOpMsg( @@ -170,6 +173,7 @@ func SimulateMsgRequestData( ), nil, nil } + // Find the number of active validator to define ask count value maxAskCount := 0 sk.IterateBondedValidatorsByPower(ctx, func(idx int64, val stakingtypes.ValidatorI) (stop bool) { @@ -187,12 +191,12 @@ func SimulateMsgRequestData( "active validators are not enough", ), nil, nil } - if maxAskCount > 10 { maxAskCount = 10 } askCount := simtypes.RandIntBetween(r, 1, maxAskCount+1) + // Generate request message from above information msg := types.MsgRequestData{ Sender: simAccount.Address.String(), OracleScriptID: types.OracleScriptID(oid), @@ -221,11 +225,13 @@ func SimulateMsgReportData( return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { var simAccount simtypes.Account + // Get available request that we will send report to rCount := keeper.GetRequestCount(ctx) rid := types.RequestID(0) for i := uint64(1); i <= rCount; i++ { req, _ := keeper.GetRequest(ctx, types.RequestID(i)) + // Make sure if our account is assigned on that request and we didn't report it yet for _, val := range req.RequestedValidators { valAddr, _ := sdk.ValAddressFromBech32(val) acc, ok := simtypes.FindAccount(accs, sdk.AccAddress(valAddr)) @@ -250,6 +256,7 @@ func SimulateMsgReportData( ), nil, nil } + // Generate raw report that we will report var rawReports []types.RawReport for i := 1; i <= 3; i++ { rawReports = append(rawReports, types.RawReport{ @@ -259,6 +266,7 @@ func SimulateMsgReportData( }) } + // Generate report message msg := types.MsgReportData{ RequestID: types.RequestID(rid), RawReports: rawReports, @@ -283,6 +291,7 @@ func SimulateMsgCreateDataSource( ownerAccount, _ := simtypes.RandomAcc(r, accs) treaAccount, _ := simtypes.RandomAcc(r, accs) + // Generate create data source message msg := types.MsgCreateDataSource{ Sender: simAccount.Address.String(), Name: simtypes.RandStringOfLength(r, 10), @@ -307,15 +316,18 @@ func SimulateMsgEditDataSource( keeper keeper.Keeper, ) simtypes.Operation { return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + var simAccount simtypes.Account ownerAccount, _ := simtypes.RandomAcc(r, accs) treaAccount, _ := simtypes.RandomAcc(r, accs) + // Get available data source that is owned by our account dCount := keeper.GetDataSourceCount(ctx) did := types.DataSourceID(0) for i := uint64(1); i <= dCount; i++ { os, _ := keeper.GetDataSource(ctx, types.DataSourceID(i)) - _, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(os.Owner)) + acc, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(os.Owner)) if ok { + simAccount = acc did = types.DataSourceID(i) break } @@ -329,16 +341,7 @@ func SimulateMsgEditDataSource( ), nil, nil } - ds, _ := keeper.GetDataSource(ctx, types.DataSourceID(did)) - simAccount, ok := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(ds.Owner)) - if !ok { - return simtypes.NoOpMsg( - types.ModuleName, - types.MsgEditDataSource{}.Type(), - "unknown owner", - ), nil, nil - } - + // Generate edit data source message msg := types.MsgEditDataSource{ Sender: simAccount.Address.String(), DataSourceID: types.DataSourceID(did), @@ -367,6 +370,7 @@ func SimulateMsgCreateOracleScript( simAccount, _ := simtypes.RandomAcc(r, accs) ownerAccount, _ := simtypes.RandomAcc(r, accs) + // Generate create oracle script message msg := types.MsgCreateOracleScript{ Sender: simAccount.Address.String(), Name: simtypes.RandStringOfLength(r, 10), @@ -393,6 +397,7 @@ func SimulateMsgEditOracleScript( return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { var simAccount simtypes.Account + // Get available oracle script that we will edit it oCount := keeper.GetOracleScriptCount(ctx) oid := types.OracleScriptID(0) for i := uint64(1); i <= oCount; i++ { @@ -413,6 +418,7 @@ func SimulateMsgEditOracleScript( ), nil, nil } + // Generate edit oracle script message msg := types.MsgEditOracleScript{ Sender: simAccount.Address.String(), OracleScriptID: types.OracleScriptID(oid), @@ -440,6 +446,7 @@ func SimulateMsgActivate( return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { simAccount, _ := simtypes.RandomAcc(r, accs) + // Send no op message if the status of the account is already active if keeper.GetValidatorStatus(ctx, sdk.ValAddress(simAccount.Address)).IsActive { return simtypes.NoOpMsg( types.ModuleName, @@ -448,6 +455,7 @@ func SimulateMsgActivate( ), nil, nil } + // Generate activate message for the account msg := types.MsgActivate{ Validator: sdk.ValAddress(simAccount.Address).String(), } diff --git a/x/oracle/simulation/operations_test.go b/x/oracle/simulation/operations_test.go index b41729630..13559c4a5 100644 --- a/x/oracle/simulation/operations_test.go +++ b/x/oracle/simulation/operations_test.go @@ -88,13 +88,16 @@ func (suite *SimTestSuite) TestWeightedOperations() { // TestSimulateMsgRequestData tests the normal scenario of a valid message of type TypeMsgRequestData func (suite *SimTestSuite) TestSimulateMsgRequestData() { + // Prepare oracle script for request suite.TestSimulateMsgCreateOracleScript() + // Prepare data sources for request for i := 1; i <= 3; i++ { ds, _ := suite.app.OracleKeeper.GetDataSource(suite.ctx, types.DataSourceID(i)) ds.Fee = sdk.NewCoins() suite.app.OracleKeeper.SetDataSource(suite.ctx, types.DataSourceID(i), ds) } + // Simulate MsgRequestData op := simulation.SimulateMsgRequestData( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -104,6 +107,7 @@ func (suite *SimTestSuite) TestSimulateMsgRequestData() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgRequestData err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -127,6 +131,7 @@ func (suite *SimTestSuite) TestSimulateMsgRequestData() { // TestSimulateMsgReportData tests the normal scenario of a valid message of type TypeMsgReportData func (suite *SimTestSuite) TestSimulateMsgReportData() { + // Prepare request that we will simulate to send report to suite.app.OracleKeeper.AddRequest( suite.ctx, types.NewRequest(types.OracleScriptID(1), @@ -146,6 +151,7 @@ func (suite *SimTestSuite) TestSimulateMsgReportData() { ), ) + // Simulate MsgReportData op := simulation.SimulateMsgReportData( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -155,6 +161,7 @@ func (suite *SimTestSuite) TestSimulateMsgReportData() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgReportData err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -170,6 +177,7 @@ func (suite *SimTestSuite) TestSimulateMsgReportData() { // TestSimulateMsgCreateDataSource tests the normal scenario of a valid message of type TypeMsgCreateDataSource func (suite *SimTestSuite) TestSimulateMsgCreateDataSource() { + // Simulate MsgCreateDataSource op := simulation.SimulateMsgCreateDataSource( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -179,6 +187,7 @@ func (suite *SimTestSuite) TestSimulateMsgCreateDataSource() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgCreateDataSource err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -200,6 +209,7 @@ func (suite *SimTestSuite) TestSimulateMsgCreateDataSource() { // TestSimulateMsgEditDataSource tests the normal scenario of a valid message of type TypeMsgEditDataSource func (suite *SimTestSuite) TestSimulateMsgEditDataSource() { + // Prepare data source for us to edit by message suite.app.OracleKeeper.SetDataSource( suite.ctx, 1, @@ -213,6 +223,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditDataSource() { ), ) + // Simulate MsgEditDataSource op := simulation.SimulateMsgEditDataSource( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -222,6 +233,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditDataSource() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgEditDataSource err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -244,6 +256,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditDataSource() { // TestSimulateMsgCreateOracleScript tests the normal scenario of a valid message of type TypeMsgCreateOracleScript func (suite *SimTestSuite) TestSimulateMsgCreateOracleScript() { + // Simulate MsgCreateOracleScript op := simulation.SimulateMsgCreateOracleScript( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -253,6 +266,7 @@ func (suite *SimTestSuite) TestSimulateMsgCreateOracleScript() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgCreateOracleScript err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -276,6 +290,7 @@ func (suite *SimTestSuite) TestSimulateMsgCreateOracleScript() { // TestSimulateMsgEditOracleScript tests the normal scenario of a valid message of type TypeMsgEditOracleScript func (suite *SimTestSuite) TestSimulateMsgEditOracleScript() { + // Prepare oracle script for us to edit by message suite.app.OracleKeeper.SetOracleScript( suite.ctx, 1, @@ -289,6 +304,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditOracleScript() { ), ) + // Simulate MSgEditOracleScript op := simulation.SimulateMsgEditOracleScript( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -298,6 +314,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditOracleScript() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgEditOracleScript err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) @@ -322,6 +339,7 @@ func (suite *SimTestSuite) TestSimulateMsgEditOracleScript() { // TestSimulateMsgActivate tests the normal scenario of a valid message of type TypeMsgActivate func (suite *SimTestSuite) TestSimulateMsgActivate() { + // Simulate MsgActivate op := simulation.SimulateMsgActivate( suite.app.AccountKeeper, suite.app.BankKeeper, @@ -331,6 +349,7 @@ func (suite *SimTestSuite) TestSimulateMsgActivate() { operationMsg, futureOperations, err := op(suite.r, suite.app.BaseApp, suite.ctx, suite.accs, "") suite.Require().NoError(err) + // Verify the fields of the message var msg types.MsgActivate err = types.AminoCdc.UnmarshalJSON(operationMsg.Msg, &msg) suite.Require().NoError(err) From b6364abbe90220beb1d6cfd660bfa00907f7638a Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Mon, 25 Sep 2023 13:20:44 +0700 Subject: [PATCH 09/13] use chain id as const string --- testing/testapp/setup.go | 5 +++-- x/oracle/simulation/operations_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/testing/testapp/setup.go b/testing/testapp/setup.go index 6035a41b7..5f02e6421 100644 --- a/testing/testapp/setup.go +++ b/testing/testapp/setup.go @@ -86,6 +86,7 @@ var ( ) const ( + ChainID string = "BANDCHAIN" TestDefaultPrepareGas uint64 = 40000 TestDefaultExecuteGas uint64 = 300000 ) @@ -386,7 +387,7 @@ func NewTestApp(chainID string, logger log.Logger) *TestingApp { // CreateTestInput creates a new test environment for unit tests. func CreateTestInput(autoActivate bool) (*TestingApp, sdk.Context, keeper.Keeper) { - app := NewTestApp("BANDCHAIN", log.NewNopLogger()) + app := NewTestApp(ChainID, log.NewNopLogger()) ctx := app.NewContext(false, tmproto.Header{Height: app.LastBlockHeight()}) if autoActivate { app.OracleKeeper.Activate(ctx, Validators[0].ValAddress) @@ -443,7 +444,7 @@ func setup(withGenesis bool, invCheckPeriod uint, chainID string) (*TestingApp, // SetupWithEmptyStore setup a TestingApp instance with empty DB func SetupWithEmptyStore() *TestingApp { - app, _, _ := setup(false, 0, "BANDCHAIN") + app, _, _ := setup(false, 0, ChainID) return app } diff --git a/x/oracle/simulation/operations_test.go b/x/oracle/simulation/operations_test.go index 13559c4a5..0dfd5cd1a 100644 --- a/x/oracle/simulation/operations_test.go +++ b/x/oracle/simulation/operations_test.go @@ -30,7 +30,7 @@ type SimTestSuite struct { func (suite *SimTestSuite) SetupTest() { app, _, _ := testapp.CreateTestInput(true) suite.app = app - suite.ctx = app.BaseApp.NewContext(false, tmproto.Header{ChainID: "BANDCHAIN"}) + suite.ctx = app.BaseApp.NewContext(false, tmproto.Header{ChainID: testapp.ChainID}) s := rand.NewSource(1) suite.r = rand.New(s) suite.accs = suite.getTestingAccounts(suite.r, 10) @@ -39,7 +39,7 @@ func (suite *SimTestSuite) SetupTest() { suite.app.BeginBlock( abci.RequestBeginBlock{ Header: tmproto.Header{ - ChainID: "BANDCHAIN", + ChainID: testapp.ChainID, Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash, }, From 500e27baa4f0e9d2a130ec58f55969390cb542b2 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 29 Nov 2023 13:17:19 +0700 Subject: [PATCH 10/13] remove unused --- x/oracle/simulation/genesis.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/oracle/simulation/genesis.go b/x/oracle/simulation/genesis.go index a90393e20..f4b36a151 100644 --- a/x/oracle/simulation/genesis.go +++ b/x/oracle/simulation/genesis.go @@ -1,7 +1,5 @@ package simulation -// DONTCOVER - import ( "encoding/json" "fmt" From e7e095a0a167909dce6e2e46482994ede7a7b725 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sun, 11 Feb 2024 21:59:32 +0700 Subject: [PATCH 11/13] add comment --- x/oracle/abci.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/oracle/abci.go b/x/oracle/abci.go index 1bb4c1b1e..6496a1bec 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -12,6 +12,7 @@ import ( func handleBeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { // Update rolling seed used for pseudorandom oracle provider selection. hash := req.GetHash() + // On the firsrt block in the test. it's possible to have empty hash. if len(hash) > 0 { rollingSeed := k.GetRollingSeed(ctx) k.SetRollingSeed(ctx, append(rollingSeed[1:], hash[0])) From c3ac2453687d251b6f8b6c246cf35d3ec0a6f1d3 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 14 Feb 2024 00:31:03 +0700 Subject: [PATCH 12/13] fix lint --- x/oracle/simulation/decoder.go | 3 ++- x/oracle/simulation/genesis_test.go | 6 +++--- x/oracle/simulation/operations.go | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x/oracle/simulation/decoder.go b/x/oracle/simulation/decoder.go index dd614cc42..761456e27 100644 --- a/x/oracle/simulation/decoder.go +++ b/x/oracle/simulation/decoder.go @@ -4,9 +4,10 @@ import ( "bytes" "fmt" - "github.com/bandprotocol/chain/v2/x/oracle/types" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/bandprotocol/chain/v2/x/oracle/types" ) // NewDecodeStore returns a decoder function closure that unmarshals the KVPair's diff --git a/x/oracle/simulation/genesis_test.go b/x/oracle/simulation/genesis_test.go index af5a96c45..aa00e6209 100644 --- a/x/oracle/simulation/genesis_test.go +++ b/x/oracle/simulation/genesis_test.go @@ -5,14 +5,13 @@ import ( "math/rand" "testing" - "github.com/stretchr/testify/require" - sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/types/module" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" "github.com/bandprotocol/chain/v2/x/oracle" "github.com/bandprotocol/chain/v2/x/oracle/simulation" @@ -77,6 +76,7 @@ func TestRandomizedGenState1(t *testing.T) { } for _, tt := range tests { - require.Panicsf(t, func() { simulation.RandomizedGenState(&tt.simState) }, tt.panicMsg) + temp := tt + require.Panicsf(t, func() { simulation.RandomizedGenState(&temp.simState) }, tt.panicMsg) } } diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go index a147ec47c..27110086d 100644 --- a/x/oracle/simulation/operations.go +++ b/x/oracle/simulation/operations.go @@ -199,7 +199,7 @@ func SimulateMsgRequestData( // Generate request message from above information msg := types.MsgRequestData{ Sender: simAccount.Address.String(), - OracleScriptID: types.OracleScriptID(oid), + OracleScriptID: oid, Calldata: []byte(simtypes.RandStringOfLength(r, 100)), AskCount: uint64(askCount), MinCount: uint64(simtypes.RandIntBetween(r, 1, askCount+1)), @@ -268,7 +268,7 @@ func SimulateMsgReportData( // Generate report message msg := types.MsgReportData{ - RequestID: types.RequestID(rid), + RequestID: rid, RawReports: rawReports, Validator: sdk.ValAddress(simAccount.Address).String(), } @@ -344,7 +344,7 @@ func SimulateMsgEditDataSource( // Generate edit data source message msg := types.MsgEditDataSource{ Sender: simAccount.Address.String(), - DataSourceID: types.DataSourceID(did), + DataSourceID: did, Name: simtypes.RandStringOfLength(r, 10), Description: simtypes.RandStringOfLength(r, 100), Executable: []byte(simtypes.RandStringOfLength(r, 100)), @@ -421,7 +421,7 @@ func SimulateMsgEditOracleScript( // Generate edit oracle script message msg := types.MsgEditOracleScript{ Sender: simAccount.Address.String(), - OracleScriptID: types.OracleScriptID(oid), + OracleScriptID: oid, Name: simtypes.RandStringOfLength(r, 10), Description: simtypes.RandStringOfLength(r, 100), Schema: simtypes.RandStringOfLength(r, 100), From e078abb57a6eea927944db92ff56a2c6b27b24b1 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Wed, 21 Feb 2024 16:25:03 +0700 Subject: [PATCH 13/13] Update x/oracle/abci.go --- x/oracle/abci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/oracle/abci.go b/x/oracle/abci.go index 6496a1bec..dc69c3a16 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -12,7 +12,7 @@ import ( func handleBeginBlock(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { // Update rolling seed used for pseudorandom oracle provider selection. hash := req.GetHash() - // On the firsrt block in the test. it's possible to have empty hash. + // On the first block in the test. it's possible to have empty hash. if len(hash) > 0 { rollingSeed := k.GetRollingSeed(ctx) k.SetRollingSeed(ctx, append(rollingSeed[1:], hash[0]))