diff --git a/.gitignore b/.gitignore index a49ca25c..9291d763 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ build/ dist/ data/ + +.envrc diff --git a/app/app.go b/app/app.go index 3418126e..50ea5186 100644 --- a/app/app.go +++ b/app/app.go @@ -127,6 +127,7 @@ import ( ibccm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" "github.com/spf13/cast" + "github.com/archway-network/archway/app/appconst" "github.com/archway-network/archway/app/keepers" "github.com/archway-network/archway/x/cwfees" "github.com/archway-network/archway/x/genmsg" @@ -154,6 +155,10 @@ import ( cwicakeeper "github.com/archway-network/archway/x/cwica/keeper" cwicatypes "github.com/archway-network/archway/x/cwica/types" + oracle "github.com/archway-network/archway/x/oracle" + oraclekeeper "github.com/archway-network/archway/x/oracle/keeper" + oracletypes "github.com/archway-network/archway/x/oracle/types" + extendedGov "github.com/archway-network/archway/x/gov" "github.com/CosmWasm/wasmd/x/wasm" @@ -162,35 +167,6 @@ import ( archway "github.com/archway-network/archway/types" ) -const appName = "Archway" - -// We pull these out so we can set them with LDFLAGS in the Makefile -var ( - NodeDir = ".archway" - Bech32Prefix = "archway" -) - -// These constants are derived from the above variables. -// These are the ones we will want to use in the code, based on -// any overrides above -var ( - // DefaultNodeHome default home directories for archwayd - DefaultNodeHome = os.ExpandEnv("$HOME/") + NodeDir - - // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address - Bech32PrefixAccAddr = Bech32Prefix - // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key - Bech32PrefixAccPub = Bech32Prefix + sdk.PrefixPublic - // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address - Bech32PrefixValAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator - // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key - Bech32PrefixValPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic - // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address - Bech32PrefixConsAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus - // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key - Bech32PrefixConsPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic -) - var ( // ModuleBasics defines the module BasicManager is in charge of setting up basic, // non-dependant module elements, such as codec registration @@ -229,6 +205,7 @@ var ( cwfees.AppModule{}, cwica.AppModuleBasic{}, cwerrors.AppModuleBasic{}, + oracle.AppModuleBasic{}, ) // module account permissions @@ -247,6 +224,7 @@ var ( wasmdTypes.ModuleName: {authtypes.Burner}, rewardsTypes.TreasuryCollector: {authtypes.Burner}, callbackTypes.ModuleName: nil, + oracletypes.ModuleName: nil, } ) @@ -317,7 +295,7 @@ func NewArchwayApp( std.RegisterLegacyAminoCodec(legacyAmino) std.RegisterInterfaces(interfaceRegistry) - bApp := baseapp.NewBaseApp(appName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) + bApp := baseapp.NewBaseApp(appconst.AppName, logger, db, encodingConfig.TxConfig.TxDecoder(), baseAppOptions...) bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) @@ -335,6 +313,7 @@ func NewArchwayApp( icacontrollertypes.StoreKey, icahosttypes.StoreKey, ibcfeetypes.StoreKey, crisistypes.StoreKey, group.StoreKey, nftkeeper.StoreKey, cwicatypes.StoreKey, trackingTypes.StoreKey, rewardsTypes.StoreKey, callbackTypes.StoreKey, cwfees.ModuleName, cwerrorsTypes.StoreKey, + oracletypes.StoreKey, ) tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey, cwerrorsTypes.TStoreKey) memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -672,8 +651,19 @@ func NewArchwayApp( logger, ) + app.Keepers.OracleKeeper = oraclekeeper.NewKeeper( + appCodec, + keys[oracletypes.StoreKey], + app.Keepers.AccountKeeper, + app.Keepers.BankKeeper, + app.Keepers.DistrKeeper, + app.Keepers.StakingKeeper, + app.Keepers.SlashingKeeper, + distrtypes.ModuleName, + ) + app.Keepers.IBCHooksKeeper = ibchookskeeper.NewKeeper(keys[ibchookstypes.StoreKey]) - ics20WasmHooks := ibchooks.NewWasmHooks(&app.Keepers.IBCHooksKeeper, nil, Bech32Prefix) + ics20WasmHooks := ibchooks.NewWasmHooks(&app.Keepers.IBCHooksKeeper, nil, appconst.Bech32Prefix) hooksIcs4Wrapper := ibchooks.NewICS4Middleware(app.Keepers.IBCKeeper.ChannelKeeper, ics20WasmHooks) var transferStack porttypes.IBCModule @@ -759,6 +749,7 @@ func NewArchwayApp( cwica.NewAppModule(appCodec, app.Keepers.CWICAKeeper, app.Keepers.AccountKeeper), cwerrors.NewAppModule(app.appCodec, app.Keepers.CWErrorsKeeper, app.Keepers.WASMKeeper), crisis.NewAppModule(&app.Keepers.CrisisKeeper, skipGenesisInvariants, app.getSubspace(crisistypes.ModuleName)), // always be last to make sure that it checks for all invariants and not only part of them + oracle.NewAppModule(appCodec, app.Keepers.OracleKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper), ) // BasicModuleManager defines the module BasicManager is in charge of setting up basic, @@ -801,6 +792,7 @@ func NewArchwayApp( paramstypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, + oracletypes.ModuleName, // additional non simd modules ibcexported.ModuleName, ibctransfertypes.ModuleName, @@ -836,6 +828,7 @@ func NewArchwayApp( upgradetypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, + oracletypes.ModuleName, // wasm ibchookstypes.ModuleName, wasmdTypes.ModuleName, @@ -876,6 +869,7 @@ func NewArchwayApp( upgradetypes.ModuleName, vestingtypes.ModuleName, consensusparamtypes.ModuleName, + oracletypes.ModuleName, // additional non simd modules ibcexported.ModuleName, ibctransfertypes.ModuleName, @@ -1219,5 +1213,17 @@ func getAcceptedStargateQueries() wasmdKeeper.AcceptedStargateQueries { "/archway.cwerrors.v1.Query/Errors": &cwerrorsTypes.QueryErrorsRequest{}, "/archway.callback.v1.Query/EstimateCallbackFees": &callbackTypes.QueryEstimateCallbackFeesRequest{}, "/archway.callback.v1.Query/Params": &callbackTypes.QueryParamsRequest{}, + "/archway.oracle.v1.Query/ExchangeRate": new(oracletypes.QueryExchangeRateResponse), + "/archway.oracle.v1.Query/ExchangeRateTwap": new(oracletypes.QueryExchangeRateResponse), + "/archway.oracle.v1.Query/ExchangeRates": new(oracletypes.QueryExchangeRatesResponse), + "/archway.oracle.v1.Query/Actives": new(oracletypes.QueryActivesResponse), + "/archway.oracle.v1.Query/VoteTargets": new(oracletypes.QueryVoteTargetsResponse), + "/archway.oracle.v1.Query/FeederDelegation": new(oracletypes.QueryFeederDelegationResponse), + "/archway.oracle.v1.Query/MissCounter": new(oracletypes.QueryMissCounterResponse), + "/archway.oracle.v1.Query/AggregatePrevote": new(oracletypes.QueryAggregatePrevoteResponse), + "/archway.oracle.v1.Query/AggregatePrevotes": new(oracletypes.QueryAggregatePrevotesResponse), + "/archway.oracle.v1.Query/AggregateVote": new(oracletypes.QueryAggregateVoteResponse), + "/archway.oracle.v1.Query/AggregateVotes": new(oracletypes.QueryAggregateVotesResponse), + "/archway.oracle.v1.Query/Params": new(oracletypes.QueryParamsResponse), } } diff --git a/app/appconst/appconst.go b/app/appconst/appconst.go new file mode 100644 index 00000000..6ddb2261 --- /dev/null +++ b/app/appconst/appconst.go @@ -0,0 +1,36 @@ +package appconst + +import ( + "os" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const AppName = "Archway" + +// We pull these out so we can set them with LDFLAGS in the Makefile +var ( + NodeDir = ".archway" + Bech32Prefix = "archway" +) + +// These constants are derived from the above variables. +// These are the ones we will want to use in the code, based on +// any overrides above +var ( + // DefaultNodeHome default home directories for archwayd + DefaultNodeHome = os.ExpandEnv("$HOME/") + NodeDir + + // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address + Bech32PrefixAccAddr = Bech32Prefix + // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key + Bech32PrefixAccPub = Bech32Prefix + sdk.PrefixPublic + // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address + Bech32PrefixValAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key + Bech32PrefixValPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic + // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address + Bech32PrefixConsAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key + Bech32PrefixConsPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic +) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 02b8de2f..0e479bcf 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -32,6 +32,7 @@ import ( callbackKeeper "github.com/archway-network/archway/x/callback/keeper" cwerrorsKeeper "github.com/archway-network/archway/x/cwerrors/keeper" cwicaKeeper "github.com/archway-network/archway/x/cwica/keeper" + oracleKeeper "github.com/archway-network/archway/x/oracle/keeper" rewardsKeeper "github.com/archway-network/archway/x/rewards/keeper" trackingKeeper "github.com/archway-network/archway/x/tracking/keeper" ) @@ -69,4 +70,5 @@ type ArchwayKeepers struct { CallbackKeeper callbackKeeper.Keeper CWErrorsKeeper cwerrorsKeeper.Keeper CWICAKeeper cwicaKeeper.Keeper + OracleKeeper oracleKeeper.Keeper } diff --git a/app/prefix.go b/app/prefix.go new file mode 100644 index 00000000..6781a392 --- /dev/null +++ b/app/prefix.go @@ -0,0 +1,16 @@ +package app + +import ( + "github.com/CosmWasm/wasmd/app" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func SetPrefixes() { + cfg := sdk.GetConfig() + cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) + cfg.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub) + cfg.SetAddressVerifier(wasmtypes.VerifyAddressLen()) + cfg.Seal() +} diff --git a/app/swagger.go b/app/swagger.go index 7214e2a2..fe6b53d7 100644 --- a/app/swagger.go +++ b/app/swagger.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/server/api" + "github.com/archway-network/archway/app/appconst" "github.com/archway-network/archway/docs" "github.com/archway-network/archway/pkg/openapiconsole" ) @@ -13,6 +14,6 @@ import ( func RegisterSwaggerAPI(apiSvr *api.Server) error { // register app's OpenAPI routes. apiSvr.Router.Handle("/static/swagger.min.json", http.FileServer(http.FS(docs.Docs))) - apiSvr.Router.HandleFunc("/", openapiconsole.Handler(appName+" Swagger UI", "/static/swagger.min.json")) + apiSvr.Router.HandleFunc("/", openapiconsole.Handler(appconst.AppName+" Swagger UI", "/static/swagger.min.json")) return nil } diff --git a/cmd/archwayd/main.go b/cmd/archwayd/main.go index 37e1a5a8..022fa184 100644 --- a/cmd/archwayd/main.go +++ b/cmd/archwayd/main.go @@ -10,7 +10,7 @@ import ( svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" - "github.com/archway-network/archway/app" + "github.com/archway-network/archway/app/appconst" ) const ArchwayASCII = ` @@ -29,7 +29,7 @@ func main() { rootCmd.AddCommand(ensureLibWasmVM()) - if err := svrcmd.Execute(rootCmd, "ARCHWAY", app.DefaultNodeHome); err != nil { + if err := svrcmd.Execute(rootCmd, "ARCHWAY", appconst.DefaultNodeHome); err != nil { _, _ = fmt.Fprintln(rootCmd.OutOrStderr(), err) os.Exit(1) } diff --git a/cmd/archwayd/root.go b/cmd/archwayd/root.go index 87ce160a..9aa6ab94 100644 --- a/cmd/archwayd/root.go +++ b/cmd/archwayd/root.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -33,21 +32,16 @@ import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/archway-network/archway/app" + "github.com/archway-network/archway/app/appconst" "github.com/archway-network/archway/app/params" ) // NewRootCmd creates a new root command for archwayd. It is called once in the // main function. func NewRootCmd() (*cobra.Command, params.EncodingConfig) { - cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub) - cfg.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub) - cfg.SetAddressVerifier(wasmtypes.VerifyAddressLen()) - cfg.Seal() + app.SetPrefixes() encodingConfig := app.MakeEncodingConfig() tempApp := app.NewArchwayApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, nil, tempDir(), 0, encodingConfig, simtestutil.NewAppOptionsWithFlagHome(tempDir()), []wasmkeeper.Option{}) @@ -59,7 +53,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { WithLegacyAmino(encodingConfig.Amino). WithInput(os.Stdin). WithAccountRetriever(authtypes.AccountRetriever{}). - WithHomeDir(app.DefaultNodeHome). + WithHomeDir(appconst.DefaultNodeHome). WithViper("") rootCmd := &cobra.Command{ @@ -123,7 +117,7 @@ func NewRootCmd() (*cobra.Command, params.EncodingConfig) { func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { rootCmd.AddCommand( - genutilcli.InitCmd(app.ModuleBasics, app.DefaultNodeHome), + genutilcli.InitCmd(app.ModuleBasics, appconst.DefaultNodeHome), tmcli.NewCompletionCmd(rootCmd, true), // testnetCmd(app.ModuleBasics, banktypes.GenesisBalancesIterator{}), debug.Cmd(), @@ -133,7 +127,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { ac := appCreator{ encCfg: encodingConfig, } - server.AddCommands(rootCmd, app.DefaultNodeHome, ac.newApp, ac.appExport, addModuleInitFlags) + server.AddCommands(rootCmd, appconst.DefaultNodeHome, ac.newApp, ac.appExport, addModuleInitFlags) // add keybase, auxiliary RPC, query, and tx child commands rootCmd.AddCommand( @@ -201,7 +195,7 @@ func txCommand() *cobra.Command { } func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { - cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, app.ModuleBasics, app.DefaultNodeHome) + cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, app.ModuleBasics, appconst.DefaultNodeHome) for _, sub_cmd := range cmds { cmd.AddCommand(sub_cmd) diff --git a/go.mod b/go.mod index f4f361ba..ae9b1519 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/CosmWasm/cosmwasm-go v0.5.1-0.20220822092235-974247a04ac7 github.com/CosmWasm/wasmd v0.51.0 github.com/CosmWasm/wasmvm v1.5.4 + github.com/NibiruChain/collections v0.50.0 github.com/cometbft/cometbft v0.38.10 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.5 @@ -31,20 +32,26 @@ require ( github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.4.0 github.com/dvsekhvalnov/jose2go v1.6.0 - github.com/gogo/protobuf v1.3.2 + github.com/gogo/protobuf v1.3.3 + github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 + github.com/google/gofuzz v1.2.0 github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.0 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 + golang.org/x/sync v0.7.0 google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.34.1 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -54,6 +61,7 @@ require ( cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.36.0 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + cosmossdk.io/x/circuit v0.1.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.2 // indirect @@ -109,11 +117,9 @@ require ( github.com/gogo/googleapis v1.4.1 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect @@ -162,7 +168,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.52.2 // indirect @@ -176,7 +181,6 @@ require ( github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect @@ -195,7 +199,6 @@ require ( golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect @@ -205,7 +208,6 @@ require ( google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect nhooyr.io/websocket v1.8.7 // indirect @@ -222,3 +224,5 @@ replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.1 replace github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 replace github.com/spf13/viper => github.com/spf13/viper v1.17.0 + +replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 diff --git a/go.sum b/go.sum index 49e4110d..2594457a 100644 --- a/go.sum +++ b/go.sum @@ -822,6 +822,8 @@ github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/NibiruChain/collections v0.50.0 h1:zjwZ17G1f/0hoKUeoljIQval0HERF8NmojfQYKEOymw= +github.com/NibiruChain/collections v0.50.0/go.mod h1:u3NETehzjViSEZLmSjps0Akd8mWVfw3FXSh8lswf5PU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -1149,12 +1151,6 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -1426,8 +1422,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= @@ -1655,6 +1649,8 @@ github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43Z github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -2241,11 +2237,9 @@ golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -2436,6 +2430,7 @@ google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= diff --git a/proto/archway/oracle/v1/event.proto b/proto/archway/oracle/v1/event.proto new file mode 100644 index 00000000..8ae02a79 --- /dev/null +++ b/proto/archway/oracle/v1/event.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; + +package archway.oracle.v1; + +import "archway/oracle/v1/oracle.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// Emitted when a price is posted +message EventPriceUpdate { + string pair = 1; + string price = 2 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + int64 timestamp_ms = 3; +} + +// Emitted when a valoper delegates oracle voting rights to a feeder address. +message EventDelegateFeederConsent { + // Validator is the Bech32 address that is delegating voting rights. + string validator = 1; + + // Feeder is the delegate or representative that will be able to send + // vote and prevote transaction messages. + string feeder = 2; +} + +// Emitted by MsgAggregateExchangeVote when an aggregate vote is added to state +message EventAggregateVote { + // Validator is the Bech32 address to which the vote will be credited. + string validator = 1; + + // Feeder is the delegate or representative that will send vote and prevote + // transaction messages on behalf of the voting validator. + string feeder = 2; + + repeated archway.oracle.v1.ExchangeRateTuple prices = 3 [ + (gogoproto.castrepeated) = "ExchangeRateTuples", + (gogoproto.nullable) = false + ]; +} + +// Emitted by MsgAggregateExchangePrevote when an aggregate prevote is added +// to state +message EventAggregatePrevote { + // Validator is the Bech32 address to which the vote will be credited. + string validator = 1; + + // Feeder is the delegate or representative that will send vote and prevote + // transaction messages on behalf of the voting validator. + string feeder = 2; +} + +message EventValidatorPerformance { + // Validator is the Bech32 address to which the vote will be credited. + string validator = 1; + + // Tendermint consensus voting power + int64 voting_power = 2; + + // RewardWeight: Weight of rewards the validator should receive in units of + // consensus power. + int64 reward_weight = 3; + + // Number of valid votes for which the validator will be rewarded + int64 win_count = 4; + + // Number of abstained votes for which there will be no reward or punishment + int64 abstain_count = 5; + + // Number of invalid/punishable votes + int64 miss_count = 6; +} \ No newline at end of file diff --git a/proto/archway/oracle/v1/genesis.proto b/proto/archway/oracle/v1/genesis.proto new file mode 100644 index 00000000..a543b63b --- /dev/null +++ b/proto/archway/oracle/v1/genesis.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package archway.oracle.v1; + +import "gogoproto/gogo.proto"; +import "archway/oracle/v1/oracle.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// GenesisState defines the oracle module's genesis state. +message GenesisState { + archway.oracle.v1.Params params = 1 [ (gogoproto.nullable) = false ]; + repeated archway.oracle.v1.FeederDelegation feeder_delegations = 2 + [ (gogoproto.nullable) = false ]; + repeated archway.oracle.v1.ExchangeRateTuple exchange_rates = 3 [ + (gogoproto.castrepeated) = "ExchangeRateTuples", + (gogoproto.nullable) = false + ]; + repeated archway.oracle.v1.MissCounter miss_counters = 4 + [ (gogoproto.nullable) = false ]; + repeated archway.oracle.v1.AggregateExchangeRatePrevote + aggregate_exchange_rate_prevotes = 5 [ (gogoproto.nullable) = false ]; + repeated archway.oracle.v1.AggregateExchangeRateVote + aggregate_exchange_rate_votes = 6 [ (gogoproto.nullable) = false ]; + repeated string pairs = 7 [ + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; + repeated archway.oracle.v1.Rewards rewards = 8 + [ (gogoproto.nullable) = false ]; +} + +// FeederDelegation is the address for where oracle feeder authority are +// delegated to. By default this struct is only used at genesis to feed in +// default feeder addresses. +message FeederDelegation { + string feeder_address = 1; + string validator_address = 2; +} + +// MissCounter defines an miss counter and validator address pair used in +// oracle module's genesis state +message MissCounter { + string validator_address = 1; + uint64 miss_counter = 2; +} diff --git a/proto/archway/oracle/v1/oracle.proto b/proto/archway/oracle/v1/oracle.proto new file mode 100644 index 00000000..92e4750e --- /dev/null +++ b/proto/archway/oracle/v1/oracle.proto @@ -0,0 +1,153 @@ +syntax = "proto3"; +package archway.oracle.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// Params defines the module parameters for the x/oracle module. +message Params { + option (gogoproto.equal) = true; + + // VotePeriod defines the number of blocks during which voting takes place. + uint64 vote_period = 1 [ (gogoproto.moretags) = "yaml:\"vote_period\"" ]; + + // VoteThreshold specifies the minimum proportion of votes that must be + // received for a ballot to pass. + string vote_threshold = 2 [ + (gogoproto.moretags) = "yaml:\"vote_threshold\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + // RewardBand defines a maxium divergence that a price vote can have from the + // weighted median in the ballot. If a vote lies within the valid range + // defined by: + // μ := weightedMedian, + // validRange := μ ± (μ * rewardBand / 2), + // then rewards are added to the validator performance. + // Note that if the reward band is smaller than 1 standard + // deviation, the band is taken to be 1 standard deviation.a price + string reward_band = 3 [ + (gogoproto.moretags) = "yaml:\"reward_band\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + // The set of whitelisted markets, or asset pairs, for the module. + // Ex. '["unibi:uusd","ubtc:uusd"]' + repeated string whitelist = 4 [ + (gogoproto.moretags) = "yaml:\"whitelist\"", + (gogoproto.customtype) = "github.com/archway-network/archway/x/common/asset.Pair" + ]; + // SlashFraction returns the proportion of an oracle's stake that gets + // slashed in the event of slashing. `SlashFraction` specifies the exact + // penalty for failing a voting period. + string slash_fraction = 5 [ + (gogoproto.moretags) = "yaml:\"slash_fraction\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + // SlashWindow returns the number of voting periods that specify a + // "slash window". After each slash window, all oracles that have missed more + // than the penalty threshold are slashed. Missing the penalty threshold is + // synonymous with submitting fewer valid votes than `MinValidPerWindow`. + uint64 slash_window = 6 [ (gogoproto.moretags) = "yaml:\"slash_window\"" ]; + string min_valid_per_window = 7 [ + (gogoproto.moretags) = "yaml:\"min_valid_per_window\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Amount of time to look back for TWAP calculations. + // Ex: "900.000000069s" corresponds to 900 seconds and 69 nanoseconds in JSON. + google.protobuf.Duration twap_lookback_window = 8 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.jsontag) = "twap_lookback_window,omitempty", + (gogoproto.moretags) = "yaml:\"twap_lookback_window\"" + ]; + + // The minimum number of voters (i.e. oracle validators) per pair for it to be + // considered a passing ballot. Recommended at least 4. + uint64 min_voters = 9 [ (gogoproto.moretags) = "yaml:\"min_voters\"" ]; + + // The validator fee ratio that is given to validators every epoch. + string validator_fee_ratio = 10 [ + (gogoproto.moretags) = "yaml:\"validator_fee_ratio\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + uint64 expiration_blocks = 11 + [ (gogoproto.moretags) = "yaml:\"expiration_blocks\"" ]; +} + +// Struct for aggregate prevoting on the ExchangeRateVote. +// The purpose of aggregate prevote is to hide vote exchange rates with hash +// which is formatted as hex string in +// SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") +message AggregateExchangeRatePrevote { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string hash = 1 [ (gogoproto.moretags) = "yaml:\"hash\"" ]; + string voter = 2 [ (gogoproto.moretags) = "yaml:\"voter\"" ]; + uint64 submit_block = 3 [ (gogoproto.moretags) = "yaml:\"submit_block\"" ]; +} + +// MsgAggregateExchangeRateVote - struct for voting on +// the exchange rates different assets. +message AggregateExchangeRateVote { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + repeated ExchangeRateTuple exchange_rate_tuples = 1 [ + (gogoproto.moretags) = "yaml:\"exchange_rate_tuples\"", + (gogoproto.castrepeated) = "ExchangeRateTuples", + (gogoproto.nullable) = false + ]; + + string voter = 2 [ (gogoproto.moretags) = "yaml:\"voter\"" ]; +} + +// ExchangeRateTuple - struct to store interpreted exchange rates data to store +message ExchangeRateTuple { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string pair = 1 [ + (gogoproto.moretags) = "yaml:\"pair\"", + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; + + string exchange_rate = 2 [ + (gogoproto.moretags) = "yaml:\"exchange_rate\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; +} + +message DatedPrice { + string exchange_rate = 1 [ + (gogoproto.moretags) = "yaml:\"exchange_rate\"", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + uint64 created_block = 2 [ (gogoproto.moretags) = "yaml:\"created_block\"" ]; +} + +// Rewards defines a credit object towards validators +// which provide prices faithfully for different pairs. +message Rewards { + // id uniquely identifies the rewards instance of the pair + uint64 id = 1; + // vote_periods defines the vote periods left in which rewards will be + // distributed. + uint64 vote_periods = 2; + // Coins defines the amount of coins to distribute in a single vote period. + repeated cosmos.base.v1beta1.Coin coins = 3 [ (gogoproto.nullable) = false ]; +} diff --git a/proto/archway/oracle/v1/query.proto b/proto/archway/oracle/v1/query.proto new file mode 100644 index 00000000..1a8dd6ab --- /dev/null +++ b/proto/archway/oracle/v1/query.proto @@ -0,0 +1,264 @@ +syntax = "proto3"; +package archway.oracle.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "archway/oracle/v1/oracle.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// Query defines the gRPC querier service. +service Query { + // ExchangeRate returns exchange rate of a pair + rpc ExchangeRate(QueryExchangeRateRequest) + returns (QueryExchangeRateResponse) { + option (google.api.http).get = "/archway/oracle/v1beta1/exchange_rate"; + } + + // ExchangeRateTwap returns twap exchange rate of a pair + rpc ExchangeRateTwap(QueryExchangeRateRequest) + returns (QueryExchangeRateResponse) { + option (google.api.http).get = "/archway/oracle/v1beta1/exchange_rate_twap"; + } + + // ExchangeRates returns exchange rates of all pairs + rpc ExchangeRates(QueryExchangeRatesRequest) + returns (QueryExchangeRatesResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/pairs/exchange_rates"; + } + + // Actives returns all active pairs + rpc Actives(QueryActivesRequest) returns (QueryActivesResponse) { + option (google.api.http).get = "/archway/oracle/v1beta1/pairs/actives"; + } + + // VoteTargets returns all vote target for pairs + rpc VoteTargets(QueryVoteTargetsRequest) returns (QueryVoteTargetsResponse) { + option (google.api.http).get = "/archway/oracle/v1beta1/pairs/vote_targets"; + } + + // FeederDelegation returns feeder delegation of a validator + rpc FeederDelegation(QueryFeederDelegationRequest) + returns (QueryFeederDelegationResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/validators/{validator_addr}/feeder"; + } + + // MissCounter returns oracle miss counter of a validator + rpc MissCounter(QueryMissCounterRequest) returns (QueryMissCounterResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/validators/{validator_addr}/miss"; + } + + // AggregatePrevote returns an aggregate prevote of a validator + rpc AggregatePrevote(QueryAggregatePrevoteRequest) + returns (QueryAggregatePrevoteResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/validators/{validator_addr}/aggregate_prevote"; + } + + // AggregatePrevotes returns aggregate prevotes of all validators + rpc AggregatePrevotes(QueryAggregatePrevotesRequest) + returns (QueryAggregatePrevotesResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/validators/aggregate_prevotes"; + } + + // AggregateVote returns an aggregate vote of a validator + rpc AggregateVote(QueryAggregateVoteRequest) + returns (QueryAggregateVoteResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/valdiators/{validator_addr}/aggregate_vote"; + } + + // AggregateVotes returns aggregate votes of all validators + rpc AggregateVotes(QueryAggregateVotesRequest) + returns (QueryAggregateVotesResponse) { + option (google.api.http).get = + "/archway/oracle/v1beta1/validators/aggregate_votes"; + } + + // Params queries all parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/archway/oracle/v1beta1/params"; + } +} + +// QueryExchangeRateRequest is the request type for the Query/ExchangeRate RPC +// method. +message QueryExchangeRateRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // pair defines the pair to query for. + string pair = 1 [ + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; +} + +// QueryExchangeRateResponse is response type for the +// Query/ExchangeRate RPC method. +message QueryExchangeRateResponse { + // exchange_rate defines the exchange rate of assets voted by validators + string exchange_rate = 1 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; +} + +// QueryExchangeRatesRequest is the request type for the Query/ExchangeRates RPC +// method. +message QueryExchangeRatesRequest {} + +// QueryExchangeRatesResponse is response type for the +// Query/ExchangeRates RPC method. +message QueryExchangeRatesResponse { + // exchange_rates defines a list of the exchange rate for all whitelisted + // pairs. + repeated archway.oracle.v1.ExchangeRateTuple exchange_rates = 1 [ + (gogoproto.castrepeated) = "ExchangeRateTuples", + (gogoproto.nullable) = false + ]; +} + +// QueryActivesRequest is the request type for the Query/Actives RPC method. +message QueryActivesRequest {} + +// QueryActivesResponse is response type for the +// Query/Actives RPC method. +message QueryActivesResponse { + // actives defines a list of the pair which oracle prices agreed upon. + repeated string actives = 1 [ + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; +} + +// QueryVoteTargetsRequest is the request type for the Query/VoteTargets RPC +// method. +message QueryVoteTargetsRequest {} + +// QueryVoteTargetsResponse is response type for the +// Query/VoteTargets RPC method. +message QueryVoteTargetsResponse { + // vote_targets defines a list of the pairs in which everyone + // should vote in the current vote period. + repeated string vote_targets = 1 [ + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; +} + +// QueryFeederDelegationRequest is the request type for the +// Query/FeederDelegation RPC method. +message QueryFeederDelegationRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator defines the validator address to query for. + string validator_addr = 1; +} + +// QueryFeederDelegationResponse is response type for the +// Query/FeederDelegation RPC method. +message QueryFeederDelegationResponse { + // feeder_addr defines the feeder delegation of a validator + string feeder_addr = 1; +} + +// QueryMissCounterRequest is the request type for the Query/MissCounter RPC +// method. +message QueryMissCounterRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator defines the validator address to query for. + string validator_addr = 1; +} + +// QueryMissCounterResponse is response type for the +// Query/MissCounter RPC method. +message QueryMissCounterResponse { + // miss_counter defines the oracle miss counter of a validator + uint64 miss_counter = 1; +} + +// QueryAggregatePrevoteRequest is the request type for the +// Query/AggregatePrevote RPC method. +message QueryAggregatePrevoteRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator defines the validator address to query for. + string validator_addr = 1; +} + +// QueryAggregatePrevoteResponse is response type for the +// Query/AggregatePrevote RPC method. +message QueryAggregatePrevoteResponse { + // aggregate_prevote defines oracle aggregate prevote submitted by a validator + // in the current vote period + archway.oracle.v1.AggregateExchangeRatePrevote aggregate_prevote = 1 + [ (gogoproto.nullable) = false ]; + ; +} + +// QueryAggregatePrevotesRequest is the request type for the +// Query/AggregatePrevotes RPC method. +message QueryAggregatePrevotesRequest {} + +// QueryAggregatePrevotesResponse is response type for the +// Query/AggregatePrevotes RPC method. +message QueryAggregatePrevotesResponse { + // aggregate_prevotes defines all oracle aggregate prevotes submitted in the + // current vote period + repeated archway.oracle.v1.AggregateExchangeRatePrevote aggregate_prevotes = 1 + [ (gogoproto.nullable) = false ]; +} + +// QueryAggregateVoteRequest is the request type for the Query/AggregateVote RPC +// method. +message QueryAggregateVoteRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator defines the validator address to query for. + string validator_addr = 1; +} + +// QueryAggregateVoteResponse is response type for the +// Query/AggregateVote RPC method. +message QueryAggregateVoteResponse { + // aggregate_vote defines oracle aggregate vote submitted by a validator in + // the current vote period + archway.oracle.v1.AggregateExchangeRateVote aggregate_vote = 1 + [ (gogoproto.nullable) = false ]; +} + +// QueryAggregateVotesRequest is the request type for the Query/AggregateVotes +// RPC method. +message QueryAggregateVotesRequest {} + +// QueryAggregateVotesResponse is response type for the +// Query/AggregateVotes RPC method. +message QueryAggregateVotesResponse { + // aggregate_votes defines all oracle aggregate votes submitted in the current + // vote period + repeated archway.oracle.v1.AggregateExchangeRateVote aggregate_votes = 1 + [ (gogoproto.nullable) = false ]; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + archway.oracle.v1.Params params = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/proto/archway/oracle/v1/state.proto b/proto/archway/oracle/v1/state.proto new file mode 100644 index 00000000..26ef3ccd --- /dev/null +++ b/proto/archway/oracle/v1/state.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package archway.oracle.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "archway/oracle/v1/oracle.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// a snapshot of the prices at a given point in time +message PriceSnapshot { + string pair = 1 [ + (gogoproto.moretags) = "yaml:\"pair\"", + (gogoproto.customtype) = + "github.com/archway-network/archway/x/common/asset.Pair", + (gogoproto.nullable) = false + ]; + + string price = 2 [ + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // milliseconds since unix epoch + int64 timestamp_ms = 3; +} \ No newline at end of file diff --git a/proto/archway/oracle/v1/tx.proto b/proto/archway/oracle/v1/tx.proto new file mode 100644 index 00000000..94e03dd6 --- /dev/null +++ b/proto/archway/oracle/v1/tx.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; +package archway.oracle.v1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "archway/oracle/v1/oracle.proto"; +import "cosmos/msg/v1/msg.proto"; + +option go_package = "github.com/archway-network/archway/x/oracle/types"; + +// Msg defines the oracle Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // AggregateExchangeRatePrevote defines a method for submitting + // aggregate exchange rate prevote + rpc AggregateExchangeRatePrevote(MsgAggregateExchangeRatePrevote) + returns (MsgAggregateExchangeRatePrevoteResponse) { + option (google.api.http).post = "/archway/oracle/prevote"; + } + + // AggregateExchangeRateVote defines a method for submitting + // aggregate exchange rate vote + rpc AggregateExchangeRateVote(MsgAggregateExchangeRateVote) + returns (MsgAggregateExchangeRateVoteResponse) { + option (google.api.http).post = "/archway/oracle/vote"; + } + + // DelegateFeedConsent defines a method for delegating oracle voting rights + // to another address known as a price feeder. + // See https://github.com/NibiruChain/pricefeeder. + rpc DelegateFeedConsent(MsgDelegateFeedConsent) + returns (MsgDelegateFeedConsentResponse) { + option (google.api.http).post = "/archway/oracle/feeder-delegate"; + } +} + +// MsgAggregateExchangeRatePrevote represents a message to submit +// aggregate exchange rate prevote. +message MsgAggregateExchangeRatePrevote { + option (cosmos.msg.v1.signer) = "feeder"; + + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string hash = 1 [ (gogoproto.moretags) = "yaml:\"hash\"" ]; + // Feeder is the Bech32 address of the price feeder. A validator may + // specify multiple price feeders by delegating them consent. The validator + // address is also a valid feeder by default. + string feeder = 2 [ (gogoproto.moretags) = "yaml:\"feeder\"" ]; + // Validator is the Bech32 address to which the prevote will be credited. + string validator = 3 [ (gogoproto.moretags) = "yaml:\"validator\"" ]; +} + +// MsgAggregateExchangeRatePrevoteResponse defines the +// Msg/AggregateExchangeRatePrevote response type. +message MsgAggregateExchangeRatePrevoteResponse {} + +// MsgAggregateExchangeRateVote represents a message to submit +// aggregate exchange rate vote. +message MsgAggregateExchangeRateVote { + option (cosmos.msg.v1.signer) = "feeder"; + + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string salt = 1 [ (gogoproto.moretags) = "yaml:\"salt\"" ]; + string exchange_rates = 2 + [ (gogoproto.moretags) = "yaml:\"exchange_rates\"" ]; + // Feeder is the Bech32 address of the price feeder. A validator may + // specify multiple price feeders by delegating them consent. The validator + // address is also a valid feeder by default. + string feeder = 3 [ (gogoproto.moretags) = "yaml:\"feeder\"" ]; + // Validator is the Bech32 address to which the vote will be credited. + string validator = 4 [ (gogoproto.moretags) = "yaml:\"validator\"" ]; +} + +// MsgAggregateExchangeRateVoteResponse defines the +// Msg/AggregateExchangeRateVote response type. +message MsgAggregateExchangeRateVoteResponse {} + +// MsgDelegateFeedConsent represents a message to delegate oracle voting rights +// to another address. +message MsgDelegateFeedConsent { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string operator = 1 [ (gogoproto.moretags) = "yaml:\"operator\"" ]; + string delegate = 2 [ (gogoproto.moretags) = "yaml:\"delegate\"" ]; +} + +// MsgDelegateFeedConsentResponse defines the Msg/DelegateFeedConsent response +// type. +message MsgDelegateFeedConsentResponse {} diff --git a/proto/buf.lock b/proto/buf.lock index d1ea0063..675cb673 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -4,8 +4,8 @@ deps: - remote: buf.build owner: cosmos repository: cosmos-proto - commit: 1935555c206d4afb9e94615dfd0fad31 - digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 + commit: 04467658e59e44bbb22fe568206e1f70 + digest: shake256:73a640bd60e0c523b0f8237ff34eab67c45a38b64bbbde1d80224819d272dbf316ac183526bd245f994af6608b025f5130483d0133c5edd385531326b5990466 - remote: buf.build owner: cosmos repository: cosmos-sdk @@ -14,13 +14,13 @@ deps: - remote: buf.build owner: cosmos repository: gogo-proto - commit: 5e5b9fdd01804356895f8f79a6f1ddc1 - digest: shake256:0b85da49e2e5f9ebc4806eae058e2f56096ff3b1c59d1fb7c190413dd15f45dd456f0b69ced9059341c80795d2b6c943de15b120a9e0308b499e43e4b5fc2952 + commit: 88ef6483f90f478fb938c37dde52ece3 + digest: shake256:89c45df2aa11e0cff97b0d695436713db3d993d76792e9f8dc1ae90e6ab9a9bec55503d48ceedd6b86069ab07d3041b32001b2bfe0227fa725dd515ff381e5ba - remote: buf.build owner: cosmos repository: ibc - commit: fbb44f5ad3194450af479a615fa715d9 - digest: shake256:3fbf41c96089017ebf3b5143f78de0d531f604cb11da1bc98b2104eb6dd295b8a49f5f35c60b8389ba50bfa08959da905109324099e75ece9afd8e4087b14019 + commit: 6b221c7d310545198c1dafe70287d254 + digest: shake256:c5ea4d89af1c47f4d02057892eacdcb863359178079d9599f30d853b374fe9e9bfb3d9ca6720361c3439999a885a4f87fff4cd41c6c26b1f1142d60c386f8323 - remote: buf.build owner: cosmos repository: ics23 @@ -29,5 +29,5 @@ deps: - remote: buf.build owner: googleapis repository: googleapis - commit: 28151c0d0a1641bf938a7672c500e01d - digest: shake256:49215edf8ef57f7863004539deff8834cfb2195113f0b890dd1f67815d9353e28e668019165b9d872395871eeafcbab3ccfdb2b5f11734d3cca95be9e8d139de + commit: e7f8d366f5264595bcc4cd4139af9973 + digest: shake256:e5e5f1c12f82e028ea696faa43b4f9dc6258a6d1226282962a8c8b282e10946281d815884f574bd279ebd9cd7588629beb3db17b892af6c33b56f92f8f67f509 diff --git a/proto/buf.yaml b/proto/buf.yaml index 34639bae..13f1ea4c 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -2,8 +2,9 @@ version: v1 name: buf.build/archway-network/archway deps: - buf.build/cosmos/gogo-proto + - buf.build/cosmos/cosmos-proto - buf.build/cosmos/cosmos-sdk:v0.50.0 - - buf.build/cosmos/ibc:fbb44f5ad3194450af479a615fa715d9 + - buf.build/cosmos/ibc:6b221c7d310545198c1dafe70287d254 - buf.build/googleapis/googleapis - buf.build/cosmos/ics23:b1abd8678aab07165efd453c96796a179eb3131f breaking: diff --git a/scripts/localnet.sh b/scripts/localnet.sh index f144248b..37121b05 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -21,17 +21,13 @@ echo_success () { } # Set localnet settings -if [[ -f "build/archwayd" ]] ;then - BINARY=build/archwayd - # Console log text colour - red=`tput setaf 9` - green=`tput setaf 10` - blue=`tput setaf 12` - reset=`tput sgr0` -else - BINARY=archwayd -fi -CHAIN_ID=localnet-1 +BINARY=build/archwayd +# Console log text colour +red=`tput setaf 9` +green=`tput setaf 10` +blue=`tput setaf 12` +reset=`tput sgr0` +CHAIN_ID=archway-localnet-1 CHAIN_DIR=./data VALIDATOR_MNEMONIC="guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host" DEVELOPER_MNEMONIC="friend excite rough reopen cover wheel spoon convince island path clean monkey play snow number walnut pull lock shoot hurry dream divide concert discover" @@ -83,23 +79,25 @@ setup_chain () { contents="$(jq '.app_state.gov.params.max_deposit_period = "20s"' $genesis)" && echo "${contents}" > $genesis echo_info "Set x/gov proposal max deposit period to 20 seconds" + $BINARY config set client keyring-backend test --home $CHAIN_DIR/$CHAIN_ID + $BINARY config set client chain-id $CHAIN_ID --home $CHAIN_DIR/$CHAIN_ID # Adding users echo_info "Adding genesis accounts..." echo_info "1. validator" - echo $VALIDATOR_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add validator --recover --keyring-backend test - $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show validator --keyring-backend test -a) $GENESIS_COINS + echo $VALIDATOR_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add validator --recover + $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show validator -a) $GENESIS_COINS echo_info "2. developer" - echo $DEVELOPER_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add developer --recover --keyring-backend test - $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show developer --keyring-backend test -a) $GENESIS_COINS + echo $DEVELOPER_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add developer --recover + $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show developer -a) $GENESIS_COINS echo_info "3. user" - echo $USER_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add user --recover --keyring-backend test - $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show user --keyring-backend test -a) $GENESIS_COINS + echo $USER_MNEMONIC | $BINARY --home $CHAIN_DIR/$CHAIN_ID keys add user --recover + $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis add-genesis-account $($BINARY --home $CHAIN_DIR/$CHAIN_ID keys show user -a) $GENESIS_COINS # Creating gentx echo_info "Creating gentx for validator..." - $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis gentx validator 100000000000000000000000stake --chain-id $CHAIN_ID --fees 950000000000000000000stake --keyring-backend test + $BINARY --home $CHAIN_DIR/$CHAIN_ID genesis gentx validator 100000000000000000000000stake --chain-id $CHAIN_ID --fees 950000000000000000000stake # Collecting gentx @@ -126,4 +124,4 @@ fi # Starting chain echo_info "Starting chain..." -$BINARY --home $CHAIN_DIR/$CHAIN_ID start --minimum-gas-prices 0stake +$BINARY --home $CHAIN_DIR/$CHAIN_ID start --minimum-gas-prices 0stake --log_level debug diff --git a/x/common/README.md b/x/common/README.md new file mode 100644 index 00000000..1ac5855b --- /dev/null +++ b/x/common/README.md @@ -0,0 +1,3 @@ +# x/common + +The x/common directory holds helper and utility functions to be utilized by other `x/` cosmos-sdk modules. \ No newline at end of file diff --git a/x/common/address.go b/x/common/address.go new file mode 100644 index 00000000..67bf644d --- /dev/null +++ b/x/common/address.go @@ -0,0 +1,33 @@ +package common + +import ( + "github.com/NibiruChain/collections" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func AddrsToStrings(addrs ...sdk.AccAddress) []string { + var addrStrings []string + for _, addr := range addrs { + addrStrings = append(addrStrings, addr.String()) + } + return addrStrings +} + +func StringsToAddrs(strs ...string) []sdk.AccAddress { + var addrs []sdk.AccAddress + for _, str := range strs { + addr := sdk.MustAccAddressFromBech32(str) + addrs = append(addrs, addr) + } + return addrs +} + +// TODO: (realu) Move to collections library +var StringValueEncoder collections.ValueEncoder[string] = stringValueEncoder{} + +type stringValueEncoder struct{} + +func (a stringValueEncoder) Encode(value string) []byte { return []byte(value) } +func (a stringValueEncoder) Decode(b []byte) string { return string(b) } +func (a stringValueEncoder) Stringify(value string) string { return value } +func (a stringValueEncoder) Name() string { return "string" } diff --git a/x/common/address_test.go b/x/common/address_test.go new file mode 100644 index 00000000..aee761c1 --- /dev/null +++ b/x/common/address_test.go @@ -0,0 +1,42 @@ +package common_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common" + "github.com/archway-network/archway/x/common/testutil" +) + +func TestAddress(t *testing.T) { + require.NotPanics(t, func() { + _, addrs := testutil.PrivKeyAddressPairs(5) + strs := common.AddrsToStrings(addrs...) + addrsOut := common.StringsToAddrs(strs...) + require.EqualValues(t, addrs, addrsOut) + }) +} + +func TestStringValueEncoder(t *testing.T) { + encoder := common.StringValueEncoder + tests := []struct { + given string + }{ + {"hello"}, + {"12345"}, + {""}, + {testutil.AccAddress().String()}, + } + + for _, tc := range tests { + t.Run(tc.given, func(t *testing.T) { + want := tc.given + encoded := encoder.Encode(tc.given) + got := encoder.Decode(encoded) + assert.Equal(t, want, got) + assert.Equal(t, want, encoder.Stringify(got)) + }) + } +} diff --git a/x/common/asset/pair.go b/x/common/asset/pair.go new file mode 100644 index 00000000..09377ae5 --- /dev/null +++ b/x/common/asset/pair.go @@ -0,0 +1,170 @@ +package asset + +import ( + "encoding/json" + "fmt" + "strings" + + sdkerrors "cosmossdk.io/errors" + "github.com/NibiruChain/collections" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// paired against USD +var ErrInvalidTokenPair = sdkerrors.Register("asset", 1, "invalid token pair") + +type Pair string + +func NewPair(base string, quote string) Pair { + // validate as denom + ap := fmt.Sprintf("%s%s%s", base, ":", quote) + return Pair(ap) +} + +// TryNewPair New returns a new asset pair instance if the pair is valid. +// The form, "token0:token1", is expected for 'pair'. +// Use this function to return an error instead of panicking. +func TryNewPair(pair string) (Pair, error) { + split := strings.Split(pair, ":") + splitLen := len(split) + if splitLen != 2 { + if splitLen == 1 { + return "", sdkerrors.Wrapf(ErrInvalidTokenPair, + "pair separator missing for pair name, %v", pair) + } else { + return "", sdkerrors.Wrapf(ErrInvalidTokenPair, + "pair name %v must have exactly two assets, not %v", pair, splitLen) + } + } + + if split[0] == "" || split[1] == "" { + return "", sdkerrors.Wrapf(ErrInvalidTokenPair, + "empty token identifiers are not allowed. token0: %v, token1: %v.", + split[0], split[1]) + } + + // validate as denom + Pair := NewPair(split[0], split[1]) + return Pair, Pair.Validate() +} + +// MustNewPair returns a new asset pair. It will panic if 'pair' is invalid. +// The form, "token0:token1", is expected for 'pair'. +func MustNewPair(pair string) Pair { + Pair, err := TryNewPair(pair) + if err != nil { + panic(err) + } + return Pair +} + +/* +String returns the string representation of the asset pair. + +Note that this differs from the output of the proto-generated 'String' method. +*/ +func (pair Pair) String() string { + return string(pair) +} + +func (pair Pair) Inverse() Pair { + return NewPair(pair.QuoteDenom(), pair.BaseDenom()) +} + +func (pair Pair) BaseDenom() string { + split := strings.Split(pair.String(), ":") + return split[0] +} + +func (pair Pair) QuoteDenom() string { + split := strings.Split(pair.String(), ":") + return split[1] +} + +// Validate performs a basic validation of the market params +func (pair Pair) Validate() error { + if len(pair) == 0 { + return ErrInvalidTokenPair.Wrap("pair is empty") + } + + split := strings.Split(pair.String(), ":") + if len(split) != 2 { + return ErrInvalidTokenPair.Wrap(pair.String()) + } + + if err := sdk.ValidateDenom(split[0]); err != nil { + return ErrInvalidTokenPair.Wrapf("invalid base asset: %s", err) + } + if err := sdk.ValidateDenom(split[1]); err != nil { + return ErrInvalidTokenPair.Wrapf("invalid quote asset: %s", err) + } + return nil +} + +func (pair Pair) Equal(other Pair) bool { + return pair.String() == other.String() +} + +var _ sdk.CustomProtobufType = (*Pair)(nil) + +func (pair Pair) Marshal() ([]byte, error) { + return []byte(pair), nil +} + +func (pair *Pair) Unmarshal(data []byte) error { + *pair = Pair(data) + return nil +} + +func (pair Pair) MarshalJSON() ([]byte, error) { + return json.Marshal(pair.String()) +} + +func (pair *Pair) UnmarshalJSON(data []byte) error { + var pairString string + if err := json.Unmarshal(data, &pairString); err != nil { + return err + } + *pair = Pair(pairString) + return nil +} + +func (pair Pair) MarshalTo(data []byte) (n int, err error) { + copy(data, pair) + return pair.Size(), nil +} + +func (pair Pair) Size() int { + return len(pair) +} + +var PairKeyEncoder collections.KeyEncoder[Pair] = pairKeyEncoder{} + +type pairKeyEncoder struct{} + +func (pairKeyEncoder) Stringify(a Pair) string { return a.String() } +func (pairKeyEncoder) Encode(a Pair) []byte { + return collections.StringKeyEncoder.Encode(a.String()) +} + +func (pairKeyEncoder) Decode(b []byte) (int, Pair) { + i, s := collections.StringKeyEncoder.Decode(b) + return i, MustNewPair(s) +} + +// MustNewPairs constructs a new asset pair set. A panic will occur if one of +// the provided pair names is invalid. +func MustNewPairs(pairStrings ...string) (pairs []Pair) { + for _, pairString := range pairStrings { + pairs = append(pairs, MustNewPair(pairString)) + } + return pairs +} + +func PairsToStrings(pairs []Pair) []string { + pairsStrings := []string{} + for _, pair := range pairs { + pairsStrings = append(pairsStrings, pair.String()) + } + return pairsStrings +} diff --git a/x/common/asset/pair_test.go b/x/common/asset/pair_test.go new file mode 100644 index 00000000..ff4f3bc2 --- /dev/null +++ b/x/common/asset/pair_test.go @@ -0,0 +1,180 @@ +package asset_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" +) + +func TestTryNewPair(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + tokenStr string + err error + }{ + { + "only one token", + denoms.NIBI, + asset.ErrInvalidTokenPair, + }, + { + "more than 2 tokens", + fmt.Sprintf("%s:%s:%s", denoms.NIBI, denoms.NUSD, denoms.USDC), + asset.ErrInvalidTokenPair, + }, + { + "different separator", + fmt.Sprintf("%s,%s", denoms.NIBI, denoms.NUSD), + asset.ErrInvalidTokenPair, + }, + { + "correct pair", + fmt.Sprintf("%s:%s", denoms.NIBI, denoms.NUSD), + nil, + }, + { + "empty token identifier", + fmt.Sprintf(":%s", denoms.ETH), + fmt.Errorf("empty token identifiers are not allowed"), + }, + { + "invalid denom 1", + "-invalid1:valid", + fmt.Errorf("invalid denom"), + }, + { + "invalid denom 2", + "valid:-invalid2", + fmt.Errorf("invalid denom"), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + _, err := asset.TryNewPair(tc.tokenStr) + if tc.err != nil { + require.ErrorContains(t, err, tc.err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestGetDenoms(t *testing.T) { + pair := asset.MustNewPair("uatom:unibi") + + require.Equal(t, "uatom", pair.BaseDenom()) + require.Equal(t, "unibi", pair.QuoteDenom()) +} + +func TestEquals(t *testing.T) { + pair := asset.MustNewPair("abc:xyz") + matchingOther := asset.MustNewPair("abc:xyz") + mismatchToken1 := asset.MustNewPair("abc:abc") + inversePair := asset.MustNewPair("xyz:abc") + + require.True(t, pair.Equal(matchingOther)) + require.False(t, pair.Equal(inversePair)) + require.False(t, pair.Equal(mismatchToken1)) +} + +func TestMustNewAssetPair(t *testing.T) { + require.Panics(t, func() { + asset.MustNewPair("aaa:bbb:ccc") + }) + + require.NotPanics(t, func() { + asset.MustNewPair("aaa:bbb") + }) +} + +func TestInverse(t *testing.T) { + pair := asset.MustNewPair("abc:xyz") + inverse := pair.Inverse() + require.Equal(t, "xyz", inverse.BaseDenom()) + require.Equal(t, "abc", inverse.QuoteDenom()) +} + +func TestMarshalJSON(t *testing.T) { + cdc := app.MakeEncodingConfig() + + testCases := []struct { + name string + input asset.Pair + strOutput string + }{ + {name: "happy-0", input: asset.Pair("abc:xyz"), strOutput: "\"abc:xyz\""}, + {name: "happy-1", input: asset.Pair("abc:xyz:foo"), strOutput: "\"abc:xyz:foo\""}, + {name: "happy-2", input: asset.Pair("abc"), strOutput: "\"abc\""}, + {name: "empty", input: asset.Pair(""), strOutput: "\"\""}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // MarshalJSON with codec.LegacyAmino + jsonBz, err := cdc.Amino.MarshalJSON(tc.input) + require.NoError(t, err) + require.Equal(t, tc.strOutput, string(jsonBz)) + + // MarshalJSON on custom type + jsonBzCustom, err := tc.input.MarshalJSON() + require.NoError(t, err) + require.Equal(t, jsonBzCustom, jsonBz) + + // UnmarshalJSON with codec.LegacyAmino + newPair := new(asset.Pair) + require.NoError(t, cdc.Amino.UnmarshalJSON(jsonBz, newPair)) + require.Equal(t, tc.input, *newPair) + + // UnmarshalJSON on custom type + newNewPair := new(asset.Pair) + *newNewPair = tc.input + require.NoError(t, newNewPair.UnmarshalJSON(jsonBz)) + + // Marshal and Unmarshal (to bytes) test + bz, err := tc.input.Marshal() + require.NoError(t, err) + newNewNewPair := new(asset.Pair) + require.NoError(t, newNewNewPair.Unmarshal(bz)) + require.Equal(t, tc.input, *newNewNewPair) + }) + } +} + +func TestPairsUtils(t *testing.T) { + testCases := []struct { + pairStrs []string + expectPanic bool + }{ + {pairStrs: []string{"eth:usd", "btc:usd", "atom:usd"}, expectPanic: false}, + {pairStrs: []string{"eth:usd", "", "abc"}, expectPanic: true}, + {pairStrs: []string{"eth:usd:ftt", "btc:usd"}, expectPanic: true}, + } + + var panicTestFn func(t require.TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) + + for idx, tc := range testCases { + t.Run(fmt.Sprint(idx), func(t *testing.T) { + if tc.expectPanic { + panicTestFn = require.Panics + } else { + panicTestFn = require.NotPanics + } + panicTestFn(t, func() { + pairs := asset.MustNewPairs(tc.pairStrs...) + newPairStrs := asset.PairsToStrings(pairs) + require.Equal(t, tc.pairStrs, newPairStrs) + }) + }) + } +} diff --git a/x/common/asset/registry.go b/x/common/asset/registry.go new file mode 100644 index 00000000..7ee4550c --- /dev/null +++ b/x/common/asset/registry.go @@ -0,0 +1,80 @@ +package asset + +import ( + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/set" +) + +type registry map[string]set.Set[string] + +var Registry registry + +func init() { + // map of base asset to supported quote assets + // quote assets are usually stables + Registry = map[string]set.Set[string]{ + denoms.BTC: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.ETH: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.NIBI: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.ATOM: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.OSMO: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.AVAX: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.SOL: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.BNB: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.ADA: set.New(denoms.USDC, denoms.NUSD, denoms.USD, denoms.USDT), + denoms.NUSD: set.New(denoms.USD, denoms.USDC), + denoms.USDC: set.New(denoms.USD, denoms.NUSD), + denoms.USDT: set.New(denoms.USD, denoms.NUSD, denoms.USDC), + } +} + +func (r registry) Pair(base string, quote string) Pair { + for q := range r[base] { + if q == quote { + return NewPair(string(base), string(quote)) + } + } + + return "" +} + +// Returns all supported base denoms +func (r registry) BaseDenoms() set.Set[string] { + baseSet := make(set.Set[string]) + for d := range r { + baseSet.Add(d) + } + return baseSet +} + +// Returns all supported quote denoms +func (r registry) QuoteDenoms() set.Set[string] { + quoteSet := make(set.Set[string]) + for base := range r { + for q := range r[base] { + quoteSet.Add(q) + } + } + return quoteSet +} + +// Checks if the provided denom is a supported base denom +func (r registry) IsSupportedBaseDenom(denom string) bool { + _, ok := r[denom] + return ok +} + +// Checks if the provided denom is a supported quote denom +func (r registry) IsSupportedQuoteDenom(denom string) bool { + return r.QuoteDenoms().Has(denom) +} + +// Checks if the provided denom is a supported denom +func (r registry) IsSupportedDenom(denom string) bool { + return r.IsSupportedBaseDenom(string(denom)) || r.IsSupportedQuoteDenom(string(denom)) +} + +// Checks if the provided base and quote denoms are a supported pair +func (r registry) IsSupportedPair(base string, quote string) bool { + return r.IsSupportedBaseDenom(base) && r.IsSupportedQuoteDenom(quote) +} diff --git a/x/common/asset/registry_test.go b/x/common/asset/registry_test.go new file mode 100644 index 00000000..f28a829d --- /dev/null +++ b/x/common/asset/registry_test.go @@ -0,0 +1,81 @@ +package asset + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/denoms" +) + +func TestIsSupportedPair(t *testing.T) { + for base := range Registry { + for quote := range Registry[base] { + require.Truef(t, Registry.IsSupportedPair(base, quote), "%s:%s should be supported", base, quote) + } + } + + t.Log("test an unsupported pair") + require.False(t, Registry.IsSupportedPair(denoms.ATOM, denoms.OSMO)) +} + +func TestPair(t *testing.T) { + for base := range Registry { + for quote := range Registry[base] { + require.Equal(t, NewPair(base, quote), Registry.Pair(base, quote)) + } + } + + t.Log("test an unsupported pair") + require.Equal(t, Pair(""), Registry.Pair(denoms.ATOM, denoms.OSMO)) + + t.Log("test an unsupported base asset") + require.Equal(t, Pair(""), Registry.Pair("unsuported_denom", denoms.USDC)) + + t.Log("test an unsupported quote asset") + require.Equal(t, Pair(""), Registry.Pair(denoms.ATOM, "unsupported_denom")) +} + +func TestBaseDenoms(t *testing.T) { + for base := range Registry { + require.Contains(t, Registry.BaseDenoms(), base) + } +} + +func TestIsSupportedBaseDenom(t *testing.T) { + for base := range Registry { + require.True(t, Registry.IsSupportedBaseDenom(base)) + } + require.False(t, Registry.IsSupportedBaseDenom("unsupported_denom")) +} + +func TestQuoteDenoms(t *testing.T) { + for base := range Registry { + for quote := range Registry[base] { + require.True(t, Registry.QuoteDenoms().Has(quote)) + } + } +} + +func TestIsSupportedQuoteDenom(t *testing.T) { + for base := range Registry { + for quote := range Registry[base] { + require.True(t, Registry.IsSupportedQuoteDenom(quote)) + } + } + + require.False(t, Registry.IsSupportedQuoteDenom("unsupported_denom")) +} + +func TestIsSupportedDenom(t *testing.T) { + for base := range Registry.BaseDenoms() { + require.True(t, Registry.IsSupportedDenom(base)) + } + + for quote := range Registry.QuoteDenoms() { + require.True(t, Registry.IsSupportedDenom(quote)) + } + + t.Log("test an unsupported denom") + require.False(t, Registry.IsSupportedDenom("unsupported_denom")) +} diff --git a/x/common/codec.go b/x/common/codec.go new file mode 100644 index 00000000..958936f7 --- /dev/null +++ b/x/common/codec.go @@ -0,0 +1,151 @@ +package common + +import ( + sdkmath "cosmossdk.io/math" + + coll "cosmossdk.io/collections" + collcodec "cosmossdk.io/collections/codec" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + // LegacyDecValue represents a collections.ValueCodec to work with LegacyDec. + LegacyDecValue collcodec.ValueCodec[sdkmath.LegacyDec] = legacyDecValueCodec{} + SdkIntKey collcodec.KeyCodec[sdkmath.Int] = mathIntKeyCodec{} + AccAddressValue collcodec.ValueCodec[sdk.AccAddress] = accAddressValueCodec{} +) + +// Collection Codecs + +// math.LegacyDec Value Codec + +type legacyDecValueCodec struct{} + +func (cdc legacyDecValueCodec) Encode(value sdkmath.LegacyDec) ([]byte, error) { + return value.Marshal() +} + +func (cdc legacyDecValueCodec) Decode(buffer []byte) (sdkmath.LegacyDec, error) { + v := sdkmath.LegacyZeroDec() + err := v.Unmarshal(buffer) + return v, err +} + +func (cdc legacyDecValueCodec) EncodeJSON(value sdkmath.LegacyDec) ([]byte, error) { + return value.MarshalJSON() +} + +func (cdc legacyDecValueCodec) DecodeJSON(buffer []byte) (sdkmath.LegacyDec, error) { + v := sdkmath.LegacyDec{} + err := v.UnmarshalJSON(buffer) + if err != nil { + return sdkmath.LegacyDec{}, err + } + return v, nil +} + +func (cdc legacyDecValueCodec) Stringify(value sdkmath.LegacyDec) string { + return value.String() +} + +func (cdc legacyDecValueCodec) ValueType() string { + return "math.LegacyDec" +} + +// AccAddress Value Codec + +type accAddressValueCodec struct{} + +func (cdc accAddressValueCodec) Encode(value sdk.AccAddress) ([]byte, error) { + return value.Marshal() +} + +func (cdc accAddressValueCodec) Decode(buffer []byte) (sdk.AccAddress, error) { + v := sdk.AccAddress{} + err := v.Unmarshal(buffer) + return v, err +} + +func (cdc accAddressValueCodec) EncodeJSON(value sdk.AccAddress) ([]byte, error) { + return value.MarshalJSON() +} + +func (cdc accAddressValueCodec) DecodeJSON(buffer []byte) (sdk.AccAddress, error) { + v := sdk.AccAddress{} + err := v.UnmarshalJSON(buffer) + if err != nil { + return nil, err + } + return v, nil +} + +func (cdc accAddressValueCodec) Stringify(value sdk.AccAddress) string { + return value.String() +} + +func (cdc accAddressValueCodec) ValueType() string { + return "sdk.AccAddress" +} + +// math.Int Key Codec + +type mathIntKeyCodec struct { + stringDecoder func(string) (sdkmath.Int, error) + keyType string +} + +func (cdc mathIntKeyCodec) Encode(buffer []byte, key sdkmath.Int) (int, error) { + bytes, err := key.Marshal() + if err != nil { + return 0, err + } + copy(bytes, buffer) + return key.Size(), nil +} + +func (cdc mathIntKeyCodec) Decode(buffer []byte) (int, sdkmath.Int, error) { + v := sdkmath.ZeroInt() + err := v.Unmarshal(buffer) + if err != nil { + return 0, v, err + } + return v.Size(), v, nil +} + +func (cdc mathIntKeyCodec) Size(key sdkmath.Int) int { + return key.Size() +} + +func (cdc mathIntKeyCodec) EncodeJSON(value sdkmath.Int) ([]byte, error) { + return coll.StringKey.EncodeJSON(value.String()) +} + +func (cdc mathIntKeyCodec) DecodeJSON(b []byte) (v sdkmath.Int, err error) { + s, err := coll.StringKey.DecodeJSON(b) + if err != nil { + return + } + v, err = cdc.stringDecoder(s) + return +} + +func (cdc mathIntKeyCodec) Stringify(key sdkmath.Int) string { + return key.String() +} + +func (cdc mathIntKeyCodec) KeyType() string { + return cdc.keyType +} + +func (cdc mathIntKeyCodec) EncodeNonTerminal(buffer []byte, key sdkmath.Int) (int, error) { + return cdc.Encode(buffer, key) +} + +func (cdc mathIntKeyCodec) DecodeNonTerminal(buffer []byte) (int, sdkmath.Int, error) { + return cdc.Decode(buffer) +} + +func (cdc mathIntKeyCodec) SizeNonTerminal(key sdkmath.Int) int { + return key.Size() +} diff --git a/x/common/constants.go b/x/common/constants.go new file mode 100644 index 00000000..03180382 --- /dev/null +++ b/x/common/constants.go @@ -0,0 +1,15 @@ +package common + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + TreasuryPoolModuleAccount = "treasury_pool" + // TO_MICRO: multiplier for converting between units and micro-units. + TO_MICRO = int64(1_000_000) + + NIBIRU_TEAM = "nibi1l8dxzwz9d4peazcqjclnkj2mhvtj7mpnkqx85mg0ndrlhwrnh7gskkzg0v" +) + +func NibiruTeamAddr() sdk.AccAddress { + return sdk.MustAccAddressFromBech32(NIBIRU_TEAM) +} diff --git a/x/common/dec.go b/x/common/dec.go new file mode 100644 index 00000000..0d1e2ffa --- /dev/null +++ b/x/common/dec.go @@ -0,0 +1,189 @@ +package common + +import ( + "fmt" + "math/big" + "strings" + + "cosmossdk.io/math" +) + +const ( + // number of decimal places + PrecisionExponent = 18 + + // bits required to represent the above precision + // Ceiling[Log2[10^Precision - 1]] + DecimalPrecisionBits = 60 + + // decimalTruncateBits is the minimum number of bits removed + // by a truncate operation. It is equal to + // Floor[Log2[10^Precision - 1]]. + decimalTruncateBits = DecimalPrecisionBits - 1 + maxBitLen = 256 + MaxDecBitLen = maxBitLen + decimalTruncateBits +) + +var ( + // precisionInt: 10 ** PrecisionExponent + precisionInt = new(big.Int).Exp(big.NewInt(10), big.NewInt(PrecisionExponent), nil) + // halfPrecisionInt: (10 ** PrecisionExponent) / 2 + halfPrecisionInt = new(big.Int).Quo(precisionInt, big.NewInt(2)) + oneInt = big.NewInt(1) +) + +// MustSqrtDec computes the square root of the input decimal using its +// underlying big.Int. The big.Int.Sqrt method is part of the standard library, +// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as +// large as 10**99. +// - NOTE, MustSqrtDec panics if it is called on a negative number, similar to the +// sdk.NewCoin and SqrtBigInt functions. A panic safe version of MustSqrtDec +// is available in the SqrtDec method. +func MustSqrtDec(dec math.LegacyDec) math.LegacyDec { + sqrtBigInt := MustSqrtBigInt(dec.BigInt()) + precision := math.LegacyNewDecFromBigInt(PRECISION_MULT) + return math.LegacyNewDecFromBigInt(sqrtBigInt).Quo(precision) +} + +// SqrtDec computes the square root of the input decimal using its +// underlying big.Int. SqrtDec is panic-safe and returns an error if the input +// decimal is negative. +// +// The big.Int.Sqrt method is part of the standard library, +// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as +// large as 10**99. +func SqrtDec(dec math.LegacyDec) (math.LegacyDec, error) { + var sqrtDec math.LegacyDec + var panicErr error = TryCatch(func() { + sqrtDec = MustSqrtDec(dec) + })() + return sqrtDec, panicErr +} + +// MustSqrtBigInt returns the square root of the input. +// - NOTE: MustSqrtBigInt panics if it is called on a negative number because it uses +// the `big.Int.Sqrt` from the "math/big" package. +func MustSqrtBigInt(i *big.Int) *big.Int { + sqrtInt := &big.Int{} + return sqrtInt.Sqrt(i) +} + +// SqrtInt is the panic-safe version of MustSqrtBigInt +func SqrtBigInt(i *big.Int) (*big.Int, error) { + sqrtInt := new(big.Int) + var panicErr error = TryCatch(func() { + *sqrtInt = *MustSqrtBigInt(i) + })() + return sqrtInt, panicErr +} + +// BigIntPow10 returns a big int that is a power of 10, e.g. BigIngPow10(3) +// returns 1000. This function is useful for creating large numbers outside the +// range of an int64 or 18 decimal precision. +func BigIntPow10(power int64) *big.Int { + bigInt, _ := new(big.Int).SetString("1"+strings.Repeat("0", int(power)), 10) + return bigInt +} + +// ———————————————————————————————————————————————— +// Logic needed from private code in the Cosmos-SDK +// See https://github.com/cosmos/cosmos-sdk/blob/v0.45.12/types/decimal.go +// + +const ( + PRECISION = 18 +) + +var ( + PRECISION_MULT = calcPrecisionMultiplier(0) + PRECISION_SQRT = int64(PRECISION / 2) + tenInt = big.NewInt(10) +) + +// calcPrecisionMultiplier computes a multiplier needed to maintain a target +// precision defined by 10 ** (PRECISION_SQRT - prec). +// The maximum available precision is PRECISION_SQRT (9). +func calcPrecisionMultiplier(prec int64) *big.Int { + if prec > PRECISION_SQRT { + panic(fmt.Sprintf("too much precision, maximum %v, provided %v", PRECISION_SQRT, prec)) + } + zerosToAdd := PRECISION_SQRT - prec + multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil) + return multiplier +} + +// ____ +// __| |__ "chop 'em +// ` \ round!" +// ___|| ~ _ -bankers +// | | __ +// | | | __|__|__ +// |_____: / | $$$ | +// |________| + +// ChopPrecisionAndRound: Remove a Precision amount of rightmost digits and +// perform bankers rounding on the remainder (gaussian rounding) on the digits +// which have been removed. +// +// Mutates the input. Use the non-mutative version if that is undesired +func ChopPrecisionAndRound(d *big.Int) *big.Int { + // remove the negative and add it back when returning + if d.Sign() == -1 { + // make d positive, compute chopped value, and then un-mutate d + d = d.Neg(d) + d = ChopPrecisionAndRound(d) + d = d.Neg(d) + return d + } + + // Divide out the 'precisionInt', which truncates to a quotient and remainder. + quo, rem := d, big.NewInt(0) + quo, rem = quo.QuoRem(d, precisionInt, rem) + + return BankersRound(quo, rem, halfPrecisionInt) +} + +// BankersRound: Banker's rounding is a method commonly used in banking and +// accounting to reduce roudning bias when processing large volumes of rounded +// numbers. +// +// 1. If the remainder < half precision, round down +// 2. If the remainder > half precision, round up +// 3. If remainder == half precision, round to the nearest even number +// +// The name comes from the idea that it provides egalitarian rounding that +// doesn't consistently favor one party over another (e.g. always rounding up). +// With this method, rounding errors tend to cancel out rather than +// accumulating in one direction. +func BankersRound(quo, rem, halfPrecision *big.Int) *big.Int { + // Zero remainder after dividing precision means => no rounding is needed. + if rem.Sign() == 0 { + return quo + } + + // Nonzero remainder after dividing precision means => do banker's rounding + switch rem.Cmp(halfPrecision) { + case -1: + return quo + case 1: + return quo.Add(quo, oneInt) + default: + // default case: bankers rounding must take place + // always round to an even number + if quo.Bit(0) == 0 { + return quo + } + return quo.Add(quo, oneInt) + } +} + +// Clamp return the value if it is within the clampValue, otherwise return the clampValue. +// e.g. Clamp(1.5, 1) = 1, Clamp(-1.5, 1) = -1, Clamp(0.5, 1) = 0.5 +func Clamp(value math.LegacyDec, clampValue math.LegacyDec) math.LegacyDec { + if value.GT(clampValue) { + return clampValue + } else if value.LT(clampValue.Neg()) { + return clampValue.Neg() + } + return value +} diff --git a/x/common/dec_test.go b/x/common/dec_test.go new file mode 100644 index 00000000..3666cedc --- /dev/null +++ b/x/common/dec_test.go @@ -0,0 +1,166 @@ +package common_test + +import ( + "fmt" + "math/big" + "testing" + + "cosmossdk.io/math" + + "github.com/stretchr/testify/assert" + + "github.com/archway-network/archway/x/common" +) + +func TestSqrtBigInt(t *testing.T) { + testCases := []struct { + bigInt *big.Int + sqrtBigInt *big.Int + }{ + {bigInt: big.NewInt(1), sqrtBigInt: big.NewInt(1)}, + {bigInt: big.NewInt(4), sqrtBigInt: big.NewInt(2)}, + {bigInt: big.NewInt(250_000), sqrtBigInt: big.NewInt(500)}, + {bigInt: big.NewInt(4_819_136_400), sqrtBigInt: big.NewInt(69_420)}, + { + bigInt: new(big.Int).Mul(big.NewInt(4_819_136_400), common.BigIntPow10(32)), + sqrtBigInt: new(big.Int).Mul(big.NewInt(69_420), common.BigIntPow10(16)), + }, + { + bigInt: new(big.Int).Mul(big.NewInt(9), common.BigIntPow10(100)), + sqrtBigInt: new(big.Int).Mul(big.NewInt(3), common.BigIntPow10(50)), + }, + } + + for _, testCase := range testCases { + tc := testCase + t.Run(fmt.Sprintf(`bigInt: %s, sqrtBigInt: %s`, tc.bigInt, tc.sqrtBigInt), func(t *testing.T) { + sqrtInt, err := common.SqrtBigInt(tc.bigInt) + assert.NoError(t, err) + assert.Equal(t, tc.sqrtBigInt.String(), sqrtInt.String()) + }) + } +} + +func TestSqrtDec(t *testing.T) { + testCases := []struct { + dec math.LegacyDec + sqrtDec math.LegacyDec + }{ + // -------------------------------------------------------------------- + // Cases: 1 or higher + {dec: math.LegacyOneDec(), sqrtDec: math.LegacyOneDec()}, + {dec: math.LegacyNewDec(4), sqrtDec: math.LegacyNewDec(2)}, + {dec: math.LegacyNewDec(250_000), sqrtDec: math.LegacyNewDec(500)}, + {dec: math.LegacyNewDec(4_819_136_400), sqrtDec: math.LegacyNewDec(69_420)}, + + // -------------------------------------------------------------------- + // Cases: Between 0 and 1 + {dec: math.LegacyMustNewDecFromStr("0.81"), sqrtDec: math.LegacyMustNewDecFromStr("0.9")}, + {dec: math.LegacyMustNewDecFromStr("0.25"), sqrtDec: math.LegacyMustNewDecFromStr("0.5")}, + // ↓ dec 1e-12, sqrtDec: 1e-6 + {dec: math.LegacyMustNewDecFromStr("0.000000000001"), sqrtDec: math.LegacyMustNewDecFromStr("0.000001")}, + + // -------------------------------------------------------------------- + // The math/big library panics if you call sqrt() on a negative number. + } + + t.Run("negative sqrt should panic", func(t *testing.T) { + panicString := common.TryCatch(func() { + common.MustSqrtDec(math.LegacyNewDec(-9)) + })().Error() + + assert.Contains(t, panicString, "square root of negative number") + }) + + for _, testCase := range testCases { + tc := testCase + t.Run(fmt.Sprintf(`dec: %s, sqrtDec: %s`, tc.dec, tc.sqrtDec), func(t *testing.T) { + sqrtDec, err := common.SqrtDec(tc.dec) + assert.NoError(t, err) + assert.Equal(t, tc.sqrtDec.String(), sqrtDec.String()) + }) + } +} + +func TestBankersRound(t *testing.T) { + quo := big.NewInt(56789) + halfPrecision := big.NewInt(50000) + + testCases := []struct { + name string + quo *big.Int + rem *big.Int + rounded *big.Int + }{ + { + name: "Remainder < half precision => round down", + rem: big.NewInt(49_999), + rounded: quo, + }, + { + name: "Remainder > half precision => round up", + rem: big.NewInt(50_001), + rounded: big.NewInt(56_790), // = quo + 1 + }, + { + name: "Remainder = half precision, quotient is odd => round up", + rem: halfPrecision, + rounded: big.NewInt(56_742), + quo: big.NewInt(56_741), + }, + { + name: "Remainder = half precision, quotient is even => no change", + rem: halfPrecision, + rounded: quo, + }, + { + name: "Remainder = 0 => no change", + rem: big.NewInt(0), + rounded: quo, + }, + } + + for _, tc := range testCases { + tcQuo := quo + if tc.quo != nil { + tcQuo = tc.quo + } + rounded := common.BankersRound(tcQuo, tc.rem, halfPrecision) + assert.EqualValues(t, tc.rounded, rounded) + } +} + +func TestClamp(t *testing.T) { + tests := []struct { + value math.LegacyDec + clampValue math.LegacyDec + expected math.LegacyDec + description string + }{ + { + value: math.LegacyNewDec(15), + clampValue: math.LegacyNewDec(1), + expected: math.LegacyNewDec(1), + description: "Clamping 15 to 1", + }, + { + value: math.LegacyNewDec(-15), + clampValue: math.LegacyNewDec(1), + expected: math.LegacyNewDec(-1), + description: "Clamping -15 to 1", + }, + { + value: math.LegacyMustNewDecFromStr("0.5"), + clampValue: math.LegacyNewDec(1), + expected: math.LegacyMustNewDecFromStr("0.5"), + description: "Clamping 0.5 to 1", + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + result := common.Clamp(tt.value, tt.clampValue) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/x/common/denoms/denoms.go b/x/common/denoms/denoms.go new file mode 100644 index 00000000..5256f86c --- /dev/null +++ b/x/common/denoms/denoms.go @@ -0,0 +1,20 @@ +package denoms + +const ( // stablecoins + USDC = "uusdc" + NUSD = "unusd" + USD = "uusd" + USDT = "uusdt" +) + +const ( // volatile assets + NIBI = "unibi" + BTC = "ubtc" + ETH = "ueth" + ATOM = "uatom" + OSMO = "uosmo" + AVAX = "uavax" + SOL = "usol" + BNB = "ubnb" + ADA = "uada" +) diff --git a/x/common/error.go b/x/common/error.go new file mode 100644 index 00000000..2f640ce5 --- /dev/null +++ b/x/common/error.go @@ -0,0 +1,197 @@ +package common + +import ( + "errors" + "fmt" + "runtime/debug" + + grpccodes "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" +) + +// TryCatch is an implementation of the try-catch block from languages like C++ and JS. +// given a 'callback' function, TryCatch defers and recovers from any panics or +// errors, allowing one to safely handle multiple panics in succession. +// +// Typically, you'll write something like: `err := TryCatch(aRiskyFunction)()` +// +// Usage example: +// +// var calmPanic error = TryCatch(func() { +// panic("something crazy") +// })() +// fmt.Println(calmPanic.Error()) // prints "something crazy" +// +// Note that TryCatch doesn't return an error. It returns a function that returns +// an error. Only when you call the output of TryCatch will it "feel" like a +// try-catch block from other languages. +// +// This means that TryCatch can be used to restart go routines that panic as well. +func TryCatch(callback func()) func() error { + return func() (err error) { + defer func() { + if panicInfo := recover(); panicInfo != nil { + err = fmt.Errorf("%v, %s", panicInfo, string(debug.Stack())) + return + } + }() + callback() // calling the decorated function + return err + } +} + +// ToError converts a value to an error type if it: +// (1) is a string, +// (2) has a String() function +// (3) is already an error. +// (4) or is a slice of these cases +// I.e., the types supported are: +// string, []string, error, []error, fmt.Stringer, []fmt.Stringer +// +// The type is inferred from try catch blocks at runtime. +func ToError(v any) (out error, ok bool) { + if v == nil { + return nil, true + } + switch v := v.(type) { + case string: + return errors.New(v), true + case error: + return v, true + case fmt.Stringer: + return errors.New(v.String()), true + case []string: + return toErrorFromSlice(v) + case []error: + return toErrorFromSlice(v) + case []fmt.Stringer: + return toErrorFromSlice(v) + default: + // Cases for duck typing at runtime + + // case: error + if tcErr := TryCatch(func() { + v := v.(error) + out = errors.New(v.Error()) + })(); tcErr == nil { + return out, true + } + + // case: string + if tcErr := TryCatch(func() { + v := v.(string) + out = errors.New(v) + })(); tcErr == nil { + return out, true + } + + // case: fmt.Stringer (object with String method) + if tcErr := TryCatch(func() { + v := v.(fmt.Stringer) + out = errors.New(v.String()) + })(); tcErr == nil { + return out, true + } + + // case: []string + if tcErr := TryCatch(func() { + if maybeOut, okLocal := ToError(v.([]string)); okLocal { + out = maybeOut + } + })(); tcErr == nil { + return out, true + } + + // case: []error + if tcErr := TryCatch(func() { + if maybeOut, okLocal := ToError(v.([]error)); okLocal { + out = maybeOut + } + })(); tcErr == nil { + return out, true + } + + // case: []fmt.Stringer + if tcErr := TryCatch(func() { + if maybeOut, okLocal := ToError(v.([]fmt.Stringer)); okLocal { + out = maybeOut + } + })(); tcErr == nil { + return out, true + } + + return fmt.Errorf("invalid type: %T", v), false + } +} + +func toErrorFromSlice(slice any) (out error, ok bool) { + switch slice := slice.(type) { + case []string: + var errs []error + for _, str := range slice { + if err, okLocal := ToError(str); okLocal { + errs = append(errs, err) + } else { + return err, false + } + } + return CombineErrors(errs...), true + case []error: + return CombineErrors(slice...), true + case []fmt.Stringer: + var errs []error + for _, stringer := range slice { + if err, okLocal := ToError(stringer.String()); okLocal { + errs = append(errs, err) + } else { + return err, false + } + } + return CombineErrors(errs...), true + } + return nil, false +} + +// Combines errors into single error. Error descriptions are ordered the same way +// they're passed to the function. +func CombineErrors(errs ...error) (outErr error) { + for _, e := range errs { + switch { + case e != nil && outErr == nil: + outErr = e + case e != nil && outErr != nil: + outErr = fmt.Errorf("%s: %s", outErr, e) + } + } + return outErr +} + +func CombineErrorsGeneric(errAnySlice any) (out error, ok bool) { + err, ok := ToError(errAnySlice) + if ok { + return err, true + } else { + return err, false + } +} + +func CombineErrorsFromStrings(strs ...string) (err error) { + var errs []error + for _, s := range strs { + err, _ := ToError(s) + errs = append(errs, err) + } + return CombineErrors(errs...) +} + +func ErrNilGrpcMsg() error { + return grpcstatus.Errorf(grpccodes.InvalidArgument, "nil msg") +} + +// ErrNotImplemented: Represents an function error value. +func ErrNotImplemented() error { return fmt.Errorf("fn not implemented yet") } + +// ErrNotImplementedGprc: Represents an unimplemented gRPC method. +func ErrNotImplementedGprc() error { + return grpcstatus.Error(grpccodes.Unimplemented, ErrNotImplemented().Error()) +} diff --git a/x/common/error_test.go b/x/common/error_test.go new file mode 100644 index 00000000..7585460a --- /dev/null +++ b/x/common/error_test.go @@ -0,0 +1,181 @@ +package common_test + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/archway-network/archway/x/common" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/testutil" +) + +func newErrors(strs ...string) []error { + var errs []error + for _, s := range strs { + errs = append(errs, errors.New(s)) + } + return errs +} + +func TestCombineErrors(t *testing.T) { + testCases := []struct { + name string + errs []error + errOut error + }{ + {name: "single nil remains nil", errs: []error{nil}, errOut: nil}, + {name: "multiple nil becomes nil", errs: []error{nil, nil, nil}, errOut: nil}, + {name: "single err unaffected", errs: newErrors("err0"), errOut: errors.New("err0")}, + { + name: "multiple err coalesces - A", + errs: newErrors("err0", "err1"), + errOut: errors.New("err0: err1"), + }, + { + name: "multiple err coalesces - B", + errs: newErrors("err0", "err1", "err2", "foobar"), + errOut: errors.New(strings.Join([]string{"err0", "err1", "err2", "foobar"}, ": ")), + }, + } + + for _, testCase := range testCases { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + errOut := common.CombineErrors(tc.errs...) + assert.EqualValuesf(t, tc.errOut, errOut, + "tc.errOut: %s\nerrOut: %s", tc.errOut, errOut) + }) + } +} + +func TestCombineErrorsGeneric(t *testing.T) { + testCases := []struct { + name string + in any + out error + fail bool + }{ + // cases: error + {name: "type=err | single err unaffected", in: errors.New("err0"), out: errors.New("err0")}, + {name: "type=err | single nil remains nil", in: nil, out: nil}, + + // cases: []error + {name: "type=err | single nil remains nil", in: []error{nil}, out: nil}, + {name: "multiple nil becomes nil", in: []error{nil, nil, nil}, out: nil}, + { + name: "multiple err coalesces - A", + in: newErrors("err0", "err1"), + out: errors.New("err0: err1"), + }, + { + name: "multiple err coalesces - B", + in: newErrors("err0", "err1", "err2", "foobar"), + out: errors.New(strings.Join([]string{"err0", "err1", "err2", "foobar"}, ": ")), + }, + + // cases: string + {name: "type=string | empty string", in: "", out: errors.New("")}, + {name: "type=string | happy string", in: "happy", out: errors.New("happy")}, + + // cases: []string + {name: "type=[]string | empty string", in: []string{""}, out: errors.New("")}, + {name: "type=[]string | empty strings", in: []string{"", ""}, out: errors.New(": ")}, + {name: "type=[]string | mixed", in: []string{"", "abc", ""}, out: errors.New(": abc: ")}, + + // cases: fmt.Stringer + {name: "type=fmt.Stringer |", in: asset.Registry.Pair(denoms.USDC, denoms.NUSD), out: errors.New("uusdc:unusd")}, + + // cases: []fmt.Stringer + { + name: "type=[]fmt.Stringer | happy", + in: []fmt.Stringer{asset.Registry.Pair(denoms.BTC, denoms.NUSD), asset.Registry.Pair(denoms.ETH, denoms.NUSD)}, + out: errors.New("ubtc:unusd: ueth:unusd"), + }, + {name: "type=[]fmt.Stringer | empty", in: []fmt.Stringer{}, out: nil}, + } + + for _, testCase := range testCases { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + out, ok := common.CombineErrorsGeneric(tc.in) + if tc.fail { + assert.Falsef(t, ok, "out: %v", out) + } else { + assert.EqualValuesf(t, tc.out, out, + "tc.errOut: %s\nerrOut: %s", tc.out, out) + assert.True(t, ok) + } + }) + } +} + +func TestToError(t *testing.T) { + testCases := []testutil.FunctionTestCase{ + { + Name: "string nonempty", + Test: func() { + description := "an error description" + out, ok := common.ToError(description) + assert.True(t, ok) + assert.EqualValues(t, out.Error(), description) + }, + }, + { + Name: "error nonempty", + Test: func() { + description := "an error description" + out, ok := common.ToError(errors.New(description)) + assert.True(t, ok) + assert.EqualValues(t, out.Error(), description) + }, + }, + { + Name: "empty string creates blank error", + Test: func() { + description := "" + out, ok := common.ToError("") + assert.True(t, ok) + assert.EqualValues(t, out.Error(), description) + }, + }, + { + Name: "fail - bad type", + Test: func() { + descriptionOfBadType := int64(2200) + _, ok := common.ToError(descriptionOfBadType) + assert.False(t, ok) + }, + }, + { + Name: "nil input returns nil", + Test: func() { + err, ok := common.ToError(nil) + assert.True(t, ok) + assert.Equal(t, nil, err) + }, + }, + { + Name: "slice of strings", + Test: func() { + err, ok := common.ToError([]string{"abc", "123"}) + assert.True(t, ok) + assert.Equal(t, errors.New("abc: 123"), err) + }, + }, + { + Name: "slice of error", + Test: func() { + err, ok := common.ToError(newErrors("abc", "123")) + assert.True(t, ok) + assert.Equal(t, errors.New("abc: 123"), err) + }, + }, + } + + testutil.RunFunctionTests(t, testCases) +} diff --git a/x/common/ewma/ewma.go b/x/common/ewma/ewma.go new file mode 100644 index 00000000..323b018c --- /dev/null +++ b/x/common/ewma/ewma.go @@ -0,0 +1,42 @@ +package ewma + +import ( + "cosmossdk.io/math" +) + +type MovingAverage interface { + Add(math.LegacyDec) + Value() math.LegacyDec + Set(math.LegacyDec) +} + +func NewMovingAverage(span math.LegacyDec) MovingAverage { + return &variableEWMA{ + value: math.LegacyZeroDec(), + decay: math.LegacyMustNewDecFromStr("2").Quo(span.Add(math.LegacyOneDec())), + } +} + +type variableEWMA struct { + decay math.LegacyDec + value math.LegacyDec +} + +func (v *variableEWMA) Add(dec math.LegacyDec) { + if v.value.IsZero() { + v.value = dec + + return + } + + // val = val * (1 - decay) + dec * decay + v.value = v.value.Mul(math.LegacyOneDec().Sub(v.decay)).Add(dec.Mul(v.decay)) +} + +func (v *variableEWMA) Value() math.LegacyDec { + return v.value +} + +func (v *variableEWMA) Set(dec math.LegacyDec) { + v.value = dec +} diff --git a/x/common/ewma/ewma_test.go b/x/common/ewma/ewma_test.go new file mode 100644 index 00000000..98d9f9d5 --- /dev/null +++ b/x/common/ewma/ewma_test.go @@ -0,0 +1,41 @@ +package ewma + +import ( + "encoding/csv" + "fmt" + "io" + "os" + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" +) + +func TestSimpleEWMA(t *testing.T) { + file, err := os.Open("testdata/ewma.csv") + require.NoError(t, err) + defer file.Close() + + // discard the header + reader := csv.NewReader(file) + _, err = reader.Read() + require.NoError(t, err) + + ewma := NewMovingAverage(math.LegacyMustNewDecFromStr("240")) + + for { + record, err := reader.Read() + if err == io.EOF { + break + } + require.NoError(t, err) + + ewma.Add(math.LegacyMustNewDecFromStr(record[1])) + require.Equal( + t, + math.LegacyMustNewDecFromStr(record[2]).Mul(math.LegacyMustNewDecFromStr("100000")).TruncateInt(), + ewma.Value().Mul(math.LegacyMustNewDecFromStr("100000")).TruncateInt(), + fmt.Sprintf("value in position %s: %s should be equal to %s", record[0], ewma.Value().Mul(math.LegacyMustNewDecFromStr("1000000")).TruncateInt().String(), record[2]), + ) + } +} diff --git a/x/common/ewma/testdata/ewma.csv b/x/common/ewma/testdata/ewma.csv new file mode 100644 index 00000000..c46f1dcc --- /dev/null +++ b/x/common/ewma/testdata/ewma.csv @@ -0,0 +1,8762 @@ +value,price,exponential moving average +0,46650.01,46650.01 +1,46766.78,46650.97904564316 +2,46796.14,46652.18370086604 +3,46789.56,46653.323753141005 +4,46690.81,46653.63484232656 +5,47184.18,46658.037706705596 +6,46972.19,46660.644779678994 +7,47186.83,46665.011462005314 +8,47117.84,46668.76937518369 +9,47137.37,46672.65817704939 +10,46864.09,46674.246822883004 +11,46755.01,46674.917056718 +12,47062.37,46678.13243384067 +13,47005.55,46680.84959206606 +14,46909.2,46682.74461619829 +15,47194.78,46686.993872495405 +16,47249.99,46691.66603952864 +17,47733.16,46700.30914293504 +18,47546.74,46707.33346540031 +19,47326.98,46712.47576029326 +20,47385.22,46718.05870004187 +21,47322.2,46723.07232078841 +22,47428.16,46728.92367082336 +23,47704.35,46737.01849513188 +24,47622.14,46744.36390181129 +25,47381.21,46749.64893167177 +26,47374.28,46754.83259198985 +27,47330.75,46759.61198956669 +28,46902.11,46760.79454566988 +29,47077.7,46763.4244664527 +30,47122.49,46766.404263411605 +31,47171.04,46769.76223632935 +32,47211.36,46773.42694806105 +33,47263.18,46777.49128874105 +34,47175.95,46780.79800003781 +35,47231.3,46784.536605846624 +36,47279.57,46788.64476679396 +37,47133.07,46791.50306748447 +38,47110.2,46794.147855306175 +39,47329.68,46798.59210546961 +40,47780.0,46806.73656932463 +41,47402.3,46811.67900443397 +42,47046.95,46813.631460828714 +43,46908.29,46814.417008871635 +44,47012.47,46816.06060215901 +45,47023.47,46817.78184197512 +46,47261.73,46821.46605905417 +47,47280.0,46825.27131997488 +48,47059.43,46827.21454553526 +49,47117.42,46829.622889555714 +50,47005.39,46831.08153777517 +51,46802.86,46830.847334142185 +52,46823.58,46830.78702431528 +53,47083.37,46832.883148594825 +54,46930.85,46833.69615151105 +55,47041.11,46835.417428262 +56,46970.01,46836.53437906481 +57,46979.01,46837.71674936303 +58,47116.89,46840.033539824755 +59,47364.11,46844.382722066875 +60,47192.64,46847.27282395844 +61,47157.33,46849.8459125563 +62,46874.66,46850.05183859318 +63,46689.99,46848.72352457996 +64,46484.0,46845.696773338634 +65,46564.54,46843.363522107604 +66,46469.49,46840.26083727683 +67,46364.55,46836.313029498604 +68,45904.74,46828.582132988246 +69,45974.5,46821.494314457224 +70,46214.28,46816.45519151568 +71,46445.81,46813.379297810156 +72,46251.5,46808.71639907314 +73,46065.39,46802.547715263405 +74,46111.81,46796.8154520662 +75,46313.95,46792.80826989138 +76,46107.5,46787.12106433212 +77,46151.49,46781.846117740155 +78,46359.14,46778.3381831531 +79,46521.68,46776.208239724445 +80,46475.02,46773.70875225785 +81,46355.2,46770.235650579365 +82,46599.84,46768.821578790325 +83,46709.98,46768.33326693314 +84,46628.88,46767.17597841088 +85,46933.15,46768.5533561834 +86,47303.01,46772.98868102835 +87,46925.28,46774.25250940156 +88,46767.94,46774.20012343142 +89,46564.0,46772.455724066844 +90,45742.69,46763.909950423134 +91,46058.09,46758.05252344867 +92,46267.79,46753.98395478934 +93,46230.6,46749.640519479886 +94,46155.89,46744.71312927674 +95,45833.42,46737.15053069353 +96,46260.08,46733.1914391525 +97,46112.1,46728.0371533504 +98,46291.45,46724.414023447076 +99,46409.76,46721.80278673797 +100,46419.0,46719.28990054098 +101,46302.72,46715.83288891824 +102,46454.0,46713.66000187327 +103,46399.33,46711.05145413988 +104,46626.53,46710.35003128395 +105,46850.75,46711.51517625255 +106,46809.4,46712.32749844132 +107,46271.93,46708.67274741692 +108,46228.27,46704.68600262507 +109,46429.36,46702.40113953275 +110,46682.79,46702.23839148683 +111,46611.46,46701.48504383964 +112,46308.23,46698.22151650487 +113,46007.99,46692.49345412724 +114,45922.29,46686.10172421747 +115,44673.22,46669.39731156837 +116,43958.56,46646.90073636863 +117,43604.0,46621.648448100015 +118,43415.84,46595.04422861371 +119,43421.69,46568.7093387497 +120,43656.0,46544.53747701734 +121,43525.91,46519.48662658566 +122,43248.4,46492.34067947706 +123,42817.38,46461.84308047726 +124,43007.21,46433.17392628243 +125,43038.12,46404.99920490249 +126,43091.6,46377.50211606513 +127,43222.22,46351.31720223887 +128,43050.0,46323.92037898377 +129,42591.58,46292.946599905066 +130,42874.38,46264.57675260295 +131,42865.15,46236.36574220791 +132,43047.4,46209.90129621449 +133,43123.15,46184.28510288491 +134,42802.24,46156.218338545616 +135,42947.79,46129.592377229885 +136,42878.24,46102.61019982549 +137,42994.68,46076.818247959716 +138,43353.88,46054.221250051334 +139,43409.03,46032.2694554451 +140,43206.67,46008.82049730863 +141,43105.1,45984.7232317708 +142,43163.57,45961.311171756104 +143,43080.01,45937.399958712485 +144,42787.99,45911.26377648251 +145,42988.26,45887.006483731624 +146,42795.77,45861.353068928875 +147,41789.97,45827.56565756848 +148,41680.0,45793.14602555546 +149,41835.97,45760.306390488615 +150,41520.0,45725.117125837256 +151,41691.61,45691.644037656035 +152,42089.3,45661.74906638918 +153,42409.0,45634.75529820338 +154,42167.9,45605.9847148158 +155,42249.99,45578.13413626961 +156,42393.02,45551.701653810946 +157,41471.53,45517.84130813617 +158,41876.11,45487.61947155413 +159,41297.41,45452.845948968614 +160,42131.75,45425.284986736515 +161,41978.79,45396.68336858932 +162,41695.07,45365.9645854475 +163,41764.32,45336.07541876329 +164,41910.96,45307.65122441671 +165,41862.38,45279.05976197342 +166,41522.5,45247.884992164516 +167,41553.86,45217.229183100906 +168,41992.23,45190.465704402974 +169,41880.01,45162.99304295565 +170,41870.9,45135.672768740245 +171,41796.03,45107.95789099136 +172,41880.0,45081.16985870098 +173,41954.47,45055.22214203126 +174,41901.74,45029.05216574884 +175,41902.21,45003.10326810777 +176,41948.85,44977.7567679575 +177,42138.03,44954.190570713035 +178,41907.21,44928.90442489799 +179,41918.02,44903.91783216025 +180,41851.23,44878.584323179675 +181,41884.04,44853.73333294582 +182,41737.01,44827.868409020964 +183,41600.72,44801.08709442328 +184,41342.06,44772.381475382426 +185,40846.58,44739.80221002655 +186,40684.09,44706.14484728774 +187,40837.51,44674.03999378327 +188,40976.06,44643.35136312947 +189,41775.18,44619.5491111533 +190,41912.88,44597.08712682837 +191,41688.05,44572.94573988374 +192,41774.19,44549.719551171016 +193,41800.81,44526.907023775406 +194,42090.34,44506.68655054906 +195,41835.6,44484.519857183506 +196,42065.29,44464.44326085833 +197,41762.99,44442.0245615981 +198,41866.0,44420.64676440641 +199,41784.63,44398.771106610504 +200,41657.45,44376.02155385855 +201,41503.75,44352.18527540329 +202,41636.97,44329.65236855347 +203,41854.25,44309.10961030821 +204,41737.43,44287.767870803575 +205,41467.75,44264.3652328716 +206,41530.99,44241.68162098055 +207,41611.09,44219.85098512179 +208,42127.99,44202.491142921615 +209,42084.96,44184.918270366245 +210,41954.99,44166.41264156652 +211,42678.95,44154.068553254765 +212,42414.64,44139.633461526515 +213,42290.28,44124.28612989559 +214,42193.23,44108.26076782177 +215,41851.22,44089.53013904317 +216,41644.58,44069.24009639551 +217,41790.01,44050.32532381132 +218,41905.0,44032.52179415313 +219,41946.05,44015.20667552945 +220,42164.36,43999.846952081076 +221,42006.0,43983.30050434596 +222,41914.84,43966.134857007 +223,42034.99,43950.1087586086 +224,41803.54,43932.29490998944 +225,41788.54,43914.50441281111 +226,41765.01,43896.666284903964 +227,41564.26,43877.31021614957 +228,40931.61,43852.86457120228 +229,40753.0,43827.139554013884 +230,40768.6,43801.75748302622 +231,40973.56,43778.28696449488 +232,41892.96,43762.64109756962 +233,41167.99,43741.10872331593 +234,41584.61,43723.21246835065 +235,41280.45,43702.94058064649 +236,41698.68,43686.30771275731 +237,41696.66,43669.796113481316 +238,41592.95,43652.56087602504 +239,41801.55,43637.19978991695 +240,41585.19,43620.170663029676 +241,41913.72,43606.00924673898 +242,42146.26,43593.89514510629 +243,42251.59,43582.75568332117 +244,42284.21,43571.97937059651 +245,42096.61,43559.73564137994 +246,42088.99,43547.53028336019 +247,42191.79,43536.279326651806 +248,41978.17,43523.348958795774 +249,41967.92,43510.44083465639 +250,41619.09,43494.74497710737 +251,41800.95,43480.68858725586 +252,41796.8,43466.71440810851 +253,41691.83,43451.98507692089 +254,41616.57,43436.75341653151 +255,41716.42,43422.47679066817 +256,42684.18,43416.34984634728 +257,42523.15,43408.93739948962 +258,42730.31,43403.30563683825 +259,42938.55,43399.44874358649 +260,42774.08,43394.25896148203 +261,42643.77,43388.03083732036 +262,42685.05,43382.19697145048 +263,42700.68,43376.541228948816 +264,42664.57,43370.63275401978 +265,42617.4,43364.381859795554 +266,42545.54,43357.58649166447 +267,42592.92,43351.24071165066 +268,42614.35,43345.125436035305 +269,42688.49,43339.67617930472 +270,42661.99,43334.052227609245 +271,42550.09,43327.54631700668 +272,42594.19,43321.460372467205 +273,42778.0,43316.95032788242 +274,42739.14,43312.15522142697 +275,42906.98,43308.79277145662 +276,43245.67,43308.26893102959 +277,43881.76,43313.028193012746 +278,43819.68,43317.23277232385 +279,43549.99,43319.164367574274 +280,43593.0,43321.436862449176 +281,43814.93,43325.53224118404 +282,43570.0,43327.5610192655 +283,43707.2,43330.711550225955 +284,43794.87,43334.56348756848 +285,43756.14,43338.06204783762 +286,43813.58,43342.00825490951 +287,43880.72,43346.47889179823 +288,43699.62,43349.40952340156 +289,43540.96,43350.99915391275 +290,43583.2,43352.92613188858 +291,43501.19,43354.15653743307 +292,43604.4,43356.23324666599 +293,43645.7,43358.6354603866 +294,43692.74,43361.40811216763 +295,43682.7,43364.074434888236 +296,43876.44,43368.32643127921 +297,43855.47,43372.36911649681 +298,43766.01,43375.63584582049 +299,43592.1,43377.43222884273 +300,43786.9,43380.83030163241 +301,43891.64,43385.069386266165 +302,44120.24,43391.17038721001 +303,43170.0,43389.334948312004 +304,43224.79,43387.969430068755 +305,42800.0,43383.09001571134 +306,42699.54,43377.417401473074 +307,42690.02,43371.71285872226 +308,42760.66,43366.64188064158 +309,42780.0,43361.77348329186 +310,42566.68,43355.17519712346 +311,42534.32,43348.363120798786 +312,42732.33,43343.250812742364 +313,42576.16,43336.88491388143 +314,42690.0,43331.516574347144 +315,42611.77,43325.54357373015 +316,42655.5,43319.98304614733 +317,42864.34,43316.201776054826 +318,42781.41,43311.763670029475 +319,42635.62,43306.15251924085 +320,42505.72,43299.50992572019 +321,42535.65,43293.17083919969 +322,42059.75,43282.93498161297 +323,41983.12,43272.14813529253 +324,42029.37,43261.834623796334 +325,42140.37,43252.52786343288 +326,42945.09,43249.97651186912 +327,43082.57,43248.58724621046 +328,43183.63,43248.048181926555 +329,42904.07,43245.19359120518 +330,43030.9,43243.415221153686 +331,43055.09,43241.85235624785 +332,43075.96,43240.475656196 +333,43268.35,43240.70697855123 +334,43230.0,43240.61812395744 +335,43042.4,43238.973160273155 +336,43082.04,43237.67081039537 +337,42965.85,43235.41503603525 +338,43080.0,43234.12528469886 +339,42918.27,43231.50407901671 +340,42895.77,43228.71790408712 +341,42965.67,43226.53493392873 +342,43048.54,43225.05779754758 +343,43073.99,43223.80412287914 +344,42968.18,43221.68276086355 +345,43042.65,43220.19701181074 +346,43085.01,43219.0751278953 +347,42995.23,43217.217491979165 +348,42700.0,43212.92523063494 +349,42889.22,43210.23888017324 +350,43162.19,43209.840134279686 +351,43370.01,43211.169344783586 +352,43306.35,43211.95922573974 +353,43348.2,43213.08985457177 +354,43297.97,43213.7942541189 +355,43496.85,43216.14326445816 +356,43473.72,43218.28083072822 +357,43281.73,43218.807379850805 +358,43225.92,43218.86640574416 +359,43070.51,43217.635232252505 +360,43060.0,43216.32705605124 +361,42979.6,43214.36251616699 +362,42915.41,43211.881582422866 +363,42966.51,43209.84530373056 +364,43004.91,43208.144595815786 +365,42941.14,43205.928790041384 +366,43171.67,43205.64448472983 +367,43173.02,43205.37374211797 +368,43063.92,43204.19985214189 +369,43064.22,43203.03819361789 +370,42904.4,43200.55986835965 +371,42960.0,43198.56352090438 +372,42990.01,43196.832786291074 +373,43097.88,43196.0116013426 +374,43280.64,43196.71391170491 +375,43207.81,43196.8059954252 +376,43359.08,43198.15266766234 +377,43059.54,43197.00235506763 +378,43042.16,43195.71735627038 +379,42817.01,43192.57455663329 +380,43014.28,43191.09493375667 +381,42937.0,43188.98626210724 +382,43157.21,43188.72255868727 +383,43060.66,43187.65979886414 +384,42873.86,43185.055651155715 +385,42879.99,43182.523986000895 +386,42901.91,43180.1952392291 +387,42635.91,43175.67834927699 +388,42606.26,43170.95288579752 +389,42649.7,43166.62713570792 +390,42730.98,43163.01180678088 +391,42639.51,43158.6673934466 +392,42770.01,43155.442020886876 +393,42825.25,43152.70183814093 +394,42775.48,43149.57136645511 +395,42656.61,43145.48040075839 +396,42544.28,43140.49118581434 +397,42631.58,43136.26785647149 +398,42645.25,43132.193019488324 +399,42467.49,43126.67681185772 +400,42212.29,43119.088539560136 +401,42108.49,43110.70182968827 +402,42162.86,43102.835922387945 +403,42221.15,43095.519026766466 +404,42065.0,43086.967001648074 +405,41710.21,43075.541632339795 +406,42063.78,43067.145270245695 +407,42176.34,43059.75269538888 +408,42338.0,43053.76304646449 +409,42238.63,43046.99845686728 +410,42254.78,43040.42402983934 +411,42027.18,43032.015365691295 +412,42022.52,43023.637810789296 +413,42084.88,43015.84728953793 +414,42219.79,43009.24100497745 +415,42040.0,43001.19751116021 +416,41709.99,42990.48209612983 +417,41885.84,42981.3149418051 +418,41909.98,42972.42419540008 +419,41842.4,42963.04640124739 +420,41793.31,42953.33904522044 +421,41640.32,42942.44262160866 +422,41441.51,42929.986749230164 +423,41574.09,42918.73449404983 +424,41693.15,42908.56366837307 +425,41480.65,42896.71376241147 +426,41596.98,42885.9275901093 +427,41680.93,42875.92761010839 +428,41767.25,42866.726966041104 +429,42348.55,42862.42674225653 +430,42460.89,42859.09448713407 +431,42337.58,42854.76656607902 +432,42242.57,42849.686096650985 +433,42409.54,42846.03343194849 +434,42324.74,42841.7073453763 +435,41690.59,42832.154504335835 +436,41699.55,42822.755296830976 +437,41751.99,42813.86927777014 +438,41700.0,42804.62554932392 +439,41271.21,42791.900109080576 +440,41362.88,42780.041021038414 +441,41553.2,42769.85976775179 +442,41494.2,42759.27337963767 +443,42023.15,42753.164471922835 +444,42075.1,42747.53738086953 +445,42171.24,42742.75482999095 +446,42475.99,42740.54101397443 +447,41971.31,42734.15735410742 +448,41904.33,42727.27082004844 +449,42150.0,42722.48019083642 +450,42000.01,42716.48458759296 +451,41864.47,42709.41392711501 +452,41626.0,42700.422940168 +453,41704.26,42692.15602780146 +454,41956.47,42686.05074956245 +455,41657.89,42677.51829520923 +456,41824.64,42670.44046703322 +457,41756.21,42662.853492203074 +458,41969.69,42657.10109807691 +459,41915.36,42650.94557029204 +460,41875.9,42644.51365684563 +461,41878.84,42638.15951861454 +462,41988.84,42632.7709748916 +463,42039.99,42627.85163070163 +464,41956.98,42622.284231276724 +465,42038.6,42617.44037873501 +466,42089.94,42613.06278223099 +467,42117.31,42608.94865125812 +468,42019.89,42604.060197720704 +469,42368.81,42602.10791392219 +470,42941.36,42604.92328393114 +471,43275.74,42610.49022763295 +472,43257.22,42615.85727968579 +473,43020.35,42619.21406574649 +474,42824.44,42620.91718553283 +475,42909.99,42623.31613005124 +476,42715.35,42624.07989660683 +477,41319.68,42613.25500119931 +478,41137.96,42601.01188915616 +479,40661.69,42584.9179315698 +480,40884.57,42570.80716035345 +481,39750.69,42547.403698441805 +482,39818.48,42524.757028745196 +483,38447.91,42490.92427331992 +484,38819.99,42460.46008847909 +485,38888.95,42430.821000607895 +486,38663.95,42399.56066035389 +487,39106.42,42372.23169221818 +488,39140.86,42345.415329627154 +489,38942.78,42317.17769203689 +490,38956.26,42289.28625890795 +491,38878.71,42260.982721489636 +492,38169.34,42227.027180232464 +493,38484.39,42195.96795052099 +494,38411.24,42164.559419811274 +495,38831.88,42136.90232919043 +496,38704.37,42108.41658371997 +497,38369.69,42077.38980709158 +498,38254.15,42045.6616759124 +499,37967.04,42011.814193124745 +500,38337.36,41981.32079733118 +501,36634.29,41936.947097768265 +502,36093.06,41888.45010940505 +503,36425.98,41843.11840725231 +504,36468.66,41798.5170926693 +505,36475.49,41754.34259397495 +506,36460.42,41710.4096263901 +507,36319.99,41665.67585355699 +508,36258.38,41620.80202904614 +509,35728.06,41571.899605568586 +510,35706.43,41523.22350925681 +511,35374.14,41472.19377059078 +512,35583.24,41423.32278494272 +513,34804.48,41368.39462905108 +514,35461.77,41319.376997274725 +515,35385.59,41270.133951654185 +516,35744.46,41224.27773628776 +517,35572.97,41177.378916899484 +518,35008.02,41126.18091758912 +519,34829.93,41073.92987262987 +520,35085.04,41024.229541736684 +521,34590.01,40970.83352894219 +522,34379.45,40916.1332506937 +523,34506.47,40862.94102454687 +524,35275.85,40816.57512392822 +525,35364.01,40771.325620825075 +526,34861.04,40722.27760737425 +527,35043.73,40675.15273096451 +528,35378.61,40631.19801950423 +529,35296.61,40586.92757950834 +530,34862.32,40539.42046266594 +531,35032.09,40493.7164754239 +532,35199.96,40449.78488641624 +533,35429.32,40408.121277400336 +534,35262.76,40365.42118381195 +535,35553.2,40325.485738303134 +536,35820.2,40288.09747491473 +537,35860.35,40251.35268259179 +538,35883.99,40215.109008877334 +539,35671.57,40177.403290961345 +540,35780.01,40140.91040057992 +541,35944.34,40106.08409020167 +542,35308.53,40066.27036331203 +543,35265.33,40026.428534570856 +544,35211.97,39986.47452183583 +545,35314.96,39947.7067664679 +546,34942.06,39906.166129401776 +547,34763.65,39863.489647000104 +548,35271.3,39825.38018934865 +549,35335.74,39788.12176454078 +550,35490.61,39752.4577664948 +551,36230.01,39723.225834822646 +552,35820.19,39690.835495944455 +553,35640.34,39657.22142543869 +554,35191.05,39620.15776215704 +555,35315.02,39584.430477823786 +556,35106.28,39547.26740331903 +557,35164.38,39510.894893747914 +558,35257.01,39475.592944422206 +559,34875.53,39437.41814820294 +560,34952.37,39400.19783162034 +561,34500.0,39359.53228944921 +562,33270.45,39309.000486217265 +563,33690.01,39262.36985977563 +564,33080.0,39211.06388583558 +565,33319.31,39162.16966271662 +566,33609.51,39116.08949954054 +567,34217.72,39075.439130249746 +568,34384.59,39036.510921699955 +569,35331.17,39005.76120450742 +570,36154.52,38982.09945177292 +571,36050.48,38957.770659642025 +572,37210.38,38943.26949234209 +573,36800.0,38925.48302352597 +574,36368.23,38904.26100673323 +575,36648.31,38885.53942161511 +576,36027.97,38861.82515255606 +577,36116.84,38839.045192783815 +578,36298.0,38817.95768081051 +579,36405.01,38797.9332187291 +580,35939.0,38774.20763185168 +581,36037.16,38751.49354362054 +582,35834.99,38727.290194710826 +583,36035.0,38704.94753749331 +584,36059.27,38682.991707306646 +585,36389.86,38663.9615686568 +586,36410.05,38645.256908336 +587,36385.0,38626.49958959462 +588,36497.26,38608.8295515067 +589,36479.25,38591.1566921581 +590,36285.7,38572.02427147629 +591,36522.01,38555.01170490803 +592,36805.52,38540.4931015478 +593,37245.24,38529.74411315322 +594,37299.99,38519.5386848283 +595,37039.99,38507.26027250608 +596,36836.91,38493.398444518476 +597,36544.92,38477.22849892081 +598,36769.99,38463.06054457292 +599,36934.26,38450.373403124184 +600,36618.66,38435.17246201942 +601,36833.76,38421.882732044156 +602,36800.15,38408.42436912263 +603,37363.81,38399.75537020875 +604,37762.15,38394.46403933565 +605,37575.0,38387.66350788888 +606,37284.0,38378.50447462839 +607,37321.95,38369.73638770201 +608,37571.86,38363.1150068912 +609,37620.01,38356.94816036099 +610,37910.63,38353.24427521276 +611,38045.91,38350.69378330228 +612,37737.5,38345.60503821264 +613,38170.01,38344.14781797851 +614,38201.88,38342.96717218616 +615,37962.11,38339.80653175308 +616,38234.95,38338.93635306633 +617,38032.27,38336.391404078226 +618,37950.7,38333.19064553816 +619,37548.79,38326.681096612534 +620,36989.1,38315.58083854936 +621,36311.23,38298.94722163194 +622,36489.99,38283.93512850637 +623,36787.43,38271.51599880922 +624,36975.1,38260.757359814954 +625,35868.54,38240.90493359243 +626,35995.29,38222.2691250149 +627,35730.42,38201.58987916416 +628,35912.56,38182.59378058189 +629,35953.76,38164.09723468495 +630,36113.73,38147.081738961424 +631,36150.76,38130.51475357585 +632,36420.01,38116.3196933802 +633,36505.31,38102.95031833141 +634,36301.17,38087.99778456932 +635,36670.15,38076.23141291315 +636,36650.16,38064.3967953786 +637,36806.08,38053.95433234641 +638,36856.18,38044.014296393325 +639,36625.3,38032.240733767656 +640,36513.66,38019.638404026846 +641,36266.37,38005.08845876521 +642,36191.5,37990.03793213645 +643,36194.34,37975.13587460835 +644,35574.0,37955.209435814926 +645,36168.83,37940.384710206505 +646,36683.88,37929.957285225544 +647,37131.0,37923.32693431081 +648,37129.08,37916.73567344516 +649,36862.77,37907.98907034603 +650,36928.66,37899.861858143995 +651,37341.84,37895.230971354424 +652,37210.01,37889.54449026435 +653,37196.06,37883.789432253856 +654,36896.94,37875.599810409425 +655,36788.86,37866.58122277117 +656,36643.79,37856.43357776893 +657,36473.29,37844.95520782894 +658,36596.93,37834.59815216232 +659,36433.29,37822.969038866366 +660,36268.18,37810.066225265815 +661,36890.17,37802.432231695144 +662,36749.65,37793.695449689374 +663,37169.99,37788.51947085378 +664,37082.54,37782.66072005832 +665,36832.16,37774.77274727775 +666,37022.43,37768.52923900159 +667,37205.88,37763.85995071112 +668,37730.18,37763.58044904547 +669,37780.0,37763.71671087912 +670,37642.62,37762.71175892162 +671,37696.21,37762.159877104845 +672,37771.58,37762.23805239858 +673,37520.01,37760.22786109237 +674,37524.39,37758.2707004194 +675,37715.0,37757.91160746986 +676,37793.51,37758.20702981451 +677,37644.0,37757.25925363348 +678,37729.08,37757.02540090623 +679,37822.15,37757.56585401074 +680,37767.6,37757.64912493181 +681,37690.66,37757.09319858383 +682,37740.1,37756.95217618894 +683,37723.57,37756.675145681154 +684,37826.86,37757.25759260496 +685,38129.98,37760.35072461654 +686,37599.38,37759.014867980724 +687,37543.79,37757.22876949126 +688,37519.07,37755.252348167676 +689,37628.76,37754.20261913724 +690,37781.35,37754.42790860499 +691,37831.0,37755.06336164561 +692,38319.8,37759.74997275228 +693,38336.71,37764.53802277094 +694,38120.2,37767.48957444919 +695,38150.01,37770.66401781476 +696,37808.09,37770.97460687854 +697,37916.8,37772.18477611606 +698,37940.63,37773.58266179145 +699,37899.99,37774.63168534505 +700,38081.78,37777.18063401439 +701,38122.67,37780.04776568232 +702,38090.01,37782.62006638206 +703,38228.0,37786.31616541623 +704,37992.01,37788.02316819286 +705,37881.93,37788.80247800039 +706,38173.57,37791.99556946927 +707,38095.46,37794.51394648613 +708,37921.37,37795.5666938182 +709,37945.42,37796.810289720124 +710,37825.49,37797.048295614564 +711,37962.13,37798.41826826507 +712,37822.24,37798.61595898487 +713,37975.0,37800.0797269601 +714,37813.89,37800.19433503512 +715,37505.73,37797.75064760745 +716,37665.0,37796.648982482075 +717,37713.0,37795.95480005484 +718,37767.09,37795.715258145676 +719,37870.01,37796.33181201999 +720,36821.39,37788.241008600744 +721,36804.37,37780.07610396505 +722,36965.9,37773.31945579937 +723,36998.67,37766.890829610165 +724,36894.99,37759.65513807813 +725,36981.0,37753.193269712334 +726,36901.05,37746.12154133298 +727,37098.68,37740.74858248374 +728,37124.29,37735.632743624956 +729,37185.01,37731.06326027537 +730,37274.01,37727.27028716105 +731,37245.0,37723.26804411408 +732,37063.91,37717.79619312558 +733,37225.71,37713.71249027806 +734,37484.04,37711.80649450812 +735,37694.46,37711.66254019685 +736,38304.62,37716.5833489919 +737,38449.98,37722.66962825338 +738,38446.43,37728.675938392356 +739,38400.0,37734.247092430596 +740,38432.32,37740.040228592996 +741,38395.17,37745.476990181436 +742,38385.61,37750.789297316864 +743,38439.04,37756.50092140552 +744,38326.19,37761.22863160132 +745,38223.22,37765.06258486604 +746,38458.41,37770.816505323586 +747,38570.11,37777.44964635825 +748,38541.68,37783.791806969384 +749,38433.9,37789.186895708226 +750,38275.0,37793.21853972725 +751,38420.65,37798.425439812505 +752,38576.82,37804.88514570618 +753,38239.32,37808.49041420654 +754,38231.36,37811.999705374954 +755,38402.86,37816.903110309606 +756,38624.67,37823.60656997509 +757,38840.98,37832.049503004346 +758,38492.1,37837.52710048979 +759,38604.97,37843.89592123261 +760,39092.04,37854.253963380055 +761,38846.32,37862.486876547024 +762,38516.49,37867.91428835991 +763,38356.04,37871.96512414116 +764,38491.19,37877.10391979144 +765,38763.41,37884.4591569716 +766,38682.6,37891.08273243242 +767,38680.51,37897.63399606368 +768,38539.88,37902.96383842 +769,38425.01,37907.296171711125 +770,38598.61,37913.03321592929 +771,38540.29,37918.2386664195 +772,38412.1,37922.337100723074 +773,38404.67,37926.339863372676 +774,38262.57,37929.13015496294 +775,38268.32,37931.94500844873 +776,38430.3,37936.0807345197 +777,38505.0,37940.80205622494 +778,38540.64,37945.77996447203 +779,38473.75,37950.161458542796 +780,38692.33,37956.320533575636 +781,38350.65,37959.59297728041 +782,37695.24,37957.399176639075 +783,37419.51,37952.93536604456 +784,37311.15,37947.60934640934 +785,37449.99,37943.47972527731 +786,37463.57,37939.49707195551 +787,37538.91,37936.172697914386 +788,37537.21,37932.861804155764 +789,36898.85,37924.280793332895 +790,36992.97,37916.55207305627 +791,36892.03,37908.04981518859 +792,36867.89,37899.41778352728 +793,36831.1,37890.552075780164 +794,36942.68,37882.68591747493 +795,36801.12,37873.71026670751 +796,36885.69,37865.510928394586 +797,36980.01,37858.16237297223 +798,36907.86,37850.27604622557 +799,36995.5,37843.18246907847 +800,37049.98,37836.59987597409 +801,36594.0,37826.287843808335 +802,36361.43,37814.131347179224 +803,36754.99,37805.34179243085 +804,36667.21,37795.89671531524 +805,36571.39,37785.73483386034 +806,36491.31,37774.99271905652 +807,36876.56,37767.536845869334 +808,36668.57,37758.41678905714 +809,36810.65,37750.55150450065 +810,36742.68,37742.187425625125 +811,36538.76,37732.20047603488 +812,36359.06,37720.80511938729 +813,36929.99,37714.24233831354 +814,36913.08,37707.59368820306 +815,37289.86,37704.12701859142 +816,37156.61,37699.58330889356 +817,37149.48,37695.018136205654 +818,37306.17,37691.79118071847 +819,37282.81,37688.397146023715 +820,37270.6,37684.929949791156 +821,37521.17,37683.57094605845 +822,37969.5,37685.943801277885 +823,37858.99,37687.37986931707 +824,37934.03,37689.426758368376 +825,37995.23,37691.96454460599 +826,37873.08,37693.467577430834 +827,37800.02,37694.35182990029 +828,37946.81,37696.44691844884 +829,37415.8,37694.117898378725 +830,37939.57,37696.15484528014 +831,39481.72,37710.97281336911 +832,40249.05,37732.03569458596 +833,40497.48,37754.985439859105 +834,40452.9,37777.37477230841 +835,40701.71,37801.643114446924 +836,40568.01,37824.600515986785 +837,40599.97,37847.63262788731 +838,40617.2,37870.6165894816 +839,41550.0,37901.15089164358 +840,41465.14,37930.727564741974 +841,41262.32,37958.375634744116 +842,41350.85,37986.5289489786 +843,41428.66,38015.09435189165 +844,41451.66,38043.613568888395 +845,41509.99,38072.38017827522 +846,41485.01,38100.700757708626 +847,41368.14,38127.81643606789 +848,41428.44,38155.207502988495 +849,41535.96,38183.26354030809 +850,41552.2,38211.2215192267 +851,41427.85,38237.915531515275 +852,41372.94,38263.93233208362 +853,41503.15,38290.81380650616 +854,41443.87,38316.98024794595 +855,41731.74,38345.31850314971 +856,41276.89,38369.646897314444 +857,41575.72,38396.25331310437 +858,41659.8,38423.336688099356 +859,41532.76,38449.141030936706 +860,41631.01,38475.54658254719 +861,41621.19,38501.65150717336 +862,41571.23,38527.12518761175 +863,41374.69,38550.75643086809 +864,41462.87,38574.92334845424 +865,41591.71,38599.958922326 +866,41517.18,38624.16822587516 +867,41429.06,38647.445336033874 +868,41522.43,38671.30412992571 +869,41558.58,38695.26492552798 +870,41441.96,38718.059075523604 +871,41588.0,38741.87601265619 +872,41621.86,38765.776294708834 +873,41630.0,38789.54578603905 +874,41570.0,38812.6200948686 +875,41510.72,38835.0109654506 +876,41604.13,38857.99120640122 +877,41679.99,38881.41028352652 +878,41300.0,38901.48156748067 +879,41494.99,38923.00445903684 +880,41618.8,38945.37620626476 +881,41619.99,38967.572171358 +882,41486.59,38988.476883628886 +883,41692.8,39010.91939911744 +884,41552.61,39032.012267174556 +885,41632.61,39053.593991098416 +886,41575.81,39074.52524428433 +887,42361.35,39101.80179827367 +888,41801.49,39124.20584974028 +889,42083.75,39148.76638210759 +890,42306.91,39174.97504283699 +891,42784.68,39204.93110057278 +892,42783.52,39234.628933763044 +893,42782.53,39264.07209613846 +894,42740.51,39292.92220322445 +895,42712.8,39321.302931828395 +896,42584.63,39348.384484261354 +897,42401.3,39373.71988273222 +898,42534.0,39399.94627374689 +899,42692.25,39427.268296371396 +900,42610.62,39453.68615283304 +901,42775.74,39481.25506442779 +902,43376.83,39513.58348712963 +903,43805.26,39549.19905985055 +904,43776.5,39584.28039545345 +905,44188.04,39622.4858693501 +906,44052.88,39659.252625621055 +907,44243.0,39697.29202291881 +908,44118.1,39733.97922604812 +909,44073.94,39769.99549803112 +910,44050.0,39805.51420759103 +911,43829.99,39838.91234694712 +912,43836.71,39872.08909095586 +913,44026.01,39906.56146364503 +914,43939.99,39940.03389963137 +915,44162.22,39975.07278843111 +916,44338.06,40011.28015118273 +917,44867.12,40051.577577314 +918,44774.24,40090.769796589404 +919,44842.26,40130.201250559614 +920,44000.0,40162.315763003106 +921,43844.1,40192.86998903628 +922,43837.14,40223.11289369158 +923,43978.64,40254.27909374393 +924,43445.93,40280.76582325643 +925,43620.99,40308.485525968 +926,43562.59,40335.49054235 +927,43406.53,40360.976346977804 +928,43009.26,40382.95380467923 +929,42955.06,40404.29908430845 +930,42963.29,40425.53552344282 +931,42985.41,40446.77929503251 +932,44257.22,40478.40120959656 +933,44159.9,40508.95306677833 +934,44041.89,40538.27204547727 +935,44021.57,40567.179082444265 +936,44218.03,40597.47660043228 +937,43852.25,40624.48716806355 +938,43317.14,40646.832834718625 +939,43321.58,40669.02990662967 +940,43434.95,40691.98360035059 +941,43567.22,40715.84448333523 +942,43679.26,40740.43714322457 +943,43798.9,40765.81857772063 +944,43374.51,40787.467469191826 +945,43372.26,40808.918029613465 +946,43599.08,40832.07290073701 +947,43686.65,40855.76233724542 +948,44120.42,40882.854931957074 +949,43928.29,40908.128252023824 +950,44074.76,40934.407353666786 +951,43855.17,40958.64604782723 +952,44070.0,40984.46641257555 +953,44044.33,41009.85947139235 +954,44146.75,41035.89175793681 +955,44455.07,41064.26668110746 +956,44743.28,41094.79791196964 +957,44443.01,41122.58390440143 +958,44531.19,41150.87109191677 +959,44358.08,41177.48693347763 +960,44099.88,41201.739158096076 +961,44239.02,41226.94480823636 +962,43773.22,41248.07572269083 +963,43748.78,41268.828455282615 +964,43760.77,41289.50846810185 +965,43851.95,41310.773543055366 +966,43849.58,41331.84247630802 +967,44066.19,41354.534157002556 +968,44189.67,41378.06225528469 +969,44377.2,41402.95136519934 +970,44627.62,41429.71210075785 +971,44815.74,41457.81191734908 +972,44797.71,41485.528913885595 +973,43628.82,41503.315561903146 +974,44367.8,41527.08721699109 +975,44936.02,41555.377115605275 +976,45367.15,41587.010085600254 +977,45314.45,41617.94319692307 +978,45199.0,41647.661510641556 +979,44355.73,41670.135108063616 +980,44077.47,41690.11299098426 +981,43721.65,41706.972219274845 +982,43935.02,41725.4622423514 +983,43471.29,41739.950439510314 +984,43012.98,41750.51500017828 +985,43162.02,41762.228734616634 +986,43104.41,41773.367168354256 +987,43310.87,41786.12652795298 +988,43068.0,41796.76448207785 +989,42826.0,41805.305855670566 +990,43214.64,41817.001574710644 +991,43330.46,41829.561395667406 +992,42966.27,41838.9946620934 +993,43470.01,41852.53005908848 +994,43420.43,41865.54167685538 +995,43332.95,41877.71933928811 +996,43549.55,41891.59345265502 +997,43729.28,41906.84396342137 +998,43493.4,41920.010403558954 +999,43648.87,41934.35778610204 +1000,43564.79,41947.88834389373 +1001,43404.71,41959.97815016846 +1002,42652.95,41965.72895390151 +1003,42725.35,41972.03286299777 +1004,42334.2,41975.038399404424 +1005,42532.99,41979.66870314381 +1006,42144.59,41981.0373446115 +1007,42366.0,41984.2320554446 +1008,42314.89,41986.976104777015 +1009,42439.51,41990.73157278717 +1010,42441.01,41994.46832322047 +1011,42279.98,41996.837714729016 +1012,42286.33,41999.24014033292 +1013,42268.45,42001.47424705215 +1014,42032.57,42001.73230309321 +1015,42090.76,42002.47112215468 +1016,42268.76,42004.68098836087 +1017,42374.84,42007.75284737862 +1018,42259.49,42009.841952379626 +1019,42385.66,42012.96077435158 +1020,42260.34,42015.01371398352 +1021,42088.38,42015.62256283013 +1022,42144.17,42016.68934654108 +1023,42115.6,42017.5101818395 +1024,42704.38,42023.21034630556 +1025,42691.48,42028.75615256029 +1026,42810.01,42035.239586978874 +1027,42644.67,42040.297100779884 +1028,42610.01,42045.02500865723 +1029,41860.01,42043.489614394515 +1030,42146.33,42044.343061577965 +1031,42199.87,42045.633741564874 +1032,42372.41,42048.34557773446 +1033,42224.88,42049.810593686874 +1034,42208.21,42051.12511158159 +1035,42214.65,42052.48216459752 +1036,42186.35,42053.5931009909 +1037,42245.85,42055.188593928746 +1038,42404.0,42058.08329439407 +1039,42385.69,42060.80202224143 +1040,42424.27,42063.81835400705 +1041,42293.02,42065.720442355545 +1042,42552.11,42069.75687021982 +1043,42462.0,42073.011999927534 +1044,42454.4,42076.17704557129 +1045,42489.49,42079.607028595594 +1046,42519.45,42083.25717773588 +1047,42548.47,42087.117865057575 +1048,42292.33,42088.8208703268 +1049,42252.12,42090.176049826165 +1050,41965.33,42089.13998302263 +1051,42015.27,42088.52695411788 +1052,42421.79,42091.29262254843 +1053,42180.55,42092.03334767251 +1054,42353.38,42094.202199559055 +1055,42041.13,42093.761766367694 +1056,42099.98,42093.81336996631 +1057,41663.77,42090.244545319285 +1058,41690.84,42086.92998477722 +1059,41892.95,42085.32019237243 +1060,41940.0,42084.11421567224 +1061,41765.24,42081.467956621025 +1062,42154.26,42082.072039968574 +1063,42290.41,42083.80098569498 +1064,42027.43,42083.33317668506 +1065,42067.01,42083.197714637885 +1066,42205.42,42084.21200746247 +1067,42107.81,42084.40784142544 +1068,42500.07,42087.857319919836 +1069,42465.61,42090.99219693295 +1070,42500.0,42094.386452560066 +1071,42565.0,42098.291959177826 +1072,42624.97,42102.662731300836 +1073,42553.66,42106.40544722365 +1074,42227.85,42107.41328583591 +1075,42102.29,42107.370768941 +1076,42171.93,42107.9065301946 +1077,42215.33,42108.79801127183 +1078,42659.99,42113.37221864717 +1079,42525.14,42116.789378658395 +1080,42561.04,42120.47610580646 +1081,43443.33,42131.45414642218 +1082,43538.01,42143.126809107474 +1083,43535.1,42154.67845384517 +1084,43445.1,42165.38734634438 +1085,43543.2,42176.82147625024 +1086,43523.7,42187.99889138509 +1087,43665.7,42200.26197112464 +1088,43970.4,42214.95191327298 +1089,44027.5,42229.99380610888 +1090,44235.0,42246.63286165985 +1091,44214.4,42262.96287940541 +1092,44161.8,42278.72086380868 +1093,44230.9,42294.92152054056 +1094,44235.0,42311.02175688463 +1095,44190.3,42326.617426951976 +1096,44110.9,42341.42475120963 +1097,43928.2,42354.5930105357 +1098,44114.2,42369.1955581661 +1099,43989.6,42382.64289793236 +1100,44129.8,42397.14212699517 +1101,43975.0,42410.236383202675 +1102,44164.8,42424.797077118004 +1103,44529.6,42442.2643212913 +1104,44195.1,42456.81067547145 +1105,43987.2,42469.51100181609 +1106,44066.1,42482.76070304583 +1107,43970.9,42495.11040675499 +1108,43886.9,42506.66052786076 +1109,44044.9,42519.4260006586 +1110,44026.3,42531.931179076375 +1111,44165.5,42545.48776680188 +1112,44134.5,42558.674590313894 +1113,44002.8,42570.6590335478 +1114,44092.8,42583.290908788076 +1115,44160.6,42596.380610789834 +1116,44035.8,42608.32599991192 +1117,43638.9,42616.878481240456 +1118,43492.2,42624.142560234315 +1119,43588.5,42632.14552653943 +1120,43630.0,42640.426476526656 +1121,43560.2,42648.0594518252 +1122,43817.8,42657.76684226648 +1123,44202.2,42670.58371494477 +1124,44078.7,42682.26932726888 +1125,44050.4,42693.623108785316 +1126,44000.0,42704.4644107871 +1127,43860.7,42714.05972688016 +1128,44050.2,42725.148027901894 +1129,44001.9,42735.7434799525 +1130,43994.2,42746.18710252551 +1131,43574.3,42753.059408728615 +1132,43600.0,42760.087961353274 +1133,43613.8,42767.172708561964 +1134,43819.9,42775.90903463199 +1135,43902.4,42785.257507373644 +1136,43461.4,42790.86864839129 +1137,43214.7,42794.385921018744 +1138,43050.0,42796.507199682484 +1139,43158.2,42799.50879968512 +1140,43207.5,42802.89461877487 +1141,42242.6,42798.244870901224 +1142,41919.9,42790.9557018481 +1143,41907.7,42783.625779011185 +1144,41968.8,42776.86373935134 +1145,41730.0,42768.17607346461 +1146,41265.0,42755.70158322839 +1147,41177.1,42742.6011551518 +1148,40865.6,42727.02438208001 +1149,40643.4,42709.73289343205 +1150,40691.7,42692.9857324907 +1151,40491.7,42674.71780110074 +1152,40545.0,42657.043794452606 +1153,40766.5,42641.35463433267 +1154,40745.5,42625.62140085273 +1155,40677.0,42609.45026889545 +1156,40492.7,42591.883876622465 +1157,40607.9,42575.419280135975 +1158,40690.0,42559.772647105805 +1159,40637.0,42543.816027627756 +1160,40837.4,42529.654898767774 +1161,40846.2,42515.684318695014 +1162,40769.6,42501.19399239879 +1163,40167.2,42481.82474764859 +1164,40418.0,42464.69757131956 +1165,40366.0,42447.28099396421 +1166,40460.0,42430.789035508074 +1167,39805.1,42408.99908500593 +1168,39923.9,42388.37585608473 +1169,40016.5,42368.69223902179 +1170,40131.4,42350.12549844899 +1171,40196.2,42332.25059804692 +1172,40014.1,42313.01283374778 +1173,39952.3,42293.42185587435 +1174,39921.6,42273.73868694594 +1175,39966.8,42254.593967552195 +1176,40194.9,42237.50107155591 +1177,40142.1,42220.111851045076 +1178,40138.7,42202.83872365051 +1179,40107.6,42185.45085042519 +1180,40198.4,42168.960801873945 +1181,40214.1,42152.73789065507 +1182,40347.5,42137.75666334673 +1183,40245.8,42122.05577817372 +1184,39961.4,42104.12502482788 +1185,39913.7,42085.94722379197 +1186,39926.2,42068.02401031652 +1187,39822.8,42049.39144591555 +1188,39743.7,42030.25707706978 +1189,39994.0,42013.35867808995 +1190,39850.6,41995.41047329252 +1191,39950.1,41978.436942393826 +1192,39929.5,41961.433316315866 +1193,40126.1,41946.20233443773 +1194,40099.9,41930.88032336356 +1195,39959.0,41914.51617130246 +1196,40042.0,41898.976618013636 +1197,39903.3,41882.41498632888 +1198,39991.9,41866.726065280505 +1199,40052.6,41851.67107718689 +1200,39909.0,41835.549325509 +1201,39977.1,41820.12650952967 +1202,39827.8,41803.592679575064 +1203,39778.1,41786.78361169478 +1204,39532.7,41768.07752363092 +1205,39340.9,41747.93497156759 +1206,38782.8,41723.32804234297 +1207,38770.4,41698.82241543556 +1208,38142.4,41669.308536469296 +1209,38160.0,41640.18564405047 +1210,38166.7,41611.3600370459 +1211,38316.0,41584.01265084635 +1212,38189.9,41555.84574088082 +1213,38184.8,41527.87025755401 +1214,38180.2,41500.0887616407 +1215,38308.9,41473.605867353224 +1216,38246.3,41446.823246047396 +1217,38258.3,41420.36247222128 +1218,38413.2,41395.40676705762 +1219,38379.1,41370.37517562976 +1220,38298.0,41344.87828620544 +1221,38227.8,41319.01041661038 +1222,38795.6,41298.0692513273 +1223,38368.1,41273.754153805916 +1224,38719.2,41252.55453427226 +1225,39111.8,41234.78893647747 +1226,39087.9,41216.97243078056 +1227,38900.1,41197.74527367864 +1228,39273.1,41181.773113731106 +1229,39204.8,41165.366697849524 +1230,39180.0,41148.8906256682 +1231,39266.2,41133.266637073444 +1232,39045.1,41115.937453363294 +1233,38913.6,41097.66079399928 +1234,38282.2,41074.29597413207 +1235,37549.9,41045.04787476168 +1236,37523.7,41015.82507082175 +1237,37538.5,40986.96760135436 +1238,38652.7,40967.59608599042 +1239,38804.5,40949.64508112743 +1240,38679.2,40930.80321323426 +1241,38516.1,40910.76418241904 +1242,37721.4,40884.29642986785 +1243,38114.5,40861.31056737932 +1244,38230.5,40839.478114538 +1245,37029.4,40807.85920902316 +1246,37525.5,40780.6197135126 +1247,36986.4,40749.13241298552 +1248,37211.4,40719.77363777403 +1249,37234.8,40690.85269472196 +1250,36989.9,40660.13939435082 +1251,36580.0,40626.27931638941 +1252,36605.0,40592.907703805264 +1253,36725.6,40560.81386393966 +1254,36842.3,40529.95482772439 +1255,36748.6,40498.57428973498 +1256,37081.2,40470.21433712307 +1257,37112.5,40442.34948785234 +1258,37682.5,40419.44617260046 +1259,37504.7,40395.25740768261 +1260,37621.7,40372.24033375994 +1261,37824.9,40351.1005799528 +1262,38123.8,40332.61675771253 +1263,37744.7,40311.14027009666 +1264,37688.4,40289.374790676775 +1265,37619.9,40267.22147291183 +1266,37694.8,40245.87357687107 +1267,38039.6,40227.56425258168 +1268,37883.0,40208.10728782997 +1269,37863.2,40188.647476312704 +1270,37900.0,40169.65455119808 +1271,38212.0,40153.40845533752 +1272,38123.3,40136.56108226418 +1273,38043.3,40119.18962100058 +1274,37670.4,40098.86771543211 +1275,37730.4,40079.21238169409 +1276,37980.1,40061.79236192899 +1277,37955.0,40044.30860788808 +1278,37966.6,40027.066212801874 +1279,38121.6,40011.253215185265 +1280,38647.3,39999.9341013663 +1281,38846.5,39990.36203413505 +1282,38847.6,39980.87853177708 +1283,38757.2,39970.72352321461 +1284,38995.0,39962.62623256553 +1285,38935.4,39954.101533540095 +1286,38640.3,39943.198616249305 +1287,38504.9,39931.26252814765 +1288,38626.0,39920.430473972156 +1289,38072.4,39905.094121491056 +1290,37762.7,39887.314917163334 +1291,37602.8,39868.35628714538 +1292,37664.5,39850.06702335165 +1293,37500.1,39830.56522232798 +1294,37625.1,39812.26260637505 +1295,37227.8,39790.81478391551 +1296,36741.0,39765.505117658955 +1297,36923.7,39741.92167269913 +1298,35896.5,39710.009459647685 +1299,34907.8,39670.15709898671 +1300,35049.9,39631.8147164225 +1301,34658.1,39590.53907562232 +1302,34876.9,39551.421738895166 +1303,35282.3,39515.99334272176 +1304,35490.3,39482.5850992137 +1305,35325.0,39448.082318307366 +1306,35184.0,39412.69574305171 +1307,35425.4,39379.60615182306 +1308,35244.5,39345.289918197974 +1309,35449.1,39312.95639190587 +1310,35619.6,39282.306131392135 +1311,35892.2,39254.17247055071 +1312,36119.8,39228.161080753605 +1313,35815.1,39199.8369224071 +1314,36372.6,39176.37437533318 +1315,37272.3,39160.572928234986 +1316,38412.7,39154.36651389279 +1317,38384.1,39147.97426066547 +1318,37862.3,39137.30476472634 +1319,38315.1,39130.48148867052 +1320,38211.9,39122.85840577699 +1321,38547.8,39118.086136849386 +1322,38779.7,39115.27795314109 +1323,38899.4,39113.48643485777 +1324,38703.8,39110.086547431565 +1325,38438.9,39104.516534589806 +1326,38695.4,39101.121376626405 +1327,38462.5,39095.82161416478 +1328,38328.9,39089.457119441424 +1329,38656.3,39085.862454549795 +1330,38564.4,39081.534965300416 +1331,38730.0,39078.61766268381 +1332,39338.1,39080.77104307648 +1333,39340.2,39082.923980478336 +1334,38990.0,39082.15282711337 +1335,39388.5,39084.695127303305 +1336,39360.5,39086.983964421124 +1337,38965.0,39085.97164936369 +1338,38645.2,39082.31379335237 +1339,38667.3,39078.86969548223 +1340,39091.7,39078.9761710384 +1341,38945.8,39077.87097459825 +1342,39400.1,39080.54507439412 +1343,39205.4,39081.58121485558 +1344,39748.3,39087.11415083188 +1345,39686.8,39092.09079688307 +1346,39540.9,39095.81535458529 +1347,39380.0,39098.17373338541 +1348,39354.3,39100.29926256894 +1349,39009.7,39099.54740146878 +1350,39123.9,39099.74949772215 +1351,39199.8,39100.57979234686 +1352,38772.4,39097.85630859295 +1353,38780.8,39095.22513590753 +1354,38983.1,39094.294636854356 +1355,38921.0,39092.856507087934 +1356,38977.3,39091.89753192537 +1357,39069.1,39091.70834078906 +1358,39187.2,39092.500802691226 +1359,39192.0,39093.32652217097 +1360,39129.2,39093.62422738117 +1361,39229.8,39094.7543167805 +1362,39090.9,39094.72233074912 +1363,39238.4,39095.914676552035 +1364,39303.0,39097.633226954094 +1365,39420.9,39100.315938763604 +1366,39126.7,39100.53489362864 +1367,39099.3,39100.52464554873 +1368,38766.5,39097.752656788994 +1369,38228.3,39090.53728204386 +1370,38512.8,39085.74278177793 +1371,38584.1,39081.57977114077 +1372,38530.5,39077.00649503172 +1373,38563.1,39072.741710840586 +1374,38757.2,39070.12310743112 +1375,38770.3,39067.63494886323 +1376,38787.2,39065.30768787682 +1377,39053.7,39065.21135851684 +1378,39349.6,39067.5714302304 +1379,39463.4,39070.85631462683 +1380,39733.7,39076.357092098806 +1381,39029.9,39075.97155606479 +1382,38773.2,39073.4589290435 +1383,39329.1,39075.58043170704 +1384,39071.5,39075.54656920325 +1385,38956.1,39074.55531136754 +1386,38900.1,39073.10754944748 +1387,37574.7,39060.67263202468 +1388,37947.1,39051.43136536887 +1389,37385.6,39037.60703868531 +1390,37784.9,39027.211129650575 +1391,37672.0,39015.96456425929 +1392,37548.2,39003.78394546875 +1393,37948.4,38995.02557247731 +1394,37747.5,38984.67266316214 +1395,37771.7,38974.60649998238 +1396,37791.8,38964.79067840576 +1397,37811.0,38955.21565202895 +1398,37959.9,38946.95577109925 +1399,38317.1,38941.728752251955 +1400,38279.0,38936.22892858182 +1401,38077.0,38929.09839805417 +1402,38352.6,38924.31417898318 +1403,38310.4,38919.219455506136 +1404,38257.4,38913.727177867084 +1405,37976.2,38905.946869337065 +1406,39793.5,38913.31245548365 +1407,40792.8,38928.90986249208 +1408,40987.1,38945.99027857099 +1409,41415.1,38966.48081567829 +1410,41183.9,38984.882634635316 +1411,41306.4,39004.14833891221 +1412,41882.0,39028.03092531128 +1413,41633.1,39049.649755806626 +1414,43020.6,39082.60369974184 +1415,43155.0,39116.399519661 +1416,43548.7,39153.18209626133 +1417,43254.1,39187.214609985305 +1418,43148.9,39220.09166716385 +1419,43140.8,39252.628665776596 +1420,43323.5,39286.41183037596 +1421,43196.0,39318.85654547658 +1422,43439.6,39353.053586592956 +1423,43471.7,39387.233224878495 +1424,43478.7,39421.18730599984 +1425,43253.1,39452.98741134424 +1426,43569.9,39487.15266104263 +1427,43599.9,39521.283344353484 +1428,44609.6,39563.510038591216 +1429,44389.5,39603.55974781452 +1430,43796.9,39638.35925198204 +1431,43625.9,39671.450876446914 +1432,43380.1,39702.228047596735 +1433,43632.6,39734.845242222495 +1434,43785.0,39768.45648502563 +1435,43693.7,39801.03112000466 +1436,44056.3,39836.34455469342 +1437,43856.9,39869.710160048664 +1438,44128.3,39905.05115457108 +1439,44394.1,39942.30467196053 +1440,44259.0,39978.127869703596 +1441,44011.3,40011.59817783884 +1442,44212.2,40046.457943997855 +1443,44319.0,40081.91472454559 +1444,44235.1,40116.380992391685 +1445,44260.0,40150.76787212287 +1446,43920.0,40182.04780679406 +1447,43963.0,40213.42500341817 +1448,43830.0,40243.438073929225 +1449,43900.0,40273.782986178776 +1450,44135.9,40305.833749778954 +1451,44099.8,40337.31894687622 +1452,43992.4,40367.65156972372 +1453,43521.9,40393.82790524469 +1454,44818.5,40430.54717574058 +1455,44116.1,40461.13267635685 +1456,44234.9,40492.45024750742 +1457,43734.5,40519.35522470653 +1458,43598.9,40544.91161288325 +1459,43690.3,40571.01442107509 +1460,43788.4,40597.714716335875 +1461,44052.1,40626.38181412562 +1462,44047.2,40654.770346788486 +1463,43885.7,40681.58304100601 +1464,43961.0,40708.79811950388 +1465,43820.1,40734.6180521221 +1466,43783.3,40759.91831724972 +1467,43312.7,40781.103227480016 +1468,43433.1,40803.11149945114 +1469,43320.0,40823.99854094947 +1470,43430.1,40845.62593894989 +1471,43468.0,40867.388379290554 +1472,43457.0,40888.87893215952 +1473,43157.2,40907.703173386406 +1474,43389.3,40928.29733792262 +1475,43377.2,40948.620181591315 +1476,43553.8,40970.23993112168 +1477,43968.1,40995.11843791735 +1478,43409.1,41015.15147992633 +1479,42549.5,41027.884662665536 +1480,42412.1,41039.371926875785 +1481,42394.5,41050.617803001296 +1482,42254.8,41060.6110162544 +1483,42220.6,41070.2374808498 +1484,42033.5,41078.23136067677 +1485,42085.5,41086.59043652177 +1486,42482.1,41098.17142874981 +1487,42449.5,41109.385773739436 +1488,41597.1,41113.43319470425 +1489,41502.4,41116.661134997164 +1490,41402.0,41119.02909238308 +1491,41441.1,41121.70187999816 +1492,41374.8,41123.802279334275 +1493,41363.0,41125.7873226593 +1494,41394.4,41128.0164735086 +1495,41427.6,41130.502643852924 +1496,41569.3,41134.14411568817 +1497,41511.1,41137.27238028827 +1498,41656.7,41141.58298294148 +1499,41639.6,41145.71590424488 +1500,41501.9,41148.67178885695 +1501,41315.1,41150.05293583739 +1502,41101.0,41149.64585753169 +1503,40694.6,41145.869543361296 +1504,40754.1,41142.618343831324 +1505,40835.0,41140.065494504925 +1506,40610.7,41135.67241986174 +1507,39785.4,41124.466839613924 +1508,39457.4,41110.632260032064 +1509,39381.9,41096.28593422267 +1510,38978.9,41078.71426671875 +1511,39127.7,41062.52327695345 +1512,38732.3,41043.185324447615 +1513,38972.1,41025.99789436921 +1514,38851.5,41007.9522686898 +1515,39029.1,40991.530258161256 +1516,38846.3,40973.72751742963 +1517,38994.1,40957.29907330159 +1518,38972.0,40940.823562319834 +1519,39003.9,40924.74950786075 +1520,39067.5,40909.33664887436 +1521,39022.7,40893.679913199056 +1522,39118.2,40878.945640060476 +1523,39106.1,40864.23322810977 +1524,39054.7,40849.21635484745 +1525,38986.7,40833.75978758731 +1526,39100.4,40819.37505905962 +1527,39133.8,40805.386884295636 +1528,39313.7,40793.00774002762 +1529,39403.1,40781.473235961006 +1530,39380.8,40769.84939167918 +1531,39416.6,40758.61910627106 +1532,39434.8,40747.63305559661 +1533,39362.0,40736.13402608959 +1534,39437.7,40725.358639980965 +1535,39375.1,40714.153174089006 +1536,39624.5,40705.110409158806 +1537,39421.6,40694.45887049359 +1538,39478.9,40684.37124501231 +1539,39281.9,40672.73247949354 +1540,39442.1,40662.51976182139 +1541,39466.1,40652.59096711748 +1542,39457.1,40642.66988025344 +1543,39369.6,40632.104984981626 +1544,39137.3,40619.69996435937 +1545,38665.6,40603.48336714477 +1546,38240.1,40583.87022716847 +1547,38330.9,40565.17337881022 +1548,38469.8,40547.784388114706 +1549,38851.0,40533.70318987309 +1550,38786.9,40519.20689784095 +1551,38845.0,40505.313064663846 +1552,39189.9,40494.396773670785 +1553,38981.6,40481.842443598835 +1554,38868.3,40468.452049876025 +1555,38649.4,40453.356182242205 +1556,38840.0,40439.96733425679 +1557,38997.2,40427.994161358394 +1558,38954.1,40415.762674542144 +1559,38401.1,40399.04348222229 +1560,38391.5,40382.38337033663 +1561,38310.1,40365.18599796869 +1562,37770.1,40343.650014583065 +1563,37848.8,40322.94586508445 +1564,37906.3,40302.890712677116 +1565,38000.8,40283.78622543498 +1566,38138.0,40265.97887086706 +1567,37878.2,40246.16327857771 +1568,38142.6,40228.70632190902 +1569,38136.2,40211.34112421683 +1570,38012.0,40193.08933065487 +1571,38314.1,40177.4960582013 +1572,38567.6,40164.13592493822 +1573,38997.4,40154.45346912961 +1574,38833.0,40143.48705029865 +1575,39062.3,40134.51454365717 +1576,38823.3,40123.63309516209 +1577,38585.8,40110.87099478731 +1578,37961.5,40093.03389109613 +1579,37288.7,40069.7614106721 +1580,37560.5,40048.93766452545 +1581,37821.8,40030.45519428043 +1582,38199.1,40015.25722586317 +1583,37963.8,39998.232684569695 +1584,38180.7,39983.14942577659 +1585,38257.2,39968.82619402741 +1586,38596.6,39957.43842478237 +1587,38678.5,39946.82482789621 +1588,38526.7,39935.03955961492 +1589,38336.1,39921.770351651314 +1590,38262.0,39907.99632383678 +1591,38530.4,39896.56398919913 +1592,38755.0,39887.09042912279 +1593,38970.0,39879.479720167416 +1594,39044.9,39872.553747385944 +1595,38967.8,39865.04541753212 +1596,38817.9,39856.355414067126 +1597,38862.5,39848.107651294784 +1598,38752.3,39839.01381186495 +1599,38648.9,39829.13734869595 +1600,38283.9,39816.313802233744 +1601,39047.1,39809.93028520276 +1602,38668.3,39800.45617495212 +1603,38741.5,39791.66815690273 +1604,38500.1,39780.949748961626 +1605,38485.8,39770.20161826485 +1606,38535.3,39759.95347205519 +1607,38709.9,39751.23933535764 +1608,38930.0,39744.424071163805 +1609,39187.3,39739.80063488859 +1610,39381.3,39736.82552588537 +1611,41198.5,39748.95560450872 +1612,41631.2,39764.575889948486 +1613,41509.2,39779.054098330664 +1614,41590.6,39794.087674278126 +1615,41773.8,39810.51682220943 +1616,42045.6,39829.065230323875 +1617,42006.1,39847.1319089104 +1618,42209.6,39866.7374532348 +1619,42150.1,39885.68652001293 +1620,41910.0,39902.48580200452 +1621,41865.0,39918.77222688415 +1622,42207.8,39937.76830798885 +1623,42509.0,39959.10633032919 +1624,42233.4,39977.9801367165 +1625,42316.8,39997.38943018773 +1626,42029.4,40014.252588443436 +1627,42200.2,40032.393230862996 +1628,41840.5,40047.39826629152 +1629,41873.6,40062.553467401136 +1630,41890.0,40077.718998792 +1631,41937.5,40093.15286602194 +1632,41810.2,40107.40221983089 +1633,40931.0,40114.23705618084 +1634,40709.8,40119.17948724989 +1635,40765.4,40124.542313081845 +1636,39526.3,40119.57764658324 +1637,39192.8,40111.88654578172 +1638,39350.3,40105.566325484775 +1639,39219.9,40098.21639747245 +1640,39204.9,40090.80298338554 +1641,39133.5,40082.8585602869 +1642,39036.7,40074.17674650859 +1643,39055.4,40065.72216769939 +1644,39228.8,40058.776755519306 +1645,38837.8,40048.64416833658 +1646,39251.9,40042.03218353711 +1647,39155.7,40034.67672973182 +1648,39115.4,40027.04787720293 +1649,39140.9,40019.69395291079 +1650,39138.7,40012.382799774605 +1651,39405.5,40007.34642799224 +1652,39588.2,40003.86803439895 +1653,39289.9,39997.94298847033 +1654,39412.7,39993.08620018426 +1655,39407.2,39988.22407404166 +1656,39270.7,39982.26951741061 +1657,38659.0,39971.288027639566 +1658,38251.0,39957.01177844754 +1659,38703.1,39946.60587157245 +1660,38459.2,39934.26225438098 +1661,38748.7,39924.42356347325 +1662,38917.1,39916.064031826165 +1663,39111.8,39909.38964152055 +1664,39006.4,39901.89595154943 +1665,39000.1,39894.41216771914 +1666,38997.0,39886.96476383766 +1667,39878.4,39886.89368695934 +1668,39896.1,39886.97008789744 +1669,39243.8,39881.63257679456 +1670,39304.6,39876.843924704976 +1671,39070.0,39870.14812449996 +1672,38708.6,39860.50872097714 +1673,38790.6,39851.62981042961 +1674,38618.1,39841.39304851733 +1675,38776.1,39832.55244230557 +1676,38345.9,39820.215077639135 +1677,38836.4,39812.050637160806 +1678,38894.0,39804.431959673995 +1679,38713.0,39795.374433037694 +1680,39123.0,39789.79456222411 +1681,39169.1,39784.64357000648 +1682,39105.0,39779.00337440477 +1683,39073.5,39773.148574617175 +1684,39093.2,39767.50584785687 +1685,39152.8,39762.40455451366 +1686,39065.3,39756.619454476204 +1687,39092.8,39751.11057933533 +1688,39089.2,39745.617545481924 +1689,39158.2,39740.74271107959 +1690,39208.2,39736.32326949387 +1691,39054.1,39730.66166559766 +1692,39155.1,39725.88522024001 +1693,39056.4,39720.32932629611 +1694,38892.0,39713.45522400319 +1695,39018.3,39707.68630098242 +1696,39130.0,39702.892223795854 +1697,39077.5,39697.70224683489 +1698,39030.1,39692.1619792263 +1699,39006.7,39686.473498070896 +1700,39114.4,39681.726000161594 +1701,39069.2,39676.64279684075 +1702,39081.4,39671.703022593116 +1703,38792.6,39664.40756182471 +1704,38898.7,39658.053142224504 +1705,38973.1,39652.36888378281 +1706,39141.7,39648.13096773482 +1707,39170.9,39644.170544766064 +1708,39115.6,39639.78406721614 +1709,39107.4,39635.365942177 +1710,39131.4,39631.18365220043 +1711,39075.4,39626.571339734044 +1712,39055.6,39621.83298836696 +1713,38964.0,39616.37379344275 +1714,38978.8,39611.082724617496 +1715,38727.4,39603.749258023156 +1716,38475.1,39594.38287413915 +1717,38728.0,39587.192974768695 +1718,38817.3,39580.80382145111 +1719,38913.7,39575.26769015277 +1720,38897.6,39569.64389189424 +1721,38921.0,39564.26095503205 +1722,38962.9,39559.27040768739 +1723,38843.4,39553.32957442857 +1724,38672.2,39546.01729580261 +1725,38724.4,39539.19889500756 +1726,37704.0,39523.969028658954 +1727,37743.7,39509.19501182361 +1728,37817.2,39495.15355944334 +1729,38047.6,39483.140666833846 +1730,38025.0,39471.03991441199 +1731,38121.8,39459.84290267413 +1732,38500.3,39451.87989103368 +1733,38443.1,39443.5082736807 +1734,38631.1,39436.76629630576 +1735,39062.2,39433.65786231152 +1736,39060.1,39430.55779706412 +1737,39136.0,39428.11333401794 +1738,39082.3,39425.24351381862 +1739,38885.1,39420.76099503174 +1740,38832.4,39415.878331172564 +1741,38852.2,39411.200502698106 +1742,39101.0,39408.626224667416 +1743,38940.9,39404.74467923449 +1744,38824.3,39399.927710942095 +1745,38727.7,39394.34905773926 +1746,38665.4,39388.29968796549 +1747,38795.0,39383.3760391027 +1748,38710.0,39377.78785620559 +1749,38713.8,39372.27758353999 +1750,39312.0,39371.77735463095 +1751,39653.8,39374.117791521974 +1752,39564.9,39375.70104636412 +1753,38814.3,39371.04211651877 +1754,38969.1,39367.70649729455 +1755,38746.5,39362.5512566531 +1756,38768.2,39357.618881079215 +1757,38740.1,39352.49424306196 +1758,38752.9,39347.518357227425 +1759,38428.0,39339.887499491095 +1760,38347.9,39331.65523808453 +1761,38571.2,39325.344406233206 +1762,38647.7,39319.720801202224 +1763,38630.5,39314.001126503455 +1764,38838.0,39310.050909686 +1765,38709.7,39305.06874446039 +1766,39012.5,39302.64078807482 +1767,39000.8,39300.13588526922 +1768,39151.2,39298.899902818855 +1769,39277.1,39298.71899076227 +1770,39387.2,39299.45327299661 +1771,39730.7,39303.032084009086 +1772,39425.9,39304.0517347642 +1773,39642.8,39306.86292368732 +1774,39458.3,39308.11966290983 +1775,39271.0,39307.811615914725 +1776,39159.5,39306.5808141229 +1777,39501.7,39308.200060478724 +1778,40867.8,39321.14279856604 +1779,39137.9,39319.622111441015 +1780,39090.6,39317.72151300582 +1781,39298.6,39317.562828250586 +1782,39561.1,39319.5838836178 +1783,39561.0,39321.587336865785 +1784,40289.0,39329.61565772167 +1785,40488.2,39339.23046554141 +1786,40293.9,39347.1530342921 +1787,40492.8,39356.660477990925 +1788,40342.9,39364.845038339554 +1789,40612.8,39375.20151105043 +1790,40805.9,39387.07452755624 +1791,40609.0,39397.21498790847 +1792,40293.7,39404.65469755238 +1793,40417.9,39413.06337226149 +1794,39915.9,39417.23629033401 +1795,40868.3,39429.27831282087 +1796,41227.6,39444.20214424974 +1797,41085.3,39457.82121359207 +1798,40870.1,39469.54136949587 +1799,41105.8,39483.12027929258 +1800,41012.1,39495.8089076802 +1801,41205.6,39509.9980453758 +1802,41104.4,39523.22959686646 +1803,40999.0,39535.476654153885 +1804,41329.1,39550.36149519825 +1805,41052.2,39562.824885279595 +1806,40755.2,39572.7201144474 +1807,40720.6,39582.246088601365 +1808,40812.1,39592.452345127494 +1809,40823.1,39602.66518873639 +1810,40719.9,39611.93684692115 +1811,40743.4,39621.32658263134 +1812,41035.0,39633.05831223606 +1813,41112.1,39645.33251711377 +1814,40819.9,39655.07996510453 +1815,40788.0,39664.48179112026 +1816,40797.2,39673.881942231295 +1817,40765.8,39682.94350287668 +1818,40792.2,39692.1489509856 +1819,40859.2,39701.83402193177 +1820,40688.4,39710.02129145931 +1821,40983.3,39720.58791974595 +1822,40965.1,39730.9158208269 +1823,40898.1,39740.60199658768 +1824,40310.1,39745.32812109733 +1825,40519.9,39751.75610349487 +1826,40497.6,39757.945679399476 +1827,40500.6,39764.1087857945 +1828,40591.8,39770.9775925514 +1829,40734.5,39778.97362912774 +1830,40724.7,39786.82198075324 +1831,40560.7,39793.244204979346 +1832,40594.0,39799.88948128657 +1833,40499.3,39805.69371795639 +1834,40363.5,39810.32281573269 +1835,40380.7,39815.05623634901 +1836,40437.3,39820.22008501001 +1837,40580.0,39826.5253125203 +1838,40555.0,39832.570745611425 +1839,40902.2,39841.44733693416 +1840,41386.2,39854.26686110898 +1841,41634.8,39869.043069730484 +1842,41679.5,39884.06760857089 +1843,42020.3,39901.79567820931 +1844,41711.1,39916.81065183412 +1845,41788.6,39932.344173395664 +1846,41689.9,39946.92969892765 +1847,41751.5,39961.90538607348 +1848,41854.0,39977.607416064566 +1849,41710.7,39991.98992713457 +1850,41757.1,40006.6381435069 +1851,41785.0,40021.39633318734 +1852,41788.1,40036.0577744057 +1853,41733.4,40050.143602004 +1854,41754.1,40064.28431899982 +1855,41605.1,40077.0711711243 +1856,41690.1,40090.45730248427 +1857,41662.8,40103.50578960058 +1858,41679.3,40116.58291997734 +1859,41718.6,40129.8776675294 +1860,41836.3,40144.03884871173 +1861,41714.7,40157.07338108757 +1862,41800.6,40170.71260614078 +1863,41909.0,40185.13822766658 +1864,41953.1,40199.810109594655 +1865,41869.7,40213.668116983914 +1866,41991.3,40228.420248793176 +1867,42008.4,40243.19186498577 +1868,42170.1,40259.18280386556 +1869,42090.3,40274.37879719448 +1870,41927.1,40288.09432584847 +1871,42188.2,40303.862837667155 +1872,42018.0,40318.088042333824 +1873,41948.5,40331.618432024 +1874,41792.4,40343.74110063791 +1875,41922.2,40356.84034461602 +1876,41833.6,40369.09561146568 +1877,41882.0,40381.65083460705 +1878,41874.6,40394.04045423687 +1879,41848.7,40406.11231768719 +1880,41850.1,40418.09561795535 +1881,41760.7,40429.237563034556 +1882,41753.2,40440.224803175355 +1883,41587.9,40449.74907866768 +1884,41620.0,40459.460704570854 +1885,41473.8,40467.878458059895 +1886,41416.4,40475.75000612579 +1887,41334.2,40482.874072465 +1888,41290.4,40489.57553244454 +1889,41037.3,40494.120963710564 +1890,41220.0,40500.1448561279 +1891,41364.5,40507.31792786128 +1892,41273.2,40513.67379567986 +1893,41343.0,40520.55617081945 +1894,41426.1,40528.07105736867 +1895,41261.0,40534.15345523283 +1896,41404.9,40541.37956763754 +1897,41215.0,40546.96977869449 +1898,40785.4,40548.94845273022 +1899,40811.3,40551.12564399387 +1900,40840.8,40553.52958055824 +1901,40918.9,40556.56170022166 +1902,40832.8,40558.85413424472 +1903,41076.4,40563.14912068252 +1904,41135.3,40567.89726075985 +1905,41308.4,40574.04251170791 +1906,41306.3,40580.11933733689 +1907,41218.7,40585.4187619233 +1908,41203.1,40590.54474730153 +1909,41085.0,40594.64811039446 +1910,41016.7,40598.15061570239 +1911,41198.3,40603.131108518144 +1912,40768.6,40604.50429433957 +1913,41035.7,40608.08268193842 +1914,40924.3,40610.70689204681 +1915,41168.0,40615.33173111696 +1916,41104.3,40619.389559074494 +1917,41278.0,40624.85520588716 +1918,41098.1,40628.78254857689 +1919,40982.0,40631.713813733935 +1920,41106.5,40635.6539480598 +1921,41148.9,40639.9132513954 +1922,41815.9,40649.6724775249 +1923,42230.0,40662.78722874876 +1924,42992.4,40682.12011481724 +1925,42850.0,40700.110819258596 +1926,42241.5,40712.90243071703 +1927,42197.9,40725.22606199739 +1928,42535.8,40740.25157185633 +1929,42448.0,40754.423757982004 +1930,42802.7,40771.42190106929 +1931,42977.0,40789.72545375751 +1932,42937.9,40807.552628415135 +1933,42816.2,40824.22190120837 +1934,42739.5,40840.11632526474 +1935,42567.2,40854.448969868354 +1936,42506.9,40868.162256425465 +1937,42611.9,40882.63310906924 +1938,42548.4,40896.45690069522 +1939,42268.2,40907.84066085543 +1940,42566.7,40921.607128400196 +1941,42805.0,40937.23694476202 +1942,42523.1,40950.39763401711 +1943,42349.8,40962.010931660116 +1944,42386.4,40973.831587828914 +1945,42314.4,40984.95663689257 +1946,42300.9,40995.87732870259 +1947,42058.7,41004.69743385859 +1948,41933.9,41012.408658473876 +1949,42000.1,41020.60526711725 +1950,42207.1,41030.45169643578 +1951,42094.1,41039.278653311834 +1952,42208.9,41048.98505452917 +1953,42150.1,41058.12293789407 +1954,42142.0,41067.117768284996 +1955,42028.8,41075.098533693425 +1956,42335.9,41085.56161640137 +1957,42011.7,41093.24741211588 +1958,42364.0,41103.79307674563 +1959,42588.7,41116.11595577679 +1960,42561.5,41128.1108441106 +1961,42149.0,41136.5829532881 +1962,42069.4,41144.324173592766 +1963,42192.8,41153.025217795315 +1964,42332.4,41162.81256038623 +1965,42358.3,41172.73361797638 +1966,42461.1,41183.4254551716 +1967,42867.9,41197.4044970374 +1968,43030.1,41212.6135883483 +1969,42809.5,41225.8657577396 +1970,42925.9,41239.973925725164 +1971,42696.4,41252.06044916313 +1972,42873.9,41265.51969854767 +1973,42986.6,41279.802522626116 +1974,43184.0,41295.60499131802 +1975,43061.0,41310.25557230293 +1976,43019.1,41324.436853860585 +1977,43004.1,41338.375967106556 +1978,42981.6,41352.012681072476 +1979,42949.5,41365.269837246145 +1980,42799.5,41377.17216224826 +1981,42968.9,41390.38152189765 +1982,43564.8,41408.426488520905 +1983,43967.0,41429.6594637199 +1984,43974.8,41450.7809619463 +1985,43790.5,41470.19771744882 +1986,43972.2,41490.96122186833 +1987,43891.1,41510.879386002205 +1988,43849.0,41530.2828765748 +1989,44002.7,41550.80086100156 +1990,43926.8,41570.51869617997 +1991,43973.4,41590.45961986312 +1992,43951.3,41610.0516562128 +1993,44106.8,41630.771559480745 +1994,43929.2,41649.84565442281 +1995,43961.8,41669.031997539634 +1996,44082.6,41689.06160751856 +1997,44047.2,41708.631220734176 +1998,44083.6,41728.34050520941 +1999,43902.4,41746.38249271805 +2000,43992.9,41765.02579153366 +2001,44095.2,41784.36333683214 +2002,44205.5,41804.45575727337 +2003,44554.5,41827.27770119641 +2004,44808.9,41852.02145471345 +2005,44969.9,41877.89596546272 +2006,44855.1,41902.60305288626 +2007,44320.2,41922.66609809053 +2008,44139.7,41941.06471968314 +2009,44328.8,41960.87995022519 +2010,44437.3,41981.43115395777 +2011,44428.8,42001.74126886269 +2012,44525.1,42022.682005220675 +2013,44501.3,42043.25144916076 +2014,44280.0,42061.813677798425 +2015,44290.6,42080.309829849895 +2016,44128.2,42097.30476902126 +2017,44276.6,42115.390206622746 +2018,44262.8,42133.211034783555 +2019,44374.0,42151.806793831 +2020,44377.0,42170.273127492146 +2021,44478.8,42189.43102684906 +2022,44423.2,42207.968528700934 +2023,44482.8,42226.84679817229 +2024,44484.2,42245.580019764224 +2025,44455.7,42263.92126441349 +2026,44412.9,42281.755112841594 +2027,44323.6,42298.69988368938 +2028,44330.7,42315.56295519404 +2029,44240.5,42331.53753647874 +2030,44249.3,42347.45257766979 +2031,44193.6,42362.77330316631 +2032,44281.5,42378.69634629356 +2033,44344.6,42395.010899436355 +2034,44404.1,42411.68383803024 +2035,44340.9,42427.693930660695 +2036,44541.1,42445.23257024028 +2037,44417.4,42461.59910492709 +2038,44345.6,42477.233967126864 +2039,44489.2,42493.93078067768 +2040,44680.0,42512.07243395006 +2041,44699.9,42530.22867931147 +2042,44675.1,42548.02844130888 +2043,44691.0,42565.812437646564 +2044,44565.6,42582.40818505199 +2045,44617.5,42599.29691380674 +2046,44641.6,42616.24548713615 +2047,44719.9,42633.70320093585 +2048,44587.8,42649.91977188244 +2049,44520.0,42665.43910987512 +2050,44514.1,42680.78069402554 +2051,44556.5,42696.346829344824 +2052,44423.0,42710.67590130047 +2053,44599.6,42726.35161996188 +2054,44595.3,42741.86156502444 +2055,44836.1,42759.24113709893 +2056,44931.6,42777.26901147985 +2057,44894.5,42794.839393127324 +2058,44816.4,42811.61582969888 +2059,44813.6,42828.2298062159 +2060,46114.2,42855.49926840498 +2061,46558.3,42886.2279051817 +2062,46595.0,42917.00609683995 +2063,46820.8,42949.40272674169 +2064,46915.9,42982.319716561266 +2065,46799.0,43013.99341185951 +2066,46910.1,43046.32624661586 +2067,47042.8,43079.49200390536 +2068,47111.7,43112.9543109269 +2069,47069.5,43145.7887149856 +2070,46900.5,43176.948144736765 +2071,46984.5,43208.546085444344 +2072,47015.7,43240.14072373941 +2073,47135.3,43272.465696986386 +2074,47209.0,43305.13403145123 +2075,47250.0,43337.87150836865 +2076,47200.0,43369.922367220366 +2077,47698.8,43405.84666292808 +2078,47582.0,43440.50353709465 +2079,47410.9,43473.452885334525 +2080,47449.6,43506.44995682553 +2081,47574.0,43540.20555884357 +2082,47958.1,43576.86858325151 +2083,47923.0,43612.93606388843 +2084,47913.7,43648.6270509101 +2085,47611.5,43681.513963350684 +2086,47528.5,43713.439158675574 +2087,47121.9,43741.72514076126 +2088,47392.5,43772.02202755992 +2089,47519.9,43803.12474932291 +2090,47397.3,43832.95192982646 +2091,47328.0,43861.95647812665 +2092,47579.2,43892.80497208411 +2093,47616.2,43923.70451588424 +2094,47546.8,43953.77169832503 +2095,47330.1,43981.79102033063 +2096,47528.0,44011.22014049386 +2097,47575.0,44040.79507708727 +2098,47596.4,44070.302171883224 +2099,47873.3,44101.86231983441 +2100,47822.0,44132.734831703 +2101,47677.5,44162.15197002912 +2102,47777.0,44192.15070886705 +2103,47424.0,44218.97103493454 +2104,47477.3,44246.01110933342 +2105,47448.6,44272.588610500774 +2106,47506.8,44299.42853904434 +2107,47704.0,44327.68224411451 +2108,47464.7,44353.71558648701 +2109,47257.4,44377.81255257426 +2110,47330.6,44402.31701271887 +2111,47418.5,44427.347576928674 +2112,47245.0,44450.73058458902 +2113,46860.9,44470.731990526045 +2114,47191.2,44493.308488529976 +2115,47408.1,44517.497629704005 +2116,47235.2,44540.05117634547 +2117,47487.2,44564.50884293181 +2118,47406.0,44588.089682409554 +2119,47371.6,44611.18935309496 +2120,47431.9,44634.597740206205 +2121,47263.5,44656.41435647006 +2122,47273.7,44678.134569279435 +2123,47318.1,44700.04299609039 +2124,47285.1,44721.49575130956 +2125,46990.6,44740.326491962594 +2126,47100.2,44759.91050447743 +2127,47153.7,44779.775977469326 +2128,47231.2,44800.11974529116 +2129,47640.1,44823.68804616011 +2130,47109.2,44842.654950341355 +2131,47061.2,44861.06611257918 +2132,47242.3,44880.82738965321 +2133,46973.0,44898.189817954844 +2134,47074.9,44916.25380286809 +2135,47053.2,44933.987796205285 +2136,47139.2,44952.28831241935 +2137,47380.1,44972.43612725404 +2138,47206.1,44990.972756903386 +2139,47088.4,45008.37879211581 +2140,47011.8,45025.00469425593 +2141,47083.6,45042.088472726835 +2142,47100.1,45059.16740656313 +2143,47140.0,45076.43572684062 +2144,47223.2,45094.25119798717 +2145,47284.3,45112.425876842055 +2146,47266.5,45130.30201064419 +2147,47146.5,45147.033944165814 +2148,47383.5,45165.5938284466 +2149,46905.4,45180.03205393667 +2150,46680.4,45192.483240211055 +2151,46450.0,45202.919063943744 +2152,45692.3,45206.98031652512 +2153,45824.4,45212.104131325745 +2154,45946.9,45218.20202235209 +2155,45578.1,45221.18872756078 +2156,45741.9,45225.50998293372 +2157,45736.1,45229.74724448614 +2158,45782.3,45234.3327445319 +2159,45506.0,45236.587244577284 +2160,45539.4,45239.10021350195 +2161,44926.1,45236.50270135671 +2162,44394.4,45229.514297196074 +2163,44585.0,45224.1656308293 +2164,44625.8,45219.199940946906 +2165,44624.0,45214.26052234983 +2166,44773.9,45210.60607818096 +2167,44993.8,45208.80685761514 +2168,45112.0,45208.0034812034 +2169,45062.8,45206.79847306063 +2170,45200.1,45206.742884072584 +2171,45045.2,45205.40227922551 +2172,45219.1,45205.515953256836 +2173,45408.0,45207.196318789975 +2174,46096.7,45214.57809207803 +2175,46523.5,45225.44051455041 +2176,46017.5,45232.01362231348 +2177,46449.2,45242.11475407852 +2178,46321.4,45251.07147811106 +2179,46349.4,45260.18623762881 +2180,46131.2,45267.414567607 +2181,46294.8,45275.94058779283 +2182,46360.4,45284.940250964675 +2183,46266.2,45293.083485396506 +2184,46883.3,45306.28030294508 +2185,46736.0,45318.145196696576 +2186,46480.0,45327.787145271715 +2187,46587.5,45338.241193858674 +2188,46521.7,45348.06242876441 +2189,46534.9,45357.9117032145 +2190,46620.1,45368.38629488907 +2191,46578.0,45378.42458289829 +2192,46540.0,45388.064212915735 +2193,46661.9,45398.635464260835 +2194,46710.1,45409.51898737901 +2195,46534.5,45418.854929392466 +2196,46710.1,45429.570656119504 +2197,46726.1,45440.33023573677 +2198,46603.3,45449.98143709995 +2199,46284.1,45456.903582850166 +2200,46300.7,45463.906042743525 +2201,46350.5,45471.263668944826 +2202,45824.0,45474.190941401714 +2203,46099.0,45479.37607881747 +2204,46159.4,45485.01943086048 +2205,46207.7,45491.01677998197 +2206,46100.0,45496.07058263772 +2207,45794.6,45498.54800518845 +2208,46058.0,45503.19076033212 +2209,45831.7,45505.9169780887 +2210,45763.9,45508.05791602988 +2211,45930.2,45511.56116983876 +2212,46350.0,45518.51916842931 +2213,46337.9,45525.31900935521 +2214,46412.2,45532.67901757633 +2215,46363.7,45539.575457264495 +2216,46270.6,45545.64205098015 +2217,46357.5,45552.37946134546 +2218,46170.1,45557.50577286956 +2219,46237.1,45563.14555898683 +2220,46359.4,45569.75347965914 +2221,46416.2,45576.777932110104 +2222,46620.0,45585.43537665691 +2223,46397.9,45592.17782166391 +2224,46276.6,45597.85767376629 +2225,46380.0,45604.34848145287 +2226,46577.5,45612.42442766488 +2227,46388.0,45618.860739468495 +2228,46351.9,45624.944052833904 +2229,46963.6,45636.05323081868 +2230,46684.5,45644.754033882426 +2231,46392.4,45650.95856472158 +2232,45846.9,45652.58463472389 +2233,45840.2,45654.141608709586 +2234,46022.1,45657.19520531781 +2235,45990.2,45659.95873058488 +2236,46062.9,45663.30264153439 +2237,46215.0,45667.88104284946 +2238,46189.5,45672.2098308756 +2239,46158.3,45676.24377418784 +2240,46145.7,45680.13967647674 +2241,45890.2,45681.882915676106 +2242,45985.2,45684.40006990286 +2243,46149.9,45688.263139862174 +2244,46002.6,45690.87174451062 +2245,46150.8,45694.688576506385 +2246,45890.1,45696.31024807065 +2247,45627.0,45695.73505928998 +2248,45685.8,45695.65261066517 +2249,45373.0,45692.974995638906 +2250,45488.8,45691.280597334844 +2251,45905.6,45693.05918158933 +2252,46293.6,45698.04292282095 +2253,46442.6,45704.221819727 +2254,46655.1,45712.11292495748 +2255,46580.9,45719.32277620265 +2256,46510.9,45725.89188179433 +2257,46625.4,45733.356679455785 +2258,46610.2,45740.6333875101 +2259,46630.0,45748.0140232984 +2260,46744.9,45756.28693596812 +2261,46687.8,45764.01733483975 +2262,46607.0,45771.013041604565 +2263,46705.4,45778.76729022196 +2264,46658.9,45786.07129611223 +2265,46549.1,45792.40348452624 +2266,46642.9,45799.46154689532 +2267,46673.4,45806.714148165905 +2268,46743.0,45814.48415523507 +2269,46410.8,45819.432834444735 +2270,45799.1,45819.26409722943 +2271,45740.5,45818.61045326902 +2272,45900.0,45819.285885192105 +2273,45940.7,45820.29347120711 +2274,45875.3,45820.74995692323 +2275,46019.5,45822.39933487408 +2276,45866.7,45822.76697524857 +2277,45923.6,45823.60376383572 +2278,45843.9,45823.77219733086 +2279,45477.4,45820.89773926172 +2280,44980.0,45813.91933478652 +2281,45168.4,45808.56232785884 +2282,45207.6,45803.5750886235 +2283,45200.0,45798.56616672621 +2284,45320.7,45794.60047239653 +2285,45348.3,45790.896734036396 +2286,45450.1,45788.06854537219 +2287,45255.0,45783.6447400164 +2288,45350.1,45780.046858356516 +2289,45313.3,45776.17344044484 +2290,45062.6,45770.251669154844 +2291,44802.5,45762.220534970984 +2292,44744.3,45753.77306165173 +2293,44157.0,45740.52183292433 +2294,44243.0,45728.09426584612 +2295,44159.4,45715.07605617105 +2296,43772.8,45698.95758267585 +2297,43803.3,45683.22598447937 +2298,43831.9,45667.86228336336 +2299,43751.2,45651.956372298104 +2300,43861.3,45637.096153440856 +2301,43825.3,45622.060500715204 +2302,43500.0,45604.45004012836 +2303,43159.7,45584.1616580526 +2304,42872.5,45561.658241803205 +2305,43356.2,45543.35568377994 +2306,43338.5,45525.058126238204 +2307,43292.0,45506.52652353083 +2308,43380.1,45488.8798303895 +2309,43380.8,45471.385391963035 +2310,43368.7,45453.93572066044 +2311,43416.6,45437.028370281514 +2312,43492.8,45420.89369500947 +2313,43437.0,45404.42984691811 +2314,43390.5,45387.71673615531 +2315,43739.9,45374.04190846937 +2316,43533.9,45358.77102126216 +2317,43630.0,45344.42437378281 +2318,43395.1,45328.24740802528 +2319,43305.0,45311.45697310391 +2320,43479.9,45296.25733017359 +2321,43636.5,45282.48341042112 +2322,43685.0,45269.22628668319 +2323,43367.5,45253.44432579785 +2324,43542.0,45239.24146832235 +2325,43495.2,45224.768095141255 +2326,43605.2,45211.32769601145 +2327,43417.1,45196.43783961302 +2328,43313.5,45180.81179945025 +2329,43595.5,45167.65568493199 +2330,43595.8,45154.61123941388 +2331,43582.2,45141.56218348514 +2332,43525.7,45128.152538808914 +2333,43631.0,45115.72803641216 +2334,43562.8,45102.84066681538 +2335,43742.5,45091.551532650934 +2336,43684.4,45079.87392657084 +2337,43568.4,45067.330574483116 +2338,43538.1,45054.63986432143 +2339,43289.8,45039.993890343656 +2340,43143.9,45024.258671336655 +2341,42900.0,45006.629968669964 +2342,43736.6,44996.090300880176 +2343,43636.3,44984.80573406789 +2344,43428.0,44971.88618440758 +2345,42983.9,44955.388373748596 +2346,42933.6,44938.6100469955 +2347,42722.1,44920.215772746575 +2348,42778.5,44902.44219786901 +2349,42553.6,44882.94973149666 +2350,42286.0,44861.39828144274 +2351,42229.2,44839.55431230214 +2352,42415.1,44819.43435950296 +2353,42419.3,44799.5162320382 +2354,42164.8,44777.65136704204 +2355,42357.9,44757.57044283422 +2356,42385.7,44737.88687069452 +2357,42557.2,44719.78988421573 +2358,42423.2,44700.73104700232 +2359,42466.5,44682.18971051268 +2360,42451.7,44663.679422458634 +2361,42428.5,44645.13021563325 +2362,42467.9,44627.061915088576 +2363,42435.5,44608.87467927871 +2364,42489.7,44591.28816741748 +2365,42447.1,44573.494074741815 +2366,42449.9,44555.870887399564 +2367,42243.9,44536.68440700621 +2368,42434.3,44519.23723350408 +2369,42557.2,44502.954766835996 +2370,42452.0,44485.93439532698 +2371,42490.4,44469.37394391348 +2372,42404.7,44452.239720312544 +2373,42537.2,44436.347274500826 +2374,42524.8,44420.483811641905 +2375,42727.7,44406.43581320504 +2376,42605.8,44391.49277741081 +2377,42734.4,44377.740970129395 +2378,42808.1,44364.71490398724 +2379,42761.2,44351.40772636079 +2380,42722.7,44337.89147966901 +2381,42636.3,44323.77038855143 +2382,42763.3,44310.82042682071 +2383,42610.1,44296.706564357475 +2384,42628.5,44282.86252647899 +2385,42513.8,44268.181509661736 +2386,42523.9,44253.70614443633 +2387,42666.1,44240.53098971072 +2388,42569.1,44226.66019311561 +2389,42556.5,44212.79994255034 +2390,42681.0,44200.08790983208 +2391,42722.6,44187.82659937705 +2392,42763.2,44176.003971996324 +2393,43074.1,44166.859540693455 +2394,43079.7,44157.83746981633 +2395,43217.2,44150.03134973487 +2396,43081.3,44141.16220990304 +2397,42693.3,44129.14675587895 +2398,42087.4,44112.202799398634 +2399,42140.4,44095.83929068993 +2400,42286.4,44080.82319699126 +2401,42150.0,44064.79976797058 +2402,41929.9,44047.082757448 +2403,42105.1,44030.9667179671 +2404,42251.9,44016.20267881385 +2405,42278.2,44001.7794200685 +2406,42234.8,43987.11569044137 +2407,42282.6,43972.970332014476 +2408,42180.0,43958.09091017203 +2409,41372.0,43936.6295748179 +2410,41584.1,43917.10650780697 +2411,41055.1,43893.35541645587 +2412,41133.5,43870.45205200396 +2413,41130.1,43847.71054119895 +2414,40801.8,43822.433275296884 +2415,40575.7,43795.48943068861 +2416,40756.0,43770.2654520107 +2417,40442.0,43742.64499182804 +2418,40494.2,43715.686942103326 +2419,39961.7,43684.533523496655 +2420,39813.7,43652.4104237166 +2421,39368.4,43616.85846999282 +2422,39740.7,43584.69117978541 +2423,39505.6,43550.83980070006 +2424,39522.0,43517.405445507524 +2425,39479.4,43483.89502687261 +2426,39464.9,43450.542371047944 +2427,39719.8,43419.58185344589 +2428,39892.0,43390.30731524302 +2429,40079.2,43362.829246236855 +2430,39908.7,43334.16427323904 +2431,40154.9,43307.78033736154 +2432,40153.9,43281.60705655356 +2433,40189.2,43255.94392745354 +2434,40132.1,43230.019911458076 +2435,40354.5,43206.15667567834 +2436,40588.0,43184.42923438641 +2437,40380.8,43161.162601735894 +2438,39975.5,43134.725567696594 +2439,40163.9,43110.07141360783 +2440,40074.1,43084.87663009242 +2441,40002.3,43059.29508129497 +2442,39654.4,43031.03869057883 +2443,39281.1,42999.91886742051 +2444,39495.8,42970.839042794614 +2445,39669.1,42943.43871878802 +2446,39809.3,42917.42926883957 +2447,40060.7,42893.721972002735 +2448,39917.0,42869.01888509814 +2449,39860.7,42844.05358314712 +2450,40037.4,42820.76185216665 +2451,40059.3,42797.845156298055 +2452,40112.9,42775.563453756164 +2453,40161.2,42753.86749148433 +2454,39929.9,42730.43207661724 +2455,40112.0,42708.70234984033 +2456,40143.9,42687.41768303668 +2457,40062.3,42665.63247404882 +2458,39788.1,42641.75253650484 +2459,39697.7,42617.320565247544 +2460,39681.0,42592.95275972682 +2461,40496.4,42575.5539816378 +2462,40621.0,42559.33361664496 +2463,41134.5,42547.50927127861 +2464,40988.9,42534.574754504516 +2465,41020.2,42522.00732915593 +2466,41344.2,42512.232994474136 +2467,41041.5,42500.02774140796 +2468,41217.9,42489.38767716391 +2469,41200.0,42478.68736449035 +2470,41260.3,42468.57626602986 +2471,41129.8,42457.46608954828 +2472,41172.3,42446.80081079684 +2473,41444.1,42438.47964224251 +2474,41395.3,42429.82254977577 +2475,41303.9,42420.47879417597 +2476,41271.0,42410.939551070776 +2477,41291.8,42401.65208591666 +2478,41177.7,42391.49480719536 +2479,41245.0,42381.9803274676 +2480,41158.2,42371.82447412762 +2481,41166.9,42361.82510089835 +2482,40929.9,42349.94190504027 +2483,40913.5,42338.0212253304 +2484,41125.0,42327.95465914508 +2485,40586.2,42313.500263633505 +2486,40418.2,42297.77163074028 +2487,39943.2,42278.23161720717 +2488,39929.2,42258.737578890104 +2489,39956.3,42239.63021309019 +2490,39637.4,42218.03494161226 +2491,39752.6,42197.57490060303 +2492,39887.6,42178.40498441546 +2493,39942.2,42159.84726670247 +2494,39822.9,42140.45351345182 +2495,39915.9,42121.992488443924 +2496,39835.9,42103.02076654813 +2497,39919.9,42084.9035817635 +2498,39991.9,42067.53425743351 +2499,40180.0,42051.870072724516 +2500,40082.0,42035.52260324133 +2501,40107.1,42019.51909616049 +2502,40020.3,42002.92806631683 +2503,40225.6,41988.17845580798 +2504,40140.9,41972.84834414153 +2505,40095.5,41957.268689833305 +2506,40247.3,41943.07807829942 +2507,40207.6,41928.675770595684 +2508,40139.6,41913.82866876502 +2509,40025.0,41898.15374205328 +2510,40434.8,41886.0097275964 +2511,40397.9,41873.6602692761 +2512,40364.6,41861.13694753937 +2513,40416.9,41849.151578680125 +2514,40372.4,41836.896378857055 +2515,40296.6,41824.113836293924 +2516,40380.9,41812.13695798443 +2517,40405.8,41800.466111860085 +2518,40446.4,41789.22904869112 +2519,40529.5,41778.77486571443 +2520,40533.9,41768.44395396576 +2521,40554.6,41758.37056015692 +2522,40508.0,41747.9940409855 +2523,40417.2,41736.950107035416 +2524,40517.0,41726.82603975712 +2525,40438.0,41716.13038797491 +2526,40409.1,41705.283662763504 +2527,40376.0,41694.25226307252 +2528,40359.8,41683.177970432924 +2529,40313.4,41671.81051839614 +2530,40402.9,41661.28014065012 +2531,40403.3,41650.840471433105 +2532,40435.1,41640.75133889009 +2533,40416.2,41630.58908711507 +2534,40480.4,41621.04394946266 +2535,40039.8,41607.921593035586 +2536,40185.2,41596.114774836125 +2537,40131.5,41583.960295376906 +2538,40166.8,41572.199629025235 +2539,40189.5,41560.724943307185 +2540,40261.0,41549.938844192606 +2541,40465.1,41540.9360322076 +2542,40390.0,41531.38469584074 +2543,40361.0,41521.67195977567 +2544,40356.4,41512.00165305554 +2545,40369.7,41502.521971287446 +2546,40143.9,41491.24710015643 +2547,40215.8,41480.662476918624 +2548,40267.1,41470.591419018885 +2549,40345.9,41461.25788027184 +2550,40314.0,41451.73706798743 +2551,40271.9,41441.94588899999 +2552,40366.1,41433.01770734854 +2553,40406.5,41424.49888820042 +2554,40404.0,41416.030017758916 +2555,40454.6,41408.051345412365 +2556,40410.8,41399.77540063716 +2557,40285.7,41390.529961627726 +2558,40345.1,41381.854194311316 +2559,40430.7,41373.960798507906 +2560,40175.2,41364.01257611365 +2561,40083.8,41353.388405357524 +2562,40106.6,41343.04161361182 +2563,40357.8,41334.86533466069 +2564,40246.1,41325.82993769255 +2565,40189.0,41316.395664350704 +2566,39983.4,41305.3334596673 +2567,39649.1,41291.58878365347 +2568,39707.7,41278.44447839494 +2569,39732.3,41265.613403885436 +2570,39774.5,41253.239018790955 +2571,39788.4,41241.082678386054 +2572,38798.4,41220.81145283928 +2573,38890.3,41201.471108832324 +2574,38953.1,41182.81242743122 +2575,38902.1,41163.88535334465 +2576,38851.8,41144.69792302645 +2577,38952.4,41126.5045792669 +2578,38902.9,41108.05142923149 +2579,38975.3,41090.35224724617 +2580,39366.8,41076.04890909475 +2581,39312.4,41061.41281856285 +2582,39329.0,41047.03594869926 +2583,39239.2,41032.033160743245 +2584,39441.0,41018.829566048276 +2585,40189.5,41011.94716301053 +2586,40640.1,41008.86129443783 +2587,40670.4,41006.05248701511 +2588,40649.8,41003.09603484072 +2589,40790.6,41001.332582269424 +2590,40903.9,41000.52401312196 +2591,40785.6,40998.74041135332 +2592,40860.0,40997.589038645 +2593,40731.0,40995.37668147782 +2594,40588.9,40992.00343100913 +2595,40683.2,40989.440746934364 +2596,40844.3,40988.23625940794 +2597,40651.9,40985.44508713069 +2598,40686.3,40982.96255528728 +2599,40671.6,40980.378633666645 +2600,40712.1,40978.15225496402 +2601,40648.6,40975.417381478845 +2602,40778.6,40973.78404221346 +2603,40661.5,40971.19247339841 +2604,40951.1,40971.02573088058 +2605,41419.6,40974.74833892306 +2606,41545.3,40979.483207479716 +2607,41433.3,40983.24932194047 +2608,41327.8,40986.1086636671 +2609,41330.1,40988.96336355369 +2610,41372.6,40992.14707008022 +2611,41400.1,40995.53257157333 +2612,41284.9,40997.933961020855 +2613,41299.9,41000.43990325305 +2614,41381.9,41003.60554720945 +2615,41472.9,41007.50010698365 +2616,41280.0,41009.7615168842 +2617,41334.2,41012.45395242874 +2618,41358.3,41015.32404410983 +2619,41372.6,41018.28898980187 +2620,41333.3,41020.903189056626 +2621,41426.6,41024.269967570675 +2622,41449.2,41027.79635788129 +2623,41325.6,41030.26775740095 +2624,41448.3,41033.736904642436 +2625,41512.5,41037.710042363244 +2626,41797.2,41044.01286358845 +2627,42070.8,41052.533918662404 +2628,42076.2,41061.029072864374 +2629,41580.9,41065.34335441737 +2630,41537.8,41069.2641564554 +2631,41247.1,41070.7399725844 +2632,41362.1,41073.15789812312 +2633,41355.1,41075.49766660343 +2634,40959.9,41074.538349868126 +2635,41177.6,41075.393633271706 +2636,41395.2,41078.04762801634 +2637,41471.1,41081.30947342699 +2638,41438.0,41084.26956078444 +2639,41327.7,41086.2897304045 +2640,41504.2,41089.75786542189 +2641,41649.3,41094.401368613406 +2642,41593.1,41098.53994646724 +2643,41673.9,41103.31471869573 +2644,41585.7,41107.317916050946 +2645,41443.3,41110.10614911277 +2646,41503.9,41113.374147875315 +2647,41833.7,41119.351955776765 +2648,41894.8,41125.787209255795 +2649,41781.1,41131.22548967691 +2650,42229.5,41140.3398009659 +2651,42418.0,41150.9427901695 +2652,42636.9,41163.274385271834 +2653,42671.8,41175.793270041366 +2654,42486.7,41186.672164065916 +2655,42129.2,41194.493971833006 +2656,41805.1,41199.561241776304 +2657,41546.3,41202.438741844555 +2658,41450.8,41204.49983112385 +2659,41209.9,41204.544645803326 +2660,40594.3,41199.4803748838 +2661,40706.6,41195.3900813163 +2662,40378.2,41188.608420890436 +2663,40449.8,41182.47723067558 +2664,40402.0,41176.00024120939 +2665,40474.6,41170.17949231969 +2666,40467.6,41164.34895711372 +2667,40470.4,41158.59004460655 +2668,40598.5,41153.94199444384 +2669,40639.2,41149.67027664763 +2670,40652.8,41145.54687186218 +2671,40687.1,41141.742333506474 +2672,40386.2,41135.4722726475 +2673,40284.1,41128.406942584035 +2674,40442.9,41122.71808828873 +2675,40558.0,41118.03163112451 +2676,40387.1,41111.96580845957 +2677,40230.0,41104.646590132106 +2678,39848.7,41094.223796853 +2679,39414.6,41080.28501015713 +2680,39359.1,41066.001317126786 +2681,39314.8,41051.46852611329 +2682,39503.4,41038.621484402815 +2683,39482.6,41025.70844303847 +2684,39602.2,41013.89509496346 +2685,39583.6,41002.02542612559 +2686,39683.5,40991.083306406705 +2687,39674.2,40980.15481423736 +2688,39544.9,40968.24398590344 +2689,39588.1,40956.79050884201 +2690,39478.6,40944.5233676898 +2691,39438.7,40932.0269082069 +2692,39511.4,40920.23747328402 +2693,39540.0,40908.7832203937 +2694,39562.1,40897.60742603359 +2695,39578.2,40886.65798681339 +2696,39613.6,40876.093190242325 +2697,39496.3,40864.6426243482 +2698,39571.5,40853.91115028722 +2699,39583.2,40843.36582953795 +2700,39670.0,40833.628353774155 +2701,39681.0,40824.06297324491 +2702,39694.4,40814.68817678644 +2703,39759.9,40805.93474793344 +2704,39798.0,40797.570144216144 +2705,39795.0,40789.25006003177 +2706,39659.3,40779.87288111035 +2707,39734.9,40771.200906993254 +2708,39768.9,40762.88305714269 +2709,39863.8,40755.421786958934 +2710,39785.6,40747.37347337421 +2711,39415.1,40736.31726197692 +2712,39540.5,40726.393467271715 +2713,39455.0,40715.84248414083 +2714,39520.5,40705.92262950065 +2715,39750.1,40697.9904914965 +2716,39677.5,40689.521690737194 +2717,39641.3,40680.82275554435 +2718,39722.2,40672.86737997967 +2719,39725.7,40665.007069772364 +2720,39690.8,40656.92236379915 +2721,39730.1,40649.230891900406 +2722,39671.8,40641.11943221659 +2723,39591.2,40632.40640788284 +2724,39570.2,40623.59141694605 +2725,39564.6,40614.80310643198 +2726,39729.9,40607.459512187736 +2727,39640.0,40599.43080254302 +2728,39460.5,40589.9790946381 +2729,39392.8,40580.043998417044 +2730,39635.7,40572.20711876213 +2731,39675.9,40564.76888541142 +2732,39479.1,40555.75918511755 +2733,39605.1,40547.86989727425 +2734,39528.3,40539.40873630102 +2735,39433.4,40530.23024056409 +2736,38837.4,40516.18185682497 +2737,39069.3,40504.17453851108 +2738,39066.6,40492.24445935331 +2739,39226.2,40481.73786632963 +2740,39123.1,40470.462863289555 +2741,38955.3,40457.8888976191 +2742,38610.2,40442.55537979654 +2743,38437.2,40425.91342643723 +2744,38403.8,40409.13240215145 +2745,38566.0,40393.836697569284 +2746,38404.1,40377.32435983012 +2747,38810.6,40364.32249792282 +2748,38782.2,40351.192850637155 +2749,38716.7,40337.62859461527 +2750,39048.0,40326.926282626766 +2751,38967.4,40315.64390683733 +2752,39452.4,40308.48005698805 +2753,39381.6,40300.78810630766 +2754,39865.2,40297.173267251164 +2755,40204.0,40296.4000451163 +2756,40166.2,40295.31954681658 +2757,40214.2,40294.64635555669 +2758,40329.9,40294.93891692137 +2759,40411.0,40295.90207943654 +2760,40530.3,40297.84729039558 +2761,40469.0,40299.26764483213 +2762,40517.1,40301.07538221942 +2763,40520.0,40302.89218402672 +2764,40545.3,40304.90386714683 +2765,40567.0,40307.07893878877 +2766,40509.0,40308.75463224281 +2767,40573.2,40310.94919961009 +2768,40504.1,40312.55211081665 +2769,40378.3,40313.097736453026 +2770,40368.0,40313.5533568974 +2771,40452.2,40314.70395144597 +2772,40279.0,40314.40765309372 +2773,39808.1,40310.20592983153 +2774,39269.7,40301.571025849524 +2775,38546.9,40287.009440572765 +2776,38637.9,40273.32388504934 +2777,38375.3,40257.572649488764 +2778,38383.9,40242.0234988706 +2779,38250.8,40225.49882253143 +2780,38104.2,40207.89468292536 +2781,38305.2,40192.10468555669 +2782,38195.0,40175.531202689 +2783,38093.7,40158.2545951978 +2784,38152.6,40141.61015872314 +2785,38289.0,40126.23580055946 +2786,38398.2,40111.895254496725 +2787,38324.9,40097.06541835982 +2788,38386.1,40082.86653521991 +2789,38381.7,40068.74897061228 +2790,38622.6,40056.74773434164 +2791,39064.8,40048.515802936316 +2792,38960.8,40039.489115775024 +2793,39033.5,40031.1406583827 +2794,39085.2,40023.29052843762 +2795,38998.5,40014.786042724445 +2796,38882.7,40005.39113780557 +2797,39317.2,39999.68000803125 +2798,38657.4,39988.540754852576 +2799,38990.3,39980.25659921065 +2800,39178.1,39973.599697972386 +2801,39094.4,39966.30343491867 +2802,38779.5,39956.45444375752 +2803,38836.8,39947.16270563505 +2804,39080.0,39939.96633463393 +2805,39203.7,39933.85624057058 +2806,39050.1,39926.522163885355 +2807,39209.0,39920.567623106224 +2808,39356.8,39915.88905361987 +2809,39211.9,39910.046820809745 +2810,39404.9,39905.85473101049 +2811,39332.9,39901.09991996476 +2812,39370.5,39896.69660112689 +2813,39295.4,39891.70658783953 +2814,39395.0,39887.58454146742 +2815,39365.0,39883.24774029342 +2816,39604.0,39880.93033166028 +2817,39748.6,39879.83215463406 +2818,39700.0,39878.33977160805 +2819,39666.7,39876.5834249557 +2820,39543.2,39873.81675752868 +2821,39085.7,39867.2763694994 +2822,39279.9,39862.401876806456 +2823,39428.2,39858.79854172922 +2824,39882.3,39858.99357457794 +2825,40255.3,39862.28242458144 +2826,40064.6,39863.961408609815 +2827,39973.2,39864.86795293671 +2828,39860.2,39864.82921473806 +2829,39849.3,39864.700341586715 +2830,39790.1,39864.08125161504 +2831,39732.5,39862.98929102073 +2832,39837.0,39862.77361225707 +2833,39808.6,39862.32403871137 +2834,39516.5,39859.45412967642 +2835,39528.2,39856.70513274965 +2836,39530.0,39853.993886834716 +2837,39487.2,39850.94995416389 +2838,39450.0,39847.62256865215 +2839,39583.9,39845.43399961769 +2840,39401.4,39841.74907016029 +2841,39140.0,39835.92542642452 +2842,38789.5,39827.241397989455 +2843,38737.7,39818.19956066175 +2844,38888.4,39810.483381735095 +2845,39250.2,39805.833727114885 +2846,38939.4,39798.64340572804 +2847,38988.1,39791.9169044357 +2848,38504.4,39781.23211684702 +2849,38620.0,39771.59533579435 +2850,38522.8,39761.231889024275 +2851,38314.2,39749.223325629886 +2852,38538.2,39739.17333952508 +2853,38538.5,39729.20924542113 +2854,38582.1,39719.68966662095 +2855,38572.2,39710.16693079837 +2856,38619.7,39701.11741270046 +2857,38688.1,39692.71062919258 +2858,38678.0,39684.289794095544 +2859,38641.5,39675.63593688314 +2860,38539.5,39666.20742288411 +2861,38682.0,39658.03972642864 +2862,38689.2,39649.99956272383 +2863,38634.9,39641.57549996264 +2864,38561.3,39632.610558054235 +2865,38604.6,39624.07935010358 +2866,38552.3,39615.18491566289 +2867,38564.9,39606.468858271495 +2868,38519.8,39597.450859447665 +2869,38490.4,39588.26371538586 +2870,38238.3,39577.06069700092 +2871,38439.9,39567.62367876854 +2872,38408.7,39558.006054878344 +2873,38271.3,39547.32799633164 +2874,38325.1,39537.18502540773 +2875,38447.7,39528.14365590227 +2876,38313.4,39518.062795687314 +2877,38284.4,39507.82493016294 +2878,38234.7,39497.25957804541 +2879,37614.5,39481.635017231754 +2880,37660.7,39466.523523312826 +2881,37914.1,39453.64034054675 +2882,37798.6,39439.9055659364 +2883,38021.2,39428.13207576266 +2884,37930.0,39415.69944442853 +2885,38044.1,39404.31687642497 +2886,37935.4,39392.12669487788 +2887,37944.1,39380.10987583325 +2888,38016.4,39368.792781428 +2889,37956.3,39357.07084963191 +2890,38015.4,39345.93665170966 +2891,37922.1,39334.120579911236 +2892,37959.2,39322.71045061737 +2893,37909.4,39310.98173318486 +2894,37975.0,39299.89474784723 +2895,38470.2,39293.00931425514 +2896,38489.3,39286.33952741486 +2897,38380.0,39278.81803756079 +2898,37920.1,39267.54236919929 +2899,37790.0,39255.2806067993 +2900,38285.9,39247.23595446072 +2901,38327.1,39239.59997143615 +2902,38288.1,39231.70370611303 +2903,38440.5,39225.13770025317 +2904,38648.9,39220.35564464942 +2905,38476.4,39214.1817388847 +2906,38598.9,39209.075666362834 +2907,38653.7,39204.46673967103 +2908,38950.3,39202.35747212189 +2909,38989.4,39200.590190195566 +2910,38913.0,39198.20354961303 +2911,38869.6,39195.4765492013 +2912,38911.5,39193.11989734071 +2913,38768.9,39189.59940026735 +2914,38702.1,39185.55376209085 +2915,38516.0,39179.997299334915 +2916,38489.0,39174.26288191305 +2917,38908.7,39172.05904056937 +2918,38835.0,39169.26187010822 +2919,38692.1,39165.30202056375 +2920,38546.5,39160.166734086044 +2921,38366.7,39153.58194791106 +2922,38206.4,39145.721516808066 +2923,38551.9,39140.79353741547 +2924,38312.0,39133.91558274812 +2925,38639.7,39129.814208617434 +2926,38591.8,39125.34936041314 +2927,38525.6,39120.37218729768 +2928,38412.1,39114.49440980973 +2929,38515.7,39109.52516159554 +2930,38408.4,39103.70669552421 +2931,38522.7,39098.88506319621 +2932,38503.5,39093.944108314914 +2933,38519.3,39089.17527754052 +2934,38470.0,39084.036893494536 +2935,38553.1,39079.63077819583 +2936,38527.4,39075.04795016102 +2937,38455.2,39069.90398376964 +2938,38414.0,39064.460797182335 +2939,38561.9,39060.29016816008 +2940,38442.6,39055.16410867328 +2941,38360.6,39049.40009117391 +2942,38276.0,39042.98183315588 +2943,38327.3,39037.04256483093 +2944,38148.3,39029.66710786138 +2945,38051.0,39021.54538912394 +2946,37749.6,39010.98982572872 +2947,37707.3,39000.17082302558 +2948,37773.8,38989.993471797156 +2949,37785.9,38980.00099485278 +2950,37728.9,38969.61841398264 +2951,37712.9,38959.18921552635 +2952,37882.6,38950.25486519003 +2953,37873.4,38941.31831029219 +2954,37949.8,38933.089942571925 +2955,37996.2,38925.31492230162 +2956,37980.0,38917.46998518709 +2957,38073.6,38910.4669147706 +2958,38379.7,38906.06221008371 +2959,38673.1,38904.12891373447 +2960,38957.2,38904.56933768689 +2961,38872.9,38904.3065216065 +2962,38977.6,38904.91476624047 +2963,38987.5,38905.60012087748 +2964,38943.5,38905.914642695934 +2965,38711.8,38904.30373279804 +2966,38784.0,38903.3053615715 +2967,38969.0,38903.8505452929 +2968,39184.5,38906.179586410806 +2969,38974.6,38906.74739067296 +2970,39615.1,38912.62583556364 +2971,39855.2,38920.448027799626 +2972,39811.0,38927.83850059797 +2973,39740.6,38934.5834093067 +2974,39682.4,38940.78935611744 +2975,39684.0,38946.95707930319 +2976,39657.5,38952.85370105171 +2977,39634.0,38958.50636743302 +2978,39771.3,38965.251542807026 +2979,39731.4,38971.60962128996 +2980,39739.9,38977.98547505519 +2981,39606.9,38983.204682731084 +2982,39542.2,38987.843648019625 +2983,39585.4,38992.80262189498 +2984,39485.0,38996.88724743942 +2985,39487.2,39000.95623293785 +2986,39515.4,39005.225475818035 +2987,39499.0,39009.32318971166 +2988,39508.5,39013.46573585513 +2989,38992.6,39013.29257622148 +2990,38214.8,39006.666081813004 +2991,36886.4,38989.07051266933 +2992,37007.8,38972.628433726015 +2993,36795.3,38954.55931809344 +2994,36360.4,38933.03102499723 +2995,36284.0,38911.04736503875 +2996,36451.7,38890.63784333719 +2997,36431.0,38870.22591102733 +2998,36216.7,38848.20494911009 +2999,36534.7,38829.00573791416 +3000,36351.5,38808.44552432151 +3001,36293.0,38787.570457729635 +3002,36415.9,38767.888545217356 +3003,36441.4,38748.58158633588 +3004,36414.9,38729.21493416712 +3005,36482.6,38710.57082682963 +3006,36397.8,38691.37770793478 +3007,36359.3,38672.02436596022 +3008,36197.5,38651.488894043534 +3009,36382.4,38632.658280814954 +3010,35927.9,38610.21215400322 +3011,35766.0,38586.608733638044 +3012,35810.2,38563.567997259306 +3013,35727.1,38540.028843755084 +3014,36125.8,38519.99374961604 +3015,35989.1,38498.99048198437 +3016,36200.8,38479.91836180193 +3017,35872.2,38458.277545521414 +3018,35923.8,38437.24453684489 +3019,35980.3,38416.85495562626 +3020,36059.7,38397.293503712346 +3021,35935.3,38376.86202235374 +3022,35974.5,38356.92540806035 +3023,35995.3,38337.32685695612 +3024,35976.2,38317.732443205445 +3025,35993.0,38298.44005778465 +3026,35941.1,38278.877069753245 +3027,35796.1,38258.27311066816 +3028,35733.2,38237.318147094156 +3029,35887.0,38217.81343218051 +3030,35797.3,38197.72618378067 +3031,35900.0,38178.65791669535 +3032,35991.1,38160.50390908792 +3033,35968.3,38142.31134552702 +3034,36073.1,38125.13946714091 +3035,36042.3,38107.85449230986 +3036,36017.7,38090.508811875756 +3037,35886.7,38072.21994206766 +3038,35941.4,38054.53678902146 +3039,35947.1,38037.047687037884 +3040,35947.7,38019.70870208321 +3041,35875.0,38001.910289617794 +3042,35887.0,37984.35916688237 +3043,35858.4,37966.716352219446 +3044,35773.0,37948.511237263265 +3045,34972.2,37923.81155894573 +3046,35026.1,37899.764160116305 +3047,35466.7,37879.572756297915 +3048,35103.3,37856.533148361836 +3049,34813.3,37831.27810148747 +3050,34568.9,37804.2044242967 +3051,34475.7,37776.58198094154 +3052,34391.5,37748.49001429472 +3053,34446.5,37721.08760753709 +3054,34771.5,37696.60970208035 +3055,34593.0,37670.853604967655 +3056,34660.1,37645.868097872495 +3057,34573.1,37620.36794768268 +3058,34730.0,37596.381491685315 +3059,34781.2,37573.01898967963 +3060,34621.0,37548.520906777725 +3061,34353.1,37522.002890953845 +3062,34446.9,37496.483364887834 +3063,34360.6,37470.45943654851 +3064,34382.3,37444.83155740703 +3065,34063.7,37416.772374358006 +3066,34511.9,37392.66554967454 +3067,34585.5,37369.36957000919 +3068,34220.1,37343.2345528307 +3069,34450.2,37319.22596733003 +3070,34266.3,37293.89048212397 +3071,34032.0,37266.820851566925 +3072,34146.3,37240.92441296471 +3073,34050.2,37214.44537219322 +3074,33500.0,37183.62009939494 +3075,33567.0,37153.606654586685 +3076,33649.4,37124.52610143659 +3077,33507.9,37094.51260681886 +3078,33570.1,37065.26436941788 +3079,33509.7,37035.75761116544 +3080,33666.1,37007.79364758731 +3081,33502.1,36978.70075424633 +3082,32809.5,36944.101577862544 +3083,33107.9,36912.26588012095 +3084,32858.8,36878.627159124095 +3085,33038.5,36846.75888394465 +3086,32454.6,36810.3094326256 +3087,32339.9,36773.21059915983 +3088,31691.5,36731.038726967636 +3089,31259.0,36685.62761720027 +3090,30771.3,36636.54606021106 +3091,30923.8,36589.13737921346 +3092,30939.9,36542.255741211695 +3093,30840.2,36494.93577655434 +3094,31084.3,36450.03423484019 +3095,30056.6,36396.976689322844 +3096,30776.6,36350.334559120995 +3097,30880.6,36304.94257107849 +3098,31032.8,36261.19035057162 +3099,30640.9,36214.54893687393 +3100,31208.8,36173.00745192062 +3101,31812.2,36136.8181784607 +3102,32067.8,36103.05039274733 +3103,31603.1,36065.70640608553 +3104,31750.1,36029.89224503918 +3105,31387.3,35991.36450856582 +3106,31303.1,35952.45774915863 +3107,31623.1,35916.52946908263 +3108,32153.2,35885.298519131735 +3109,31809.8,35851.47695465761 +3110,31078.4,35811.866357523526 +3111,31314.3,35774.54215538641 +3112,31204.8,35736.61898397241 +3113,31677.1,35702.930029748575 +3114,31616.6,35669.01857721954 +3115,31318.0,35632.91053923431 +3116,30996.5,35594.43410322407 +3117,30217.3,35549.81058369524 +3118,30773.4,35510.17232158988 +3119,31002.1,35472.76093302897 +3120,31031.9,35435.90731532749 +3121,30762.4,35397.123022254236 +3122,31462.2,35364.46805941395 +3123,31190.0,35329.825170954085 +3124,31200.5,35295.55691227397 +3125,31419.7,35263.392124620244 +3126,30487.4,35223.75733520431 +3127,30598.9,35185.37677640593 +3128,31395.0,35153.9213674731 +3129,31742.2,35125.608327079135 +3130,31835.0,35098.30037415732 +3131,31517.1,35068.580868977595 +3132,29298.7,35020.698040189396 +3133,31223.0,34989.18187388077 +3134,31815.3,34962.842605217855 +3135,30971.3,34929.71777031978 +3136,30324.6,34891.50102533787 +3137,29899.9,34850.0769504388 +3138,29904.4,34809.0339881945 +3139,29360.5,34763.81793849994 +3140,28463.8,34711.53563195637 +3141,29089.6,34664.880564471256 +3142,28839.2,34616.53466767067 +3143,29074.7,34570.54433847838 +3144,29808.3,34531.023638574 +3145,29199.7,34486.78028887629 +3146,28789.5,34439.499954528765 +3147,28380.9,34389.221116731846 +3148,27771.9,34334.30558879216 +3149,27103.1,34274.295583905914 +3150,27079.0,34214.58358735898 +3151,28151.9,34164.270860492936 +3152,28080.0,34113.778986132 +3153,28043.3,34063.40156715995 +3154,28590.9,34017.98661639514 +3155,28460.5,33971.866395512196 +3156,28494.8,33926.41356235441 +3157,28201.1,33878.900586733216 +3158,29559.8,33843.057428337095 +3159,29015.1,33802.991391587406 +3160,29690.8,33768.865321947684 +3161,28634.2,33726.25399147509 +3162,28334.6,33681.50997494833 +3163,28622.7,33639.52814943009 +3164,28598.8,33597.69638055515 +3165,28429.7,33554.80844378706 +3166,28815.8,33515.48057288426 +3167,29020.7,33478.17948929186 +3168,29524.3,33445.367211372424 +3169,29506.5,33412.67951667224 +3170,30130.6,33385.44234226002 +3171,30564.3,33362.03037261471 +3172,30453.8,33337.89568072579 +3173,30519.9,33314.50982445421 +3174,30377.7,33290.1379586911 +3175,30172.3,33264.263784760056 +3176,30324.5,33239.86740480355 +3177,30431.5,33216.56145123671 +3178,30464.7,33193.72442674512 +3179,30650.5,33172.618829842664 +3180,30697.3,33152.076764864716 +3181,30740.9,33132.066999181196 +3182,30678.3,33111.703787569735 +3183,30327.4,33088.59753207123 +3184,29931.0,33062.39340317438 +3185,29588.6,33033.56524215219 +3186,30117.7,33009.367190350094 +3187,30076.0,32985.02389416462 +3188,29790.5,32958.513322428815 +3189,30028.0,32934.193709794556 +3190,29802.9,32908.207869879254 +3191,29274.3,32878.05095809603 +3192,29493.1,32849.96007877573 +3193,29692.6,32823.75792044564 +3194,29514.7,32796.29685886518 +3195,29563.4,32769.46783928954 +3196,29344.0,32741.040720291283 +3197,29439.4,32713.641212239072 +3198,29494.8,32686.92883703377 +3199,29414.1,32659.768431747183 +3200,29521.6,32633.725540197418 +3201,29482.5,32607.574290901175 +3202,29313.5,32580.23757479411 +3203,29065.0,32551.065478737728 +3204,29091.4,32522.35456190173 +3205,28827.2,32491.68937881541 +3206,28930.1,32462.13262048499 +3207,28812.6,32431.84604272163 +3208,29335.4,32406.149395064193 +3209,29600.0,32382.861848217188 +3210,29517.8,32359.08540134402 +3211,29325.8,32333.912908386807 +3212,29366.7,32309.288734873222 +3213,29690.0,32287.551898899168 +3214,30223.3,32270.42117774648 +3215,30079.8,32252.241748885514 +3216,29949.7,32233.133518604307 +3217,29925.8,32213.98552259929 +3218,29841.1,32194.293526561123 +3219,29715.9,32173.725945427836 +3220,29607.5,32152.429464552915 +3221,29784.0,32132.774448249573 +3222,29951.3,32114.670925857463 +3223,29811.6,32095.558304066115 +3224,29577.5,32074.661554654776 +3225,29741.9,32055.3025376037 +3226,29932.9,32037.68923853645 +3227,30224.8,32022.644514565192 +3228,30297.4,32008.327132701583 +3229,30173.0,31993.096202139743 +3230,29958.6,31976.21241622987 +3231,30003.5,31959.841358833775 +3232,30202.9,31945.26093261939 +3233,29978.9,31928.942584630848 +3234,30305.2,31915.46754243474 +3235,30452.2,31903.3242433274 +3236,31056.0,31896.292506868253 +3237,31065.3,31889.396303491754 +3238,31087.8,31882.744052010497 +3239,31324.4,31878.110491412903 +3240,31105.0,31871.69463671238 +3241,30768.6,31862.540324374517 +3242,30489.7,31851.147458612075 +3243,30315.0,31838.39934692235 +3244,30313.9,31825.747900059923 +3245,30280.0,31812.920116656936 +3246,29557.4,31794.202107390072 +3247,29572.8,31775.767235129573 +3248,29782.0,31759.221448945926 +3249,29595.7,31741.266914099902 +3250,30161.8,31728.159304854264 +3251,29937.5,31713.29906166045 +3252,29876.2,31698.053426293973 +3253,29507.0,31679.870410308133 +3254,29469.6,31661.52791727653 +3255,29663.1,31644.943453232743 +3256,29305.6,31625.529814616704 +3257,29715.0,31609.674795408268 +3258,29895.6,31595.450108309444 +3259,29564.3,31578.594090813098 +3260,29929.9,31564.911982175647 +3261,30162.5,31553.273708464647 +3262,30012.4,31540.486374784443 +3263,29866.7,31526.596031425237 +3264,30139.6,31515.085690915486 +3265,29979.9,31502.34556070042 +3266,30049.4,31490.287921192536 +3267,30341.6,31480.755241348612 +3268,30365.1,31471.49669162788 +3269,30445.6,31462.983026137194 +3270,30514.5,31455.11179770452 +3271,30394.3,31446.30838029619 +3272,30559.9,31438.952294152652 +3273,30625.0,31432.197503329808 +3274,30440.0,31423.9634991528 +3275,30302.0,31414.652598744895 +3276,30576.2,31407.69448589224 +3277,30502.4,31400.181668581932 +3278,30052.8,31389.000077971294 +3279,30130.7,31378.557753672776 +3280,30129.0,31368.187979783375 +3281,30067.8,31357.3963782914 +3282,29828.8,31344.71093116865 +3283,30081.2,31334.225363275138 +3284,30095.0,31323.941335364143 +3285,30530.6,31317.35758984245 +3286,30333.2,31309.190306939192 +3287,30437.0,31301.952213105673 +3288,30490.5,31295.218169843385 +3289,30115.3,31285.42631781149 +3290,30204.8,31276.458464551644 +3291,29815.2,31264.331838289803 +3292,29809.5,31252.258545025987 +3293,30017.9,31242.01490564818 +3294,29725.4,31229.428889833674 +3295,29893.7,31218.344002781116 +3296,29861.0,31207.079737197873 +3297,29944.3,31196.600237304116 +3298,29940.0,31186.17201956715 +3299,29818.1,31174.818724798963 +3300,29529.7,31161.166287248765 +3301,29338.1,31146.037106441723 +3302,29219.9,31130.0525661393 +3303,28971.9,31112.142586337315 +3304,29208.3,31096.343062799246 +3305,28974.2,31078.731917049874 +3306,29186.5,31063.028747613775 +3307,29257.6,31048.04593643026 +3308,29218.1,31032.85966309889 +3309,29198.2,31017.63427170388 +3310,28957.3,31000.536061980198 +3311,28700.6,30981.44945565671 +3312,28899.3,30964.17020706205 +3313,28747.8,30945.7770933105 +3314,29160.0,30930.957366395058 +3315,29179.6,30916.423280366886 +3316,29083.2,30901.209809160522 +3317,29052.4,30885.86698916749 +3318,29307.0,30872.764358551995 +3319,29181.9,30858.73228918642 +3320,28957.9,30842.957747367447 +3321,28995.7,30827.627807555273 +3322,29189.9,30814.036705417882 +3323,29482.2,30802.984118650933 +3324,29482.2,30792.02325459574 +3325,29507.6,30781.364140449718 +3326,29852.9,30773.659043848475 +3327,30386.4,30770.445275849735 +3328,30149.9,30765.295522523185 +3329,30180.0,30760.43829826988 +3330,30220.1,30755.95416301453 +3331,29959.2,30749.34209527167 +3332,30257.8,30745.262907759043 +3333,30274.7,30741.3578213876 +3334,30182.0,30736.715847766125 +3335,30319.9,30733.25679508757 +3336,30262.5,30729.350099692652 +3337,30471.4,30727.209434964912 +3338,30328.0,30723.89649359591 +3339,30172.6,30719.32141896026 +3340,30094.1,30714.132859466816 +3341,30178.5,30709.687773496138 +3342,29999.2,30703.791609400734 +3343,30251.5,30700.038152061312 +3344,30175.7,30695.6867981023 +3345,30293.8,30692.351637952073 +3346,30414.2,30690.043325603925 +3347,30412.5,30687.74006149103 +3348,30247.1,30684.083297495257 +3349,30357.0,30681.368913283677 +3350,29485.7,30671.44634968796 +3351,29142.0,30658.753848860677 +3352,28885.8,30644.040538911628 +3353,28945.9,30629.948086306555 +3354,28990.1,30616.339388494882 +3355,29271.7,30605.18055539534 +3356,29129.1,30592.930924230237 +3357,29171.3,30581.133157224176 +3358,29136.4,30569.143670442234 +3359,29188.9,30557.68936612321 +3360,29260.0,30546.92015976534 +3361,29135.2,30535.20463976729 +3362,29210.7,30524.212900018185 +3363,29246.8,30513.611963088573 +3364,29239.9,30503.041739328502 +3365,29267.1,30492.784961408765 +3366,29343.4,30483.246496998734 +3367,29411.1,30474.34901569584 +3368,29363.2,30465.12786203861 +3369,29327.1,30455.683647415884 +3370,29284.7,30445.965940798327 +3371,29272.0,30436.223484858092 +3372,29275.1,30426.587605315704 +3373,29294.2,30417.190197802713 +3374,29388.8,30408.655839314724 +3375,29531.5,30401.37653774365 +3376,29548.5,30394.298724152417 +3377,29393.9,30385.99666005157 +3378,29464.4,30378.3485549889 +3379,29452.2,30370.662674864514 +3380,29421.4,30362.784976317922 +3381,29341.3,30354.307922572545 +3382,29396.1,30346.35598960514 +3383,29427.4,30338.729798820037 +3384,29455.3,30331.398431194975 +3385,29487.7,30324.396784463068 +3386,29380.6,30316.564446002794 +3387,29343.9,30308.49254188659 +3388,29270.2,30299.876006269275 +3389,29355.8,30292.041350615586 +3390,29390.9,30284.562999158195 +3391,29430.1,30277.47201991207 +3392,29541.8,30271.3668579211 +3393,30132.5,30270.214435863665 +3394,30183.1,30269.491494487203 +3395,29891.1,30266.351316109714 +3396,30163.0,30265.493628839096 +3397,30077.6,30263.934345612215 +3398,29963.7,30261.442774279338 +3399,29834.8,30257.90217034341 +3400,29916.5,30255.068957311512 +3401,29922.4,30252.308219076564 +3402,30026.1,30250.430972445225 +3403,29932.7,30247.794200889664 +3404,29935.1,30245.199228268175 +3405,30008.3,30243.233259568857 +3406,30267.3,30243.43298355584 +3407,30284.0,30243.769639293965 +3408,30267.3,30243.96491199692 +3409,30212.4,30243.70296251977 +3410,30118.6,30242.664763660683 +3411,30165.3,30242.022732426984 +3412,30117.8,30240.991838381946 +3413,30295.9,30241.44750777297 +3414,30472.5,30243.364955841243 +3415,30446.7,30245.0523835936 +3416,30340.4,30245.843650119794 +3417,30433.0,30247.396814849093 +3418,30479.4,30249.3221524852 +3419,30393.8,30250.521138771634 +3420,30383.6,30251.625527661497 +3421,30310.9,30252.117431996256 +3422,30246.2,30252.068324676784 +3423,30358.8,30252.95406472096 +3424,30249.1,30252.92208078137 +3425,30088.4,30251.556752310156 +3426,29866.1,30248.357941087666 +3427,29062.8,30238.519285974908 +3428,29320.1,30230.897549161837 +3429,29329.8,30223.419561201987 +3430,29190.6,30214.848444511517 +3431,29097.7,30205.57750306329 +3432,29136.0,30196.701341212145 +3433,29207.8,30188.494691077605 +3434,29251.6,30180.719631400614 +3435,29288.2,30173.312829480277 +3436,29349.8,30166.478698115297 +3437,29332.8,30159.560202695255 +3438,29327.0,30152.65098939488 +3439,29387.3,30146.299528901975 +3440,29146.4,30138.0016075003 +3441,29331.6,30131.309477977476 +3442,29329.8,30124.657947039905 +3443,29280.4,30117.651657022976 +3444,29241.3,30110.379029163865 +3445,28947.3,30100.726921038025 +3446,29058.4,30092.076905095804 +3447,29373.9,30086.116930779655 +3448,29319.8,30079.757454175677 +3449,29334.8,30073.57523463895 +3450,29163.1,30066.01942356311 +3451,29378.8,30060.3163578074 +3452,29449.3,30055.245682638877 +3453,29572.3,30051.237834650172 +3454,29587.8,30047.391877516147 +3455,29643.1,30044.036758200662 +3456,29610.1,30040.435623277837 +3457,29772.7,30038.21375088549 +3458,30108.4,30038.796209384367 +3459,30188.9,30040.041883995284 +3460,30098.0,30040.522864211092 +3461,29866.4,30039.077861188594 +3462,29799.7,30037.09132292147 +3463,29695.6,30034.257370034156 +3464,29811.5,30032.408761154205 +3465,29803.8,30030.511593011845 +3466,29720.7,30027.94054244743 +3467,29403.5,30022.758463256996 +3468,29555.0,30018.87665028391 +3469,29697.1,30016.206304638403 +3470,29767.6,30014.14318177833 +3471,29616.1,30010.83991885901 +3472,29549.8,30007.013861441093 +3473,29698.1,30004.45026093121 +3474,29864.6,30003.289677852943 +3475,29584.0,29999.810095464123 +3476,29776.3,29997.955239900104 +3477,29809.9,29996.394615502595 +3478,29771.7,29994.529929896766 +3479,29536.7,29990.7305113914 +3480,29777.6,29988.961793454542 +3481,29670.1,29986.315637492266 +3482,29798.1,29984.753681994403 +3483,29826.3,29983.438713679097 +3484,29767.9,29981.650010661015 +3485,29714.0,29979.42884874682 +3486,29668.8,29976.851015977136 +3487,29714.9,29974.67714862463 +3488,28991.0,29966.51385278542 +3489,29178.9,29959.977638239485 +3490,29135.0,29953.131350785217 +3491,28992.8,29945.161796006916 +3492,28936.7,29936.792818446695 +3493,28787.6,29927.25594858407 +3494,29236.3,29921.52187432196 +3495,29374.4,29916.98144382966 +3496,29575.2,29914.14508329995 +3497,29626.7,29911.75964692402 +3498,29595.4,29909.13425566324 +3499,29374.8,29904.699946487613 +3500,29453.0,29900.951399213856 +3501,29567.5,29898.184167685115 +3502,29496.9,29894.85400861719 +3503,29194.0,29889.037792778043 +3504,29071.0,29882.24909740229 +3505,28951.7,29874.526698253725 +3506,28974.9,29867.06091652548 +3507,29004.7,29859.904394396635 +3508,28917.3,29852.081951289612 +3509,28795.6,29843.31446621667 +3510,28962.1,29836.00148309454 +3511,28977.0,29828.87284008131 +3512,28932.2,29821.431571698893 +3513,29024.3,29814.816371933757 +3514,28829.8,29806.641962208167 +3515,28902.1,29799.135389907686 +3516,29242.5,29794.516009078576 +3517,29306.2,29790.463594065473 +3518,28778.2,29782.063066313895 +3519,28835.5,29774.207771157766 +3520,28725.5,29765.504802102514 +3521,28382.5,29754.027583827807 +3522,28452.3,29743.224865289816 +3523,28870.0,29735.97818590982 +3524,28744.5,29727.75015117198 +3525,28702.9,29719.24517066433 +3526,28809.9,29711.69873771276 +3527,28623.2,29702.665553167426 +3528,28790.1,29695.092395049855 +3529,28611.3,29686.098267290105 +3530,28669.6,29677.66259702214 +3531,28805.2,29670.422243519883 +3532,28816.4,29663.3349219969 +3533,28867.8,29656.73297243676 +3534,28850.5,29650.042242375042 +3535,28870.9,29643.57633164994 +3536,28824.6,29636.77984756986 +3537,28795.1,29629.794952569282 +3538,28802.0,29622.925284913108 +3539,28817.8,29616.243747278975 +3540,28970.1,29610.881558504876 +3541,29066.1,29606.360549720604 +3542,28944.9,29600.871250552802 +3543,28974.7,29595.674808639502 +3544,28999.0,29590.72315047652 +3545,28881.8,29584.839970804514 +3546,28987.5,29579.882792623564 +3547,29013.5,29575.18252048561 +3548,29016.2,29570.543661394444 +3549,29004.4,29565.84537374802 +3550,28981.4,29560.99520467127 +3551,29018.8,29556.495659404292 +3552,28991.9,29551.81021824741 +3553,28916.8,29546.54042390511 +3554,28899.1,29541.167474329133 +3555,28969.9,29536.42666541354 +3556,28932.5,29531.41482586654 +3557,29033.0,29527.278603245242 +3558,29027.5,29523.131062969347 +3559,29000.4,29518.79304584927 +3560,29050.2,29514.90430687957 +3561,28984.7,29510.50427113783 +3562,29144.1,29507.463571792287 +3563,29204.0,29504.945201901897 +3564,29367.8,29503.80706744628 +3565,29323.5,29502.31074323511 +3566,29289.1,29500.541359473824 +3567,29210.5,29498.134377237526 +3568,29277.5,29496.303386555057 +3569,29217.6,29493.990495380327 +3570,29163.5,29491.24783566763 +3571,29119.4,29488.161961512713 +3572,29172.7,29485.544019923396 +3573,29220.0,29483.340335110755 +3574,29415.8,29482.779834404442 +3575,29452.8,29482.531039098183 +3576,29318.3,29481.168125910648 +3577,29761.5,29483.49453150475 +3578,30206.7,29489.496236637493 +3579,30345.6,29496.60083218407 +3580,30321.6,29503.447298307026 +3581,30453.5,29511.33155309286 +3582,30688.7,29521.10224559831 +3583,30625.3,29530.265712439817 +3584,30719.7,29540.136536402973 +3585,30705.7,29549.809262241954 +3586,30594.7,29558.48055467148 +3587,30659.1,29567.61432600201 +3588,30523.8,29575.549476823573 +3589,30508.7,29583.293464567774 +3590,30459.0,29590.56073872074 +3591,30617.9,29599.086375743802 +3592,30610.0,29607.47570042643 +3593,30700.9,29616.549761003804 +3594,30661.6,29625.22237709506 +3595,30629.4,29633.555801351533 +3596,31268.9,29647.12712250214 +3597,31789.2,29664.903660904612 +3598,31764.0,29682.32354753611 +3599,31726.5,29699.28766747357 +3600,31573.7,29714.842956540182 +3601,31559.1,29730.147994245242 +3602,31694.0,29746.445521263955 +3603,31763.3,29763.182902830227 +3604,31684.0,29779.12329367811 +3605,31639.5,29794.562104518955 +3606,31502.1,29808.732543485603 +3607,31570.4,29823.35219042763 +3608,31616.6,29838.2339149884 +3609,31537.6,29852.33653810053 +3610,31656.6,29867.309678863185 +3611,31756.0,29882.983457461833 +3612,31607.0,29897.290648686216 +3613,31385.1,29909.637614257284 +3614,31673.7,29924.27713613067 +3615,32045.9,29941.883964876477 +3616,32148.5,29960.196131143064 +3617,32106.5,29978.00778150702 +3618,31671.7,29992.063318589953 +3619,31618.9,30005.564037937755 +3620,31804.3,30020.491307332464 +3621,31714.8,30034.551960383647 +3622,31856.3,30049.670201376313 +3623,31797.8,30064.177502609706 +3624,31925.5,30079.624162339085 +3625,31914.9,30094.85466721594 +3626,31794.1,30108.956288234895 +3627,31624.0,30121.529265096015 +3628,31556.4,30133.436906049577 +3629,31612.9,30145.714608074064 +3630,31575.8,30157.58253663777 +3631,31479.6,30168.553635918786 +3632,31605.6,30180.479331886265 +3633,31680.1,30192.92431668389 +3634,31538.6,30204.091749740455 +3635,31596.0,30215.642855551738 +3636,31709.2,30228.037520650894 +3637,31719.4,30240.413972761675 +3638,31160.5,30248.049541452452 +3639,30733.7,30252.07983571426 +3640,30409.7,30253.387886870158 +3641,30163.2,30252.639439676215 +3642,30165.4,30251.915460923716 +3643,30116.5,30250.791681165014 +3644,29623.6,30245.586770947877 +3645,29582.7,30240.085635919266 +3646,29731.5,30235.86500823529 +3647,29796.9,30232.22214509641 +3648,29671.2,30227.566359659926 +3649,29797.0,30223.99319484947 +3650,29809.9,30220.55673680093 +3651,29718.4,30216.389460976858 +3652,29799.9,30212.93311690236 +3653,29860.0,30210.004211367905 +3654,29898.6,30207.41994405365 +3655,29952.8,30205.306915472294 +3656,29922.2,30202.957480489124 +3657,29917.7,30200.590198493363 +3658,30016.8,30199.064968630348 +3659,30157.8,30198.7225207579 +3660,30076.5,30197.708225979826 +3661,29880.4,30195.07496269369 +3662,30041.6,30193.801311551 +3663,30194.4,30193.80627991987 +3664,30275.9,30194.48755560518 +3665,30180.4,30194.370646430038 +3666,30126.1,30193.80408504888 +3667,30272.2,30194.454673554697 +3668,30230.8,30194.75629452105 +3669,30348.1,30196.028856392244 +3670,30469.7,30198.299986214715 +3671,30437.1,30200.281729067705 +3672,30385.9,30201.822129656357 +3673,30614.0,30205.242692895725 +3674,30534.0,30207.970969303227 +3675,30510.5,30210.481583665856 +3676,30410.7,30212.14314728689 +3677,30524.9,30214.738639840525 +3678,30349.9,30215.860310879194 +3679,30424.3,30217.590100830406 +3680,30428.9,30219.343709952147 +3681,30242.8,30219.538367960846 +3682,29813.6,30216.16958482424 +3683,29744.0,30212.251165033173 +3684,29520.2,30206.508001837876 +3685,29705.0,30202.346109706443 +3686,29462.2,30196.203818339585 +3687,29486.7,30190.31581984714 +3688,29528.2,30184.821082752973 +3689,29488.3,30179.040824804815 +3690,29533.7,30173.685299287765 +3691,29560.5,30168.59662460488 +3692,29648.5,30164.280470043846 +3693,29789.0,30161.166109296595 +3694,29719.5,30157.50083038127 +3695,29682.8,30153.561404403 +3696,29697.9,30149.779981959826 +3697,29573.3,30144.995915719497 +3698,29555.6,30140.10466330689 +3699,29543.2,30135.151097636295 +3700,29700.0,30131.539885207778 +3701,29668.5,30127.69723055875 +3702,29682.0,30123.998498354948 +3703,29690.5,30120.401000443286 +3704,29734.9,30117.20182201637 +3705,29712.7,30113.844960422874 +3706,29714.6,30110.531724236793 +3707,29669.7,30106.87336967881 +3708,29520.5,30102.007200635835 +3709,29590.2,30097.759837974958 +3710,29765.2,30095.000005294667 +3711,29839.9,30092.882992802595 +3712,29737.1,30089.930436845727 +3713,29693.5,30086.64055770178 +3714,29755.6,30083.89333315654 +3715,29642.6,30080.23114781914 +3716,29739.0,30077.39935406131 +3717,29831.1,30075.355376019306 +3718,29794.6,30073.025455886367 +3719,29848.4,30071.16134421926 +3720,29725.0,30068.288635968478 +3721,29679.0,30065.058024881604 +3722,29780.9,30062.69986699877 +3723,29756.2,30060.156299637783 +3724,29704.9,30057.20811457855 +3725,29777.3,30054.885225660884 +3726,29721.2,30052.1160536637 +3727,29630.6,30048.61799512707 +3728,29678.1,30045.543156993237 +3729,29626.2,30042.063130794126 +3730,29628.7,30038.63273136845 +3731,29700.7,30035.82831036124 +3732,29703.3,30033.0687393209 +3733,29647.4,30029.868168870104 +3734,29771.4,30027.72320481309 +3735,29958.3,30027.147078632068 +3736,29858.8,30025.750007440103 +3737,30011.3,30025.630090365914 +3738,29985.5,30025.29706057035 +3739,30036.3,30025.388371271012 +3740,29929.5,30024.59261715258 +3741,29947.9,30023.9561638982 +3742,30005.2,30023.800511085767 +3743,29909.9,30022.855278628627 +3744,30285.3,30025.033243121336 +3745,30833.8,30031.74500043983 +3746,30909.0,30039.02512491751 +3747,31235.0,30048.95022761529 +3748,31212.5,30058.606242323876 +3749,31158.3,30067.732331599196 +3750,31260.4,30077.629988598375 +3751,31277.9,30087.59073558096 +3752,31455.4,30098.941849808507 +3753,31461.4,30110.2485564491 +3754,31471.9,30121.548568428778 +3755,31392.0,30132.09173383601 +3756,31400.0,30142.613794136127 +3757,31437.1,30153.35641825118 +3758,31491.4,30164.460514365277 +3759,31529.8,30175.791132503324 +3760,31302.3,30185.139753810352 +3761,31339.9,30194.72282639284 +3762,31341.7,30204.241309161367 +3763,31466.9,30214.719804521024 +3764,31454.6,30225.00926672417 +3765,31479.3,30235.4183184526 +3766,31392.7,30245.02231580984 +3767,31361.2,30254.285201155817 +3768,30195.0,30253.79320778523 +3769,29551.7,30247.966708135562 +3770,29378.7,30240.752876532777 +3771,29547.5,30234.99974062794 +3772,29509.1,30228.975676390364 +3773,29460.1,30222.594965382978 +3774,29502.4,30216.618243678557 +3775,29569.0,30211.24381841981 +3776,29631.1,30206.429346897654 +3777,29549.0,30200.973501695185 +3778,29583.0,30195.845090892737 +3779,29504.5,30190.10778723388 +3780,29490.5,30184.3019134809 +3781,29549.6,30179.03467768438 +3782,29892.1,30176.653477039697 +3783,29876.0,30174.15842743771 +3784,29898.2,30171.868316006694 +3785,29927.9,30169.84368267884 +3786,30172.2,30169.86323717943 +3787,31050.5,30177.171426082507 +3788,31350.0,30186.90444329344 +3789,31398.6,30196.960008079386 +3790,31257.4,30205.760339962548 +3791,31118.1,30213.33162344834 +3792,31075.0,30220.482398357482 +3793,31112.0,30227.88088467817 +3794,30966.6,30234.01133376798 +3795,30006.0,30232.119123529243 +3796,30228.8,30232.091578935637 +3797,30534.3,30234.599532637414 +3798,30519.8,30236.966341495197 +3799,30547.9,30239.54670380644 +3800,30494.4,30241.661668920082 +3801,30514.9,30243.92920693734 +3802,30374.5,30245.012781983507 +3803,30399.2,30246.292343958747 +3804,30127.5,30245.30651537818 +3805,30754.5,30249.53218744973 +3806,30411.3,30250.874658923178 +3807,30458.0,30252.593541421742 +3808,30327.0,30253.211022405794 +3809,30371.3,30254.1910139211 +3810,30224.6,30253.94544534084 +3811,30127.6,30252.896935421002 +3812,30192.0,30252.39156666232 +3813,30409.1,30253.692051586288 +3814,30300.0,30254.07634991337 +3815,30192.5,30253.56534286015 +3816,30211.0,30253.212103500315 +3817,30076.5,30251.74561301484 +3818,30188.3,30251.219093404758 +3819,30283.0,30251.482835368206 +3820,30248.7,30251.45974129876 +3821,30305.0,30251.904058798358 +3822,30321.0,30252.477469098787 +3823,30477.3,30254.343216243196 +3824,30496.5,30256.3528161084 +3825,30534.1,30258.65777199132 +3826,30423.9,30260.02907678807 +3827,30286.7,30260.25041225041 +3828,30102.9,30258.944599700615 +3829,30157.9,30258.106055304757 +3830,30105.8,30256.842104638326 +3831,30285.8,30257.082419122657 +3832,30330.0,30257.687544275166 +3833,30311.0,30258.12997129363 +3834,30138.3,30257.135531697833 +3835,30003.8,30255.03316214018 +3836,30178.9,30254.401351665987 +3837,30125.9,30253.33495040735 +3838,30058.9,30251.72138235418 +3839,30093.3,30250.406682085682 +3840,29737.9,30246.153514599493 +3841,29869.4,30243.026929416097 +3842,30008.9,30241.083967346254 +3843,30102.0,30239.929743550852 +3844,30081.0,30238.610824517236 +3845,30088.7,30237.366751284728 +3846,30087.8,30236.125533431743 +3847,30041.6,30234.511213652226 +3848,30015.9,30232.697012709054 +3849,29888.6,30229.8414358401 +3850,29991.9,30227.866818115286 +3851,30005.5,30226.0214503301 +3852,29631.7,30221.089322111595 +3853,29385.0,30214.15082151316 +3854,29545.6,30208.602681915545 +3855,29438.2,30202.209298663136 +3856,29034.0,30192.51461568668 +3857,29104.1,30183.482129249445 +3858,29044.5,30174.029995396755 +3859,28970.5,30164.04219460508 +3860,29184.3,30155.911553985952 +3861,29178.0,30147.796105405156 +3862,29160.1,30139.599457227523 +3863,29077.7,30130.78701359908 +3864,29220.1,30123.229445021498 +3865,29193.9,30115.51716746945 +3866,29253.2,30108.361008403313 +3867,29258.4,30101.307390076316 +3868,29363.0,30095.180357793524 +3869,29269.3,30088.326578890672 +3870,29294.3,30081.737146700707 +3871,29309.0,30075.32439029655 +3872,28994.9,30066.358212783718 +3873,29063.6,30058.036567864354 +3874,28913.5,30048.538339085397 +3875,28723.9,30037.545489798384 +3876,28454.8,30024.41067245566 +3877,28760.1,30013.91846770499 +3878,28410.8,30000.614580006193 +3879,28392.3,29987.267571043485 +3880,28459.8,29974.591491615738 +3881,28485.7,29962.235545627227 +3882,28598.7,29950.919897945674 +3883,28581.0,29939.551268087205 +3884,28397.2,29926.75167250142 +3885,28550.0,29915.32634741842 +3886,28624.1,29904.61077607055 +3887,28416.7,29892.262968800256 +3888,28496.0,29880.6757242459 +3889,28229.6,29866.973851015646 +3890,27409.5,29846.579877148295 +3891,27538.2,29827.423197669887 +3892,27422.4,29807.464498934038 +3893,27258.3,29786.309606826704 +3894,27466.0,29767.05392544225 +3895,27629.4,29749.31405884107 +3896,27553.6,29731.09236540671 +3897,27459.5,29712.24097648217 +3898,27435.1,29693.343540992693 +3899,27443.8,29674.675129864125 +3900,27422.0,29655.98073044617 +3901,27302.4,29636.44894015201 +3902,27798.6,29621.197081727514 +3903,28077.8,29608.388807190357 +3904,27923.0,29594.40217808504 +3905,28111.7,29582.097595694297 +3906,27896.9,29568.112553406376 +3907,27410.0,29550.202905660266 +3908,27366.3,29532.07923009462 +3909,27360.4,29514.056995819974 +3910,26982.5,29493.04822407043 +3911,26564.7,29468.746579057395 +3912,26291.3,29442.377727778912 +3913,25758.7,29411.807788129296 +3914,25331.8,29377.948802335693 +3915,25500.0,29345.766654598472 +3916,25373.3,29312.800126344544 +3917,25221.6,29278.848258076123 +3918,25345.3,29246.204704067193 +3919,24801.2,29209.316698224313 +3920,24172.9,29167.52070902743 +3921,24038.4,29124.955391940064 +3922,23978.0,29082.242069185377 +3923,23718.3,29037.728027117446 +3924,23668.8,28993.172607805267 +3925,23642.7,28948.77034549983 +3926,22789.7,28897.65772852473 +3927,23253.1,28850.814925798386 +3928,23722.8,28808.25878533533 +3929,23525.7,28764.420123216365 +3930,23369.5,28719.64900186187 +3931,23200.1,28673.8436159543 +3932,23238.6,28628.737859805304 +3933,23050.1,28582.4421099314 +3934,22575.8,28532.59445756682 +3935,22471.5,28482.294918499876 +3936,21740.2,28426.343923325607 +3937,20834.7,28363.342728941163 +3938,21530.0,28306.634490526714 +3939,22106.5,28255.18109226508 +3940,21963.9,28202.97129066952 +3941,22739.3,28157.629620207532 +3942,22621.8,28111.689125434026 +3943,22863.2,28068.133199081876 +3944,22599.8,28022.752840583275 +3945,22290.7,27975.18393734192 +3946,22372.7,27928.69029470838 +3947,21980.0,27879.323570270964 +3948,22141.4,27831.705947281163 +3949,22397.8,27786.611292117002 +3950,22499.8,27742.737339485328 +3951,22672.6,27700.66151094188 +3952,22474.9,27657.29419549838 +3953,22347.8,27613.232003004618 +3954,22281.8,27568.987754017027 +3955,22182.0,27524.282461452574 +3956,21981.5,27478.28426675172 +3957,21468.6,27428.411368272456 +3958,21580.6,27379.881813349035 +3959,22122.7,27336.253748507967 +3960,21799.8,27290.308074246488 +3961,22152.8,27247.67315246851 +3962,21974.0,27203.908230041387 +3963,21442.3,27156.094053858473 +3964,21133.7,27106.115679967534 +3965,21210.4,27057.188578888963 +3966,21321.3,27009.58784379445 +3967,20707.5,26957.288359613583 +3968,20255.4,26901.67102882841 +3969,20232.6,26846.3260410373 +3970,20566.7,26794.212961858568 +3971,21163.8,26747.48754308796 +3972,21183.6,26701.31420248142 +3973,21547.0,26658.539810759583 +3974,21202.4,26613.260642205565 +3975,21408.8,26570.07009745697 +3976,20714.6,26521.476984615005 +3977,21110.0,26476.568461921102 +3978,21529.1,26435.51063236159 +3979,21617.3,26395.52548188556 +3980,21674.2,26356.34435755456 +3981,22197.5,26321.831126371537 +3982,22460.3,26289.78522490787 +3983,22567.5,26258.89489109121 +3984,22658.9,26229.019414816594 +3985,22359.0,26196.903071125173 +3986,22445.0,26165.766946053594 +3987,22197.5,26132.835270152736 +3988,22084.9,26099.242446334043 +3989,21931.9,26064.658691592682 +3990,21788.4,26029.171067596064 +3991,21748.9,25993.650145873275 +3992,21773.8,25958.63064258802 +3993,21266.5,25919.69179908107 +3994,21232.9,25880.79726132936 +3995,21028.1,25840.525914762315 +3996,21128.6,25801.422795137732 +3997,21004.5,25761.61430721128 +3998,20914.7,25721.390951964713 +3999,21067.0,25682.765300911065 +4000,21170.2,25645.316626214706 +4001,21051.8,25607.196156287613 +4002,21014.8,25569.08498486614 +4003,20862.2,25530.02369868468 +4004,20698.4,25489.927236454932 +4005,20675.5,25449.97348345531 +4006,20356.6,25407.704823841574 +4007,20387.4,25366.042543145795 +4008,20353.6,25324.44550959272 +4009,20866.1,25287.446791670787 +4010,20568.5,25248.285407507545 +4011,20369.0,25207.793412424497 +4012,20519.1,25168.88309364919 +4013,20793.8,25132.575350133433 +4014,20881.5,25097.296716522367 +4015,21087.5,25064.020395223426 +4016,21056.8,25030.765454184228 +4017,20963.4,24997.011384025023 +4018,21108.4,24964.7407501327 +4019,20963.9,24931.53875220629 +4020,20645.1,24895.96664637885 +4021,20791.0,24861.900533130894 +4022,20535.5,24825.99679426674 +4023,20656.0,24791.391011741704 +4024,20564.8,24756.31556766086 +4025,20582.9,24721.681413572387 +4026,20512.6,24686.75127736017 +4027,20506.6,24652.061225265898 +4028,20641.0,24618.774410118465 +4029,20547.0,24584.983751113334 +4030,20441.7,24550.599653593723 +4031,20457.3,24516.630361862655 +4032,20561.4,24483.80687338247 +4033,20381.9,24449.766152441538 +4034,20464.2,24416.69091466194 +4035,20450.0,24383.772317859766 +4036,20482.7,24351.39827372815 +4037,20416.5,24318.743516269828 +4038,19419.8,24278.088383354727 +4039,19244.9,24236.319185152613 +4040,18997.6,24192.844337143048 +4041,19156.5,24151.048948453063 +4042,19382.8,24111.478417760507 +4043,19237.0,24071.026314708553 +4044,19147.3,24030.165515416367 +4045,19190.4,23990.001486242785 +4046,19070.5,23949.175747767742 +4047,18946.6,23907.660596333986 +4048,18866.2,23865.822749061506 +4049,18609.9,23822.205132886724 +4050,18203.7,23775.578534273558 +4051,18059.4,23728.14136801403 +4052,17798.0,23678.928576578226 +4053,18503.9,23635.982281336914 +4054,19173.6,23598.950063234533 +4055,18959.7,23560.450062709766 +4056,18960.1,23522.272883766116 +4057,18473.9,23480.377673112456 +4058,18367.8,23437.949642630196 +4059,18299.8,23395.309396633264 +4060,18496.2,23354.652887117634 +4061,18114.9,23311.169460668527 +4062,18250.1,23269.168884231443 +4063,18297.2,23227.907731665207 +4064,18657.9,23189.982356298693 +4065,18994.4,23155.16424545804 +4066,19888.2,23128.05250898121 +4067,19685.4,23099.482778616224 +4068,19338.5,23068.27130327501 +4069,19172.9,23035.94457046775 +4070,19460.0,23006.26868191615 +4071,19816.3,22979.795912771617 +4072,19514.1,22951.034950839905 +4073,19438.9,22921.888602700154 +4074,20237.4,22899.6106889848 +4075,20111.2,22876.47035131688 +4076,20634.0,22857.86063885782 +4077,20387.1,22837.35640119095 +4078,20683.0,22819.477924832518 +4079,20564.2,22800.761925456318 +4080,20054.2,22777.968880431785 +4081,19788.6,22753.16083993028 +4082,19936.3,22729.784401424633 +4083,19966.7,22706.85424041696 +4084,19850.1,22683.146736347113 +4085,20094.3,22661.662531066224 +4086,20126.4,22640.623007986836 +4087,20076.7,22619.345638625953 +4088,20554.3,22602.208330421592 +4089,20643.3,22585.951829754194 +4090,20876.3,22571.763847764534 +4091,20791.0,22556.985724546572 +4092,20542.4,22540.267170815896 +4093,20516.8,22523.474912136928 +4094,20832.7,22509.443585065255 +4095,20815.4,22495.38513207716 +4096,20269.9,22476.916375794364 +4097,19968.7,22456.101302136318 +4098,20036.8,22436.024112906973 +4099,20064.3,22416.341755123514 +4100,20446.0,22399.990371263568 +4101,20412.5,22383.49667523648 +4102,20483.1,22367.725748471035 +4103,20570.1,22352.80769246713 +4104,20666.4,22338.81260788234 +4105,20661.0,22324.88885180033 +4106,20424.8,22309.12047958622 +4107,20604.4,22294.973421664345 +4108,20649.1,22281.31472106962 +4109,20880.0,22269.685553259915 +4110,21179.9,22260.641689747383 +4111,21107.7,22251.073708919605 +4112,21345.8,22243.561064032303 +4113,21191.1,22234.826947318343 +4114,21287.1,22226.96199339869 +4115,20953.2,22216.391354449326 +4116,21089.0,22207.035409599124 +4117,21604.6,22202.03594561905 +4118,21578.4,22196.86054358072 +4119,21382.8,22190.104854422374 +4120,21390.0,22183.464980111814 +4121,21232.1,22175.569835048645 +4122,21095.5,22166.606599903014 +4123,20879.5,22155.925217331205 +4124,20851.2,22145.09762216663 +4125,20960.1,22135.263617003424 +4126,20885.6,22124.892964580158 +4127,20713.1,22113.17684039277 +4128,20580.0,22100.4533811364 +4129,20405.0,22086.383228595852 +4130,20458.5,22072.873824209168 +4131,20309.0,22058.23586716179 +4132,20424.5,22044.67789316045 +4133,20359.6,22030.693844254554 +4134,20103.5,22014.70053434373 +4135,20091.6,21998.74119380976 +4136,20226.2,21984.031308383954 +4137,20415.7,21971.016110804005 +4138,20410.4,21958.06493975999 +4139,20497.5,21945.944068890614 +4140,20352.6,21932.721296534677 +4141,20715.4,21922.619045111154 +4142,20685.9,21912.355816521023 +4143,20075.4,21897.11136991089 +4144,20310.8,21883.946960202087 +4145,20255.4,21870.432047669292 +4146,20210.0,21856.6525286015 +4147,20103.0,21842.09939558406 +4148,19871.8,21825.748363255563 +4149,19995.3,21810.55792040697 +4150,20088.4,21796.26615343264 +4151,19977.4,21781.171828507886 +4152,20407.4,21769.771232420684 +4153,20366.6,21758.126657877776 +4154,20350.1,21746.44178934767 +4155,20202.9,21733.632313917402 +4156,20345.5,21722.112543677424 +4157,20338.0,21710.626132526573 +4158,20537.6,21700.89147582511 +4159,20446.2,21690.479098432366 +4160,20560.5,21681.10167852836 +4161,20738.7,21673.2809177107 +4162,20685.8,21665.08605532306 +4163,20629.5,21656.49198017515 +4164,20553.7,21647.340179509796 +4165,20452.1,21637.421173870713 +4166,20598.0,21628.795272012863 +4167,20339.9,21618.099045689105 +4168,20275.8,21606.95963452156 +4169,20305.9,21596.16245913134 +4170,20418.9,21586.392646192493 +4171,20900.5,21580.70059103737 +4172,20805.9,21574.270710613826 +4173,20676.4,21566.819501397113 +4174,20983.4,21561.97784578386 +4175,21096.6,21558.115788972373 +4176,20999.4,21553.479143420733 +4177,21038.4,21549.2046277077 +4178,21164.1,21546.00873868108 +4179,21066.3,21542.027753297836 +4180,21040.0,21537.86154787628 +4181,20908.0,21532.634481088928 +4182,20834.4,21526.840004067446 +4183,20903.9,21521.670377477676 +4184,21039.9,21517.672283058775 +4185,20816.7,21511.855085688992 +4186,20983.5,21507.470396181197 +4187,21165.0,21504.628318204588 +4188,21206.4,21502.15339440206 +4189,21188.3,21499.548801917394 +4190,21118.1,21496.383251694017 +4191,20875.4,21491.229863713153 +4192,21032.4,21487.422147001842 +4193,20962.9,21483.069266113864 +4194,21116.8,21480.029687141967 +4195,21241.9,21478.053507165685 +4196,21188.3,21475.648913745223 +4197,21306.6,21474.24601819547 +4198,21296.8,21472.773437131607 +4199,21231.2,21470.768678317236 +4200,21183.4,21468.38387600755 +4201,21319.9,21467.151644671387 +4202,21167.0,21464.660759653365 +4203,21295.0,21463.25278654421 +4204,21226.3,21461.28637337787 +4205,21208.0,21459.184411773072 +4206,21296.9,21457.837653169147 +4207,21495.5,21458.150203765254 +4208,21383.1,21457.527380497493 +4209,21423.1,21457.241676095022 +4210,21425.1,21456.974940193817 +4211,21329.1,21455.913737370633 +4212,21142.7,21453.314453243074 +4213,21159.0,21450.872009647697 +4214,21120.0,21448.126183841494 +4215,20958.9,21444.066215510862 +4216,21042.1,21440.730396295006 +4217,21087.6,21437.799853587167 +4218,21226.1,21436.043008329183 +4219,21195.5,21434.04680079118 +4220,21214.3,21432.22317588835 +4221,21508.1,21432.852859076 +4222,21424.0,21432.779391365828 +4223,21481.9,21433.187031271504 +4224,21461.2,21433.419504041038 +4225,21387.9,21433.04174882078 +4226,21346.2,21432.32107040733 +4227,21415.1,21432.17815695997 +4228,21414.6,21432.032280138726 +4229,21431.7,21432.029522627203 +4230,21470.5,21432.348779700835 +4231,21390.1,21431.998167421163 +4232,21444.9,21432.1052365712 +4233,21405.4,21431.883616350693 +4234,21410.1,21431.70283945152 +4235,21699.5,21433.92522252661 +4236,21447.3,21434.03621653054 +4237,21395.1,21433.713094401657 +4238,21403.0,21433.458213950194 +4239,21314.2,21432.46851922862 +4240,21344.2,21431.73600039685 +4241,21251.4,21430.239436078205 +4242,21177.3,21428.140353621126 +4243,21273.6,21426.857861059954 +4244,21388.9,21426.542858063607 +4245,21372.2,21426.09187998839 +4246,21136.9,21423.69194737438 +4247,21029.1,21420.41732540447 +4248,21039.9,21417.25950527663 +4249,21079.9,21414.459841332427 +4250,21123.4,21412.044406964524 +4251,21160.9,21409.96022101461 +4252,21235.9,21408.515737852664 +4253,21173.0,21406.561250401603 +4254,21258.0,21405.328376954287 +4255,21354.9,21404.909884199482 +4256,21476.5,21405.503993044298 +4257,21443.0,21405.815163226504 +4258,21428.3,21406.0017593823 +4259,21298.2,21405.10713897249 +4260,21231.6,21403.66724570301 +4261,20729.9,21398.075816278088 +4262,20806.0,21393.162324026816 +4263,20744.0,21387.77508482327 +4264,20746.4,21382.452470011463 +4265,20711.9,21376.88771922299 +4266,20812.1,21372.200684208692 +4267,20845.8,21367.832213800324 +4268,20905.9,21363.998751445135 +4269,20789.1,21359.227807449737 +4270,20801.9,21354.60268041696 +4271,20730.0,21349.41925568321 +4272,20760.7,21344.533618706584 +4273,20609.6,21338.434584526447 +4274,20632.0,21332.572056854027 +4275,20722.6,21327.510048083455 +4276,20758.3,21322.78631324459 +4277,20730.0,21317.866924752932 +4278,20848.1,21313.96844404959 +4279,20868.2,21310.269120862456 +4280,21102.0,21308.540746415467 +4281,21050.0,21306.395180055173 +4282,21014.1,21303.969493913635 +4283,20987.9,21301.346510561656 +4284,20942.6,21298.369361096415 +4285,21044.4,21296.26173154375 +4286,20840.5,21292.47947651019 +4287,20595.4,21286.694584588946 +4288,20701.8,21281.840687621403 +4289,20622.4,21276.36815079467 +4290,20341.5,21268.609908879364 +4291,20246.6,21260.12849884717 +4292,20269.0,21251.903366076654 +4293,20384.0,21244.70084851585 +4294,20300.8,21236.86764645348 +4295,20268.6,21228.83223030034 +4296,20283.2,21220.984659924405 +4297,20345.1,21213.715907559887 +4298,20335.3,21206.426148990926 +4299,20275.9,21198.703940285606 +4300,20297.1,21191.221749909793 +4301,20177.7,21182.810781030872 +4302,19937.8,21172.47874135427 +4303,20080.6,21163.417506986185 +4304,19985.8,21153.64474759211 +4305,20056.9,21144.54313142952 +4306,20087.0,21135.766839882388 +4307,20096.9,21127.1455383066 +4308,20002.5,21117.812380312353 +4309,20074.1,21109.150866782787 +4310,20115.1,21100.90148199621 +4311,20076.3,21092.39856513317 +4312,20012.8,21083.439240941192 +4313,20078.0,21075.0953468255 +4314,20044.1,21066.539368843543 +4315,20214.1,21059.465183209988 +4316,20215.9,21052.464642270486 +4317,20285.9,21046.10310996949 +4318,20204.8,21039.121341422026 +4319,20109.9,21031.409960995286 +4320,20027.8,21023.081247626033 +4321,20008.4,21014.660656359425 +4322,20073.7,21006.85185423196 +4323,20062.6,20999.015739259085 +4324,20039.5,20991.05295304117 +4325,19990.1,20982.746289530456 +4326,19408.6,20969.682834845556 +4327,19423.5,20956.85144202526 +4328,19226.0,20942.48752964331 +4329,19139.2,20927.52248790353 +4330,19093.1,20912.2990647674 +4331,19088.7,20897.165462570163 +4332,19193.1,20883.023840474143 +4333,18855.4,20866.19708661129 +4334,19108.9,20851.613708299163 +4335,19133.5,20837.355503251038 +4336,19128.5,20823.174129780076 +4337,19092.9,20808.815008371115 +4338,18962.1,20793.489572617 +4339,18921.3,20777.952729690718 +4340,18742.9,20761.064325294945 +4341,18812.6,20744.89449686926 +4342,18885.0,20729.45968776661 +4343,19923.5,20722.77122562747 +4344,20288.9,20719.17063454343 +4345,20411.5,20716.617351269208 +4346,20292.4,20713.096875325067 +4347,19720.1,20704.856237355565 +4348,19399.7,20694.02506526133 +4349,19457.1,20683.760126960406 +4350,19390.8,20673.030167400568 +4351,19566.1,20663.844024932514 +4352,19515.9,20654.317518501543 +4353,19415.0,20644.032725816884 +4354,19062.5,20630.90797290554 +4355,19180.0,20618.867242839933 +4356,19275.1,20607.71564746367 +4357,19440.4,20598.028380679738 +4358,19400.1,20588.087066317254 +4359,19279.5,20577.227422613378 +4360,19334.9,20566.91765147136 +4361,19474.0,20557.847795442554 +4362,19425.0,20548.44656892436 +4363,19380.1,20538.750746775608 +4364,19424.8,20529.50634223805 +4365,19389.3,20520.04404894147 +4366,19292.0,20509.852812020796 +4367,19272.0,20499.58017457664 +4368,19168.8,20488.536355700486 +4369,19220.1,20478.009912914593 +4370,19189.9,20467.320204093725 +4371,19121.7,20456.15323144564 +4372,19189.0,20445.63743699381 +4373,19229.9,20435.548329632864 +4374,19209.3,20425.371994947112 +4375,19119.9,20414.538202457927 +4376,19104.1,20403.6631966284 +4377,19163.4,20393.37055599248 +4378,19232.7,20383.73843519586 +4379,19188.6,20373.82027390793 +4380,19335.6,20365.20433802488 +4381,19257.1,20356.008451402267 +4382,19274.0,20347.029128154114 +4383,19219.9,20337.675359455738 +4384,19281.5,20328.910418713367 +4385,19290.7,20320.29456461616 +4386,19310.0,20311.910377357937 +4387,19263.6,20303.210706176542 +4388,19256.0,20294.52016089707 +4389,19345.0,20286.64032553693 +4390,19321.1,20278.62754275239 +4391,19240.9,20270.015695924572 +4392,19237.2,20261.444611311093 +4393,19181.6,20252.48324524212 +4394,19255.4,20244.208695489073 +4395,19254.6,20235.996175194556 +4396,19234.1,20227.681684114104 +4397,19034.5,20217.77976142436 +4398,19141.2,20208.84548954532 +4399,19127.8,20199.874157681876 +4400,19007.6,20189.97976633182 +4401,19031.6,20180.36665623778 +4402,19064.6,20171.107181912157 +4403,19080.0,20162.052350526996 +4404,19063.3,20152.934073759137 +4405,18969.1,20143.10972459931 +4406,19068.8,20134.194291200147 +4407,19035.3,20125.07483650139 +4408,19148.1,20116.967161509678 +4409,19212.6,20109.4620398374 +4410,19227.4,20102.142022909287 +4411,19483.3,20097.006404461907 +4412,19433.5,20091.500127246458 +4413,19289.8,20084.847014157276 +4414,19292.2,20078.2690306373 +4415,19305.9,20071.85932913824 +4416,19255.0,20065.080413543732 +4417,19215.4,20058.029123804783 +4418,19066.1,20049.79734684375 +4419,19094.0,20041.865418654175 +4420,19115.3,20034.17607908028 +4421,19130.1,20026.673373029822 +4422,19093.6,20018.9300255358 +4423,19115.2,20011.43019129899 +4424,19136.1,20004.166040333854 +4425,19358.1,19998.80449643067 +4426,19458.1,19994.31732218643 +4427,19794.2,19992.656597520985 +4428,19699.4,19990.222932811266 +4429,19523.3,19986.3480537008 +4430,19601.0,19983.150144541458 +4431,19792.4,19981.567155790075 +4432,19733.3,19979.50684744327 +4433,19887.8,19978.745794767394 +4434,19919.2,19978.25163879422 +4435,19823.9,19976.970712331196 +4436,19774.4,19975.289627581562 +4437,19946.7,19975.052369261382 +4438,20153.0,19976.52911308494 +4439,20226.6,19978.604390154775 +4440,20384.1,19981.969498950173 +4441,20291.5,19984.538216801207 +4442,20167.2,19986.054082221945 +4443,20302.8,19988.68267904998 +4444,20265.0,19990.975768850396 +4445,20288.3,19993.443189855785 +4446,20377.4,19996.629553425446 +4447,20207.7,19998.38117538872 +4448,19884.7,19997.43776314483 +4449,19758.4,19995.45404726811 +4450,19731.9,19993.26687675136 +4451,19514.3,19989.292047898653 +4452,19364.9,19984.11037115261 +4453,19394.1,19979.21401952479 +4454,19453.4,19974.850417703008 +4455,19507.0,19970.967841622485 +4456,19539.3,19967.38553588288 +4457,19691.9,19965.099348863107 +4458,20107.1,19966.27777750325 +4459,20392.8,19969.817381009445 +4460,20455.9,19973.851261664968 +4461,20367.0,19977.11390679638 +4462,20431.2,19980.882256117573 +4463,20170.0,19982.451697975517 +4464,20254.4,19984.708530357464 +4465,19870.1,19983.75742222172 +4466,19855.2,19982.690555647267 +4467,19940.4,19982.339596679238 +4468,20091.0,19983.241342764886 +4469,20040.9,19983.719837845674 +4470,20084.4,19984.555357863555 +4471,20150.9,19985.935811325267 +4472,20173.2,19987.48987098232 +4473,20192.3,19989.1895401028 +4474,20125.0,19990.316597861285 +4475,20098.7,19991.21604518194 +4476,20143.9,19992.483131943918 +4477,20219.9,19994.370408857245 +4478,20188.5,19995.98144280864 +4479,20140.1,19997.177447432634 +4480,20291.9,19999.6232777444 +4481,20256.1,20001.75171527349 +4482,20373.8,20004.839252906077 +4483,20319.8,20007.453035039634 +4484,20390.0,20010.627698649263 +4485,20343.6,20013.390954262133 +4486,20506.2,20017.480655886517 +4487,20554.1,20021.933928451774 +4488,20523.4,20026.095472614 +4489,20503.9,20030.060655413887 +4490,20413.0,20033.23857528597 +4491,20474.0,20036.89634644542 +4492,20411.3,20040.003430707286 +4493,20295.7,20042.125393937928 +4494,20332.1,20044.531822204004 +4495,20432.8,20047.75396475833 +4496,20514.3,20051.625716088136 +4497,20552.0,20055.778199772052 +4498,20492.9,20059.405766578922 +4499,20504.3,20063.097834906068 +4500,20422.2,20066.077935861205 +4501,20532.5,20069.94865838518 +4502,20908.7,20076.909250431774 +4503,20881.9,20083.589671590016 +4504,21085.0,20091.900130746944 +4505,20899.6,20098.603034226224 +4506,21343.1,20108.930809875797 +4507,21797.8,20122.946321827036 +4508,21612.5,20135.307763139677 +4509,21655.7,20147.925126101174 +4510,21617.7,20160.122427959257 +4511,21613.4,20172.18282274798 +4512,21710.1,20184.94562089945 +4513,22133.0,20201.112047282026 +4514,21984.0,20215.90779792699 +4515,22119.7,20231.706903338385 +4516,21976.3,20246.184854348023 +4517,21836.2,20259.380000784968 +4518,21823.0,20272.35610036352 +4519,21791.3,20284.961443928965 +4520,21431.5,20294.476286717938 +4521,21564.8,20305.018392222355 +4522,21529.9,20315.18338481802 +4523,21577.7,20325.660701126588 +4524,21371.3,20334.338205681554 +4525,21264.7,20342.059050447682 +4526,21754.9,20353.783871605792 +4527,21947.3,20367.00807184143 +4528,21682.9,20377.928336805402 +4529,21748.9,20389.305695006187 +4530,21727.6,20400.411871811113 +4531,21805.6,20412.073184078243 +4532,21850.2,20424.007846451037 +4533,21695.9,20434.562968057253 +4534,21769.5,20445.641283675035 +4535,21587.9,20455.120609121717 +4536,21538.3,20464.10964970992 +4537,21502.6,20472.727826890754 +4538,21580.4,20481.920127082532 +4539,21605.9,20491.247760882678 +4540,21544.8,20499.990932991535 +4541,21511.6,20508.38602898331 +4542,21550.7,20517.03593745648 +4543,21644.7,20526.394145444396 +4544,21715.9,20536.265563324527 +4545,21647.7,20545.48908562059 +4546,21725.5,20555.281707316684 +4547,21437.4,20562.602191073394 +4548,21505.1,20570.4237496537 +4549,21553.4,20578.581228909687 +4550,21585.1,20586.934081781805 +4551,21538.7,20594.832554132165 +4552,21683.5,20603.867138745176 +4553,21872.4,20614.39438240704 +4554,21744.0,20623.76870288499 +4555,21613.3,20631.980580869345 +4556,21671.1,20640.60397853848 +4557,21697.8,20649.377389504967 +4558,21622.3,20657.451436065094 +4559,21580.0,20665.107440745054 +4560,21366.6,20670.928955759617 +4561,21407.5,20677.041578533393 +4562,21300.6,20682.216337217767 +4563,21288.6,20687.248566784427 +4564,21268.3,20692.07057037958 +4565,21350.0,20697.53056564614 +4566,21290.9,20702.454793317127 +4567,21329.9,20707.66180748047 +4568,21318.0,20712.726854721295 +4569,21317.9,20717.749038499544 +4570,21269.5,20722.327884653074 +4571,21296.1,20727.089478971306 +4572,21301.6,20731.857201137518 +4573,21075.3,20734.70734884592 +4574,20856.1,20735.714756739315 +4575,20892.5,20737.015879090024 +4576,20923.0,20738.55931577807 +4577,20892.5,20739.836831829703 +4578,20838.0,20740.651463930702 +4579,20913.1,20742.08257211385 +4580,20975.0,20744.0154968266 +4581,21028.7,20746.37802382389 +4582,20830.0,20747.07198213241 +4583,20853.8,20747.95769182426 +4584,20804.1,20748.423603095427 +4585,20545.1,20746.736270289657 +4586,20543.6,20745.05049211298 +4587,20540.0,20743.348828278016 +4588,20522.0,20741.51190854127 +4589,20498.1,20739.491892702754 +4590,20390.0,20736.59154504547 +4591,20429.3,20734.041407742192 +4592,20522.6,20732.286707263003 +4593,20542.9,20730.715033343808 +4594,20522.4,20728.986277880373 +4595,20465.0,20726.79552038759 +4596,20580.9,20725.584769181052 +4597,20367.1,20722.609791843453 +4598,20379.4,20719.76157780326 +4599,20430.1,20717.35774728207 +4600,20410.6,20714.812039835746 +4601,20606.0,20713.90903535578 +4602,20561.1,20712.640910581045 +4603,20499.8,20710.874595970414 +4604,20424.6,20708.49887318228 +4605,20230.3,20704.530417803177 +4606,19998.3,20698.66958446041 +4607,19954.3,20692.492243510533 +4608,19805.1,20685.127992527043 +4609,19883.0,20678.471328688644 +4610,19961.1,20672.518039653885 +4611,19934.3,20666.391748868376 +4612,19995.7,20660.825842238763 +4613,19944.7,20654.88288919114 +4614,19837.4,20648.09879882441 +4615,19722.9,20640.420800493917 +4616,19765.1,20633.156727460773 +4617,19597.6,20624.562895697614 +4618,19609.9,20616.14245672917 +4619,19765.1,20609.079863727267 +4620,19862.0,20602.88003083327 +4621,19778.0,20596.034553398968 +4622,19907.5,20590.32057370271 +4623,19876.9,20584.400071016382 +4624,19893.0,20578.662311090935 +4625,19867.4,20572.75971929765 +4626,19723.1,20565.70860129518 +4627,19383.6,20555.898571408914 +4628,19449.5,20546.716840525853 +4629,19338.2,20536.687655127298 +4630,19368.9,20526.99647126732 +4631,19322.9,20517.00396943108 +4632,19417.2,20507.87696553539 +4633,19417.2,20498.825704410618 +4634,19497.9,20490.519267029616 +4635,19462.7,20481.989646556343 +4636,19471.0,20473.599690983265 +4637,19508.9,20465.59388441909 +4638,19537.4,20457.891030606483 +4639,19699.5,20451.597329107673 +4640,19842.4,20446.541749613007 +4641,19797.6,20441.156340902526 +4642,19778.7,20435.658777907487 +4643,19833.0,20430.657460248505 +4644,19153.4,20420.05781327549 +4645,19282.4,20410.616669596853 +4646,19564.5,20403.594954496468 +4647,19445.1,20395.640639521393 +4648,19620.6,20389.208766994245 +4649,19875.3,20384.943963948648 +4650,19837.1,20380.397541011316 +4651,19644.7,20374.29216722699 +4652,19661.3,20368.375219781123 +4653,19911.4,20364.58289430576 +4654,19833.7,20360.1772271331 +4655,20220.1,20359.014760517886 +4656,20295.1,20358.48434756753 +4657,20267.9,20357.73261024332 +4658,20250.0,20356.838563685287 +4659,20215.3,20355.663969795783 +4660,20076.3,20353.345596602456 +4661,20106.9,20351.300404929407 +4662,20018.4,20348.537745967336 +4663,19981.3,20345.490129818234 +4664,19759.0,20340.622991811444 +4665,19810.4,20336.222801008033 +4666,19772.3,20331.542943738255 +4667,19728.0,20326.5342886035 +4668,19773.0,20321.940643054924 +4669,19755.0,20317.23574145281 +4670,19884.9,20313.64789297602 +4671,20379.3,20314.192723739703 +4672,20213.3,20313.355439725266 +4673,20723.2,20316.756639395597 +4674,20685.0,20319.812600894387 +4675,20602.9,20322.16187391601 +4676,20672.5,20325.06924425696 +4677,20471.0,20326.280287873084 +4678,20495.3,20327.682941085754 +4679,20578.6,20329.765240329853 +4680,20388.6,20330.253495596826 +4681,20620.1,20332.658860778596 +4682,20480.9,20333.8890777016 +4683,20601.4,20336.10908535553 +4684,20535.9,20337.767101244695 +4685,20654.0,20340.391440653453 +4686,20591.9,20342.478648614837 +4687,20823.4,20346.46969717405 +4688,20856.3,20350.700654043972 +4689,20888.0,20355.15956977805 +4690,20859.1,20359.341648037152 +4691,20764.2,20362.701468385392 +4692,20975.7,20367.788593129084 +4693,20851.6,20371.80362555125 +4694,20880.1,20376.021852725095 +4695,20785.1,20379.41669212157 +4696,20745.1,20382.451408369525 +4697,20778.3,20385.736458922474 +4698,21027.4,20391.061467562122 +4699,21156.7,20397.415314304344 +4700,20923.2,20401.77867269186 +4701,20956.5,20406.382169184042 +4702,20879.5,20410.308458236457 +4703,20823.1,20413.734114184703 +4704,20678.7,20415.933001203917 +4705,20709.4,20418.36841198231 +4706,20695.8,20420.670748812332 +4707,20727.3,20423.215389901026 +4708,20736.4,20425.81443230849 +4709,20589.7,20427.1744785134 +4710,20541.3,20428.121578276776 +4711,20541.0,20429.05832866452 +4712,20600.0,20430.476931746143 +4713,20607.2,20431.94351322543 +4714,20622.5,20433.524894858412 +4715,20604.6,20434.944605274526 +4716,20686.1,20437.028882409177 +4717,20827.8,20440.271796248104 +4718,20805.7,20443.304395449366 +4719,20935.0,20447.38485689792 +4720,21454.3,20455.74099916433 +4721,21240.0,20462.24937261525 +4722,21285.5,20469.08132802923 +4723,21184.3,20475.016752692893 +4724,21201.2,20481.04316968299 +4725,21188.4,20486.913350847448 +4726,21284.6,20493.53315706448 +4727,21183.5,20499.25902298096 +4728,21285.0,20505.77969498942 +4729,21150.8,20511.132560591166 +4730,21154.2,20516.46921983937 +4731,21314.1,20523.08856241332 +4732,21252.8,20529.1442589908 +4733,21348.7,20535.945551447312 +4734,21566.3,20544.496210771402 +4735,21460.0,20552.09375259073 +4736,21448.1,20559.529489083754 +4737,21360.1,20566.17322776356 +4738,21377.8,20572.90871964934 +4739,21450.7,20580.193294590008 +4740,21375.4,20586.792520361047 +4741,21109.0,20591.126192391246 +4742,21194.0,20596.129294529077 +4743,21158.0,20600.792121960374 +4744,21142.6,20605.288452898465 +4745,20912.5,20607.837926318396 +4746,20965.9,20610.809395809527 +4747,21020.9,20614.21263733808 +4748,20928.2,20616.818341592538 +4749,21012.1,20620.098687305464 +4750,20995.8,20623.216540522848 +4751,20794.4,20624.637150145067 +4752,20815.4,20626.220244334734 +4753,20969.6,20629.06986886308 +4754,21323.2,20634.83028488911 +4755,21274.0,20640.134597877583 +4756,21286.2,20645.49613648441 +4757,21851.9,20655.507786804043 +4758,22189.3,20668.236352888656 +4759,22243.4,20681.30825037506 +4760,22283.1,20694.60112796531 +4761,22232.0,20707.35962482867 +4762,22199.8,20719.745022132996 +4763,22130.0,20731.44838294517 +4764,22280.8,20744.30607271326 +4765,22130.8,20755.81224638369 +4766,22205.4,20767.842020272623 +4767,22674.2,20783.662418444637 +4768,22194.7,20795.37227389323 +4769,21875.2,20804.333499836026 +4770,21831.4,20812.856873281373 +4771,21614.8,20819.512002963686 +4772,21488.8,20825.0662602005 +4773,21655.9,20831.961146007965 +4774,21782.9,20839.852754754786 +4775,22423.9,20852.99837504728 +4776,22337.9,20865.321210109127 +4777,22053.3,20875.17995525345 +4778,22094.6,20885.299623674582 +4779,21897.9,20893.702946299687 +4780,21887.1,20901.946905251556 +4781,21990.0,20910.97639151503 +4782,21753.7,20917.969948431917 +4783,21761.0,20924.96604844493 +4784,21918.2,20933.208653852027 +4785,21896.8,20941.205262533753 +4786,21997.1,20949.96787446293 +4787,21967.6,20958.412954342904 +4788,22167.9,20968.450191236327 +4789,22313.6,20979.613260188722 +4790,22617.2,20993.203191639437 +4791,23116.2,21010.821422414214 +4792,23227.1,21029.213775755175 +4793,23450.0,21049.303287989573 +4794,23399.9,21068.810314645263 +4795,23391.5,21088.08574771875 +4796,23295.6,21106.40536806963 +4797,23638.1,21127.41528202756 +4798,23484.9,21146.979470558454 +4799,23385.9,21165.559723914816 +4800,23033.6,21181.062132844985 +4801,23251.3,21198.242530082785 +4802,23367.5,21216.244666762595 +4803,23433.8,21234.647615586142 +4804,23384.0,21252.48456483439 +4805,23491.0,21271.061456412528 +4806,23775.9,21291.848498268027 +4807,23376.7,21309.15017048157 +4808,23359.5,21326.165521763884 +4809,23517.4,21344.35004025547 +4810,23701.1,21363.90813120771 +4811,23720.0,21383.460760824248 +4812,23796.1,21403.482663224047 +4813,23770.4,21423.12513074916 +4814,24173.9,21445.95313796286 +4815,24153.4,21468.421576651966 +4816,23680.1,21486.775754439088 +4817,23511.0,21503.574295896025 +4818,23753.6,21522.246708378218 +4819,23650.6,21539.90939129624 +4820,23253.7,21554.131719999175 +4821,23601.8,21571.12481775852 +4822,23301.4,21585.4839479016 +4823,23221.5,21599.060844599513 +4824,23245.1,21612.72092057794 +4825,23228.8,21626.13236522045 +4826,22689.0,21634.952843517374 +4827,22774.4,21644.40883651723 +4828,22875.4,21654.62453081999 +4829,22935.3,21665.252543012353 +4830,22786.8,21674.559990788184 +4831,22926.7,21684.951194184134 +4832,22871.9,21694.80139174277 +4833,23002.3,21705.652002599676 +4834,22865.3,21715.275637432875 +4835,22605.4,21722.662561603556 +4836,22793.0,21731.545029971992 +4837,22701.4,21739.593618934883 +4838,22552.5,21746.339729981068 +4839,22633.7,21753.70371562438 +4840,23026.8,21764.268830017536 +4841,22853.8,21773.310582465525 +4842,22923.5,21782.855722860004 +4843,23193.0,21794.558164993945 +4844,23113.8,21805.506230014744 +4845,23195.7,21817.043107773956 +4846,23011.2,21826.953123477077 +4847,23144.5,21837.887122452372 +4848,23261.3,21849.69967745277 +4849,23100.1,21860.076443614988 +4850,22977.8,21869.35215777586 +4851,22903.5,21877.934297545355 +4852,22963.4,21886.94231167361 +4853,23136.8,21897.314574647276 +4854,23156.5,21907.764246226965 +4855,23109.2,21917.734667420104 +4856,23294.2,21929.157616238193 +4857,23440.1,21941.696557182273 +4858,23427.1,21954.023556707732 +4859,23600.8,21967.689751257876 +4860,23577.8,21981.051662035818 +4861,23447.9,21993.224677288632 +4862,23655.8,22007.0219828713 +4863,23286.7,22017.641717453283 +4864,23100.8,22026.63058286861 +4865,23009.4,22034.786345666384 +4866,23017.3,22042.93998595131 +4867,22578.9,22047.387786897772 +4868,22592.7,22051.913199454637 +4869,22727.1,22057.51640941767 +4870,22715.3,22062.975194401755 +4871,22675.1,22068.055068307134 +4872,22756.9,22073.771623756867 +4873,22775.6,22079.595925634407 +4874,22821.3,22085.75114616856 +4875,22796.4,22091.648647030233 +4876,22885.6,22098.2374549387 +4877,22893.8,22104.83963373589 +4878,22816.0,22110.741379514016 +4879,22764.2,22116.164272630085 +4880,22601.5,22120.1919550149 +4881,22655.8,22124.63683505627 +4882,22513.5,22127.863915263275 +4883,22226.3,22128.680812232044 +4884,22275.0,22129.895079350452 +4885,22329.4,22131.55072184547 +4886,22142.1,22131.638267722272 +4887,22209.9,22132.287742678935 +4888,22181.7,22132.697802905666 +4889,22027.0,22131.82064271558 +4890,22069.2,22131.30096933205 +4891,22268.8,22132.442040125978 +4892,22313.5,22133.94459580958 +4893,22235.5,22134.787379246845 +4894,22455.3,22137.44723502073 +4895,22440.2,22139.959706099395 +4896,22540.7,22143.28535169193 +4897,22418.9,22145.572610184114 +4898,22460.6,22148.186945369307 +4899,22326.5,22149.666721756286 +4900,22385.3,22151.622184646276 +4901,22741.8,22156.51992585253 +4902,22669.2,22160.774532276995 +4903,22692.4,22165.186361884655 +4904,22670.6,22169.380665935405 +4905,22610.7,22173.043067047976 +4906,22507.8,22175.821132881603 +4907,22648.2,22179.7412894552 +4908,22485.1,22182.275386638143 +4909,22558.8,22185.40007222621 +4910,22760.0,22190.168536357112 +4911,22837.0,22195.536432320954 +4912,22700.7,22199.728661098376 +4913,22760.9,22204.385684657726 +4914,22703.0,22208.523562793347 +4915,22695.1,22212.561541525352 +4916,22713.2,22216.71621752929 +4917,22800.1,22221.55757671992 +4918,22647.8,22225.094858240915 +4919,22570.6,22227.962120828128 +4920,22314.8,22228.68276712831 +4921,22199.4,22228.439756612723 +4922,21945.7,22226.093368591042 +4923,21858.0,22223.038651839248 +4924,21880.8,22220.1984970522 +4925,21900.1,22217.54207798953 +4926,21896.8,22214.880318006213 +4927,21951.3,22212.69292947504 +4928,22038.3,22211.24568524703 +4929,21991.4,22209.421239726307 +4930,22038.7,22208.004465952647 +4931,21931.5,22205.70982308167 +4932,21962.0,22203.687334923317 +4933,21890.3,22201.086610152168 +4934,21875.8,22198.387136209 +4935,21884.3,22195.7806039583 +4936,21940.0,22193.65794334454 +4937,21852.0,22190.822607715127 +4938,21814.8,22187.702088149028 +4939,21879.5,22185.144394471445 +4940,22160.7,22184.941536426042 +4941,22020.3,22183.575216621677 +4942,21647.4,22179.125629761747 +4943,21300.0,22171.829981381983 +4944,20985.7,22161.986579046865 +4945,21064.0,22152.87465722905 +4946,21058.0,22143.788560488563 +4947,21130.2,22135.37703716501 +4948,21116.1,22126.91830656613 +4949,21137.2,22118.704876636122 +4950,21070.4,22110.005251103874 +4951,21053.7,22101.239232422515 +4952,21057.1,22092.574176551792 +4953,21108.9,22084.41090537709 +4954,21063.5,22075.938615705913 +4955,21078.9,22067.664436322462 +4956,21022.9,22058.99419203763 +4957,20942.1,22049.72536056844 +4958,20941.8,22040.530959235923 +4959,20737.6,22029.71825418002 +4960,20925.4,22020.553787340355 +4961,20910.8,22011.344212341683 +4962,20896.5,22002.092393152125 +4963,20907.9,21993.011958354182 +4964,20974.9,21984.562896459127 +4965,20935.5,21975.85698030594 +4966,21102.2,21968.606714909212 +4967,21241.0,21962.568484909967 +4968,21194.4,21956.193642711547 +4969,21153.0,21949.528135303153 +4970,21173.3,21943.086408039228 +4971,21065.1,21935.80021378164 +4972,21100.7,21928.86992155109 +4973,21234.2,21923.10502593656 +4974,21246.3,21917.48838671717 +4975,21335.0,21912.654458196695 +4976,21344.1,21907.9361639378 +4977,21284.6,21902.76324971425 +4978,21277.9,21897.577662579693 +4979,21193.2,21891.73220479895 +4980,21360.3,21887.321979032986 +4981,21449.5,21883.688601613627 +4982,21446.3,21880.058820687373 +4983,21516.4,21877.040905162998 +4984,21518.7,21874.067121717664 +4985,21612.6,21871.897270085152 +4986,22544.9,21877.48235498071 +4987,22763.8,21884.837688134397 +4988,22769.4,21892.178454207973 +4989,22671.5,21898.64585292824 +4990,22864.0,21906.657090663273 +4991,22941.1,21915.2416791225 +4992,22778.9,21922.408968092437 +4993,22763.9,21929.39229615806 +4994,23276.7,21940.573272953432 +4995,23126.7,21950.41664828162 +4996,23062.3,21959.643896013724 +4997,23230.6,21970.191249573774 +4998,23115.5,21979.695886506775 +4999,22916.9,21987.473514004647 +5000,22962.4,21995.564190236975 +5001,22974.4,22003.687308990196 +5002,22974.4,22011.743015969532 +5003,23106.0,22020.82398679136 +5004,23029.2,22029.192252461144 +5005,22841.8,22035.935885220806 +5006,23121.7,22044.94637579989 +5007,23739.4,22059.00823160238 +5008,23762.5,22073.145092750907 +5009,23990.6,22089.057581607747 +5010,23799.4,22103.25129462345 +5011,23790.4,22117.252528692967 +5012,24022.0,22133.059561649872 +5013,23923.5,22147.917988524146 +5014,23955.5,22162.91866911731 +5015,23836.2,22176.804821240818 +5016,23876.7,22190.911835172432 +5017,23845.0,22204.638707909588 +5018,23776.4,22217.68237008461 +5019,23905.8,22231.691645021667 +5020,24004.9,22246.407067054683 +5021,23979.6,22260.790410896556 +5022,23936.3,22274.69505478953 +5023,23938.0,22288.498415330694 +5024,23919.4,22302.032868315502 +5025,24061.8,22316.636744926993 +5026,24002.0,22330.62316198154 +5027,23717.2,22342.130023707836 +5028,23513.9,22351.85425587624 +5029,23694.0,22362.99239483162 +5030,24024.7,22376.78249943883 +5031,24003.0,22390.278080356347 +5032,23840.6,22402.313946909406 +5033,23773.3,22413.691424528417 +5034,23923.3,22426.219296524032 +5035,23889.9,22438.36602435371 +5036,23931.5,22450.757177678577 +5037,23817.1,22462.09612226216 +5038,23938.0,22474.344287222637 +5039,23771.1,22485.10574541996 +5040,23914.5,22496.96793840403 +5041,23909.1,22508.686876674536 +5042,23832.1,22519.669558195907 +5043,23800.7,22530.300516219177 +5044,23734.9,22540.29719243313 +5045,23791.8,22550.68310784862 +5046,23811.3,22561.14465882083 +5047,23869.8,22572.004869121072 +5048,23885.3,22582.90358390015 +5049,23904.1,22593.867869510937 +5050,23804.3,22603.91294943201 +5051,23956.3,22615.13607848237 +5052,24372.8,22629.72250106758 +5053,24490.6,22645.165467863702 +5054,24344.0,22659.263679748652 +5055,24522.8,22674.728711451986 +5056,24572.6,22690.478680651555 +5057,24489.3,22705.406658405485 +5058,24505.5,22720.34519236063 +5059,23915.0,22730.25934014187 +5060,23951.8,22740.396607028662 +5061,23851.8,22749.619871700623 +5062,23690.6,22757.42883542095 +5063,23629.9,22764.669260023267 +5064,23734.0,22772.713498529298 +5065,23716.5,22780.54575165354 +5066,23784.0,22788.873172801643 +5067,23732.5,22796.704100828185 +5068,23791.9,22804.962987958243 +5069,23699.9,22812.389851128715 +5070,23764.6,22820.292010040514 +5071,23704.9,22827.63315518541 +5072,23588.5,22833.94740286022 +5073,23743.2,22841.493067566775 +5074,23816.2,22849.58192177784 +5075,23788.4,22857.372943173876 +5076,23801.0,22865.203873106042 +5077,23700.0,22872.13164179396 +5078,23711.1,22879.09403480812 +5079,23764.2,22886.439312527553 +5080,23758.9,22893.679650182927 +5081,23728.3,22900.605960139914 +5082,23742.2,22907.59014304332 +5083,23832.8,22915.268233142546 +5084,23791.0,22922.53571668493 +5085,23582.0,22928.00844932655 +5086,23369.1,22931.66896012052 +5087,23290.1,22934.643491571802 +5088,23365.9,22938.22238375793 +5089,23407.8,22942.119293436288 +5090,23412.0,22946.018718387026 +5091,23366.3,22949.506529852693 +5092,23380.5,22953.083239148524 +5093,23318.4,22956.114913512436 +5094,23216.8,22958.278275226025 +5095,23313.4,22961.225343481412 +5096,23280.5,22963.874925693184 +5097,23301.8,22966.67928315631 +5098,23026.5,22967.17572064049 +5099,23216.2,22969.24231217045 +5100,23129.1,22970.56893198646 +5101,23233.1,22972.747613048814 +5102,23426.5,22976.51319302351 +5103,23315.1,22979.323042044063 +5104,23193.1,22981.097124682703 +5105,22954.5,22980.876401656293 +5106,22944.5,22980.574522804374 +5107,22944.9,22980.27846867322 +5108,23113.8,22981.38653117386 +5109,23011.5,22981.63643547947 +5110,23152.9,22983.057709873832 +5111,23257.6,22985.336069127992 +5112,23179.5,22986.947388056393 +5113,22987.6,22986.952803923148 +5114,22908.0,22986.297593932086 +5115,22845.3,22985.127489418126 +5116,22852.1,22984.023526850342 +5117,22844.1,22982.86233575615 +5118,22866.6,22981.897503094275 +5119,22875.5,22981.014536263618 +5120,22800.0,22979.51234094193 +5121,22703.7,22977.223441846978 +5122,22844.4,22976.12117262003 +5123,22872.0,22975.257096498703 +5124,22726.5,22973.1927222539 +5125,22789.9,22971.67162082441 +5126,23014.5,22972.027043058235 +5127,22962.9,22971.951299962315 +5128,23367.2,22975.2313721618 +5129,23175.5,22976.893352475814 +5130,22973.0,22976.86104249676 +5131,22935.6,22976.51862720633 +5132,23016.9,22976.853742333245 +5133,23079.0,22977.701429118864 +5134,23091.6,22978.646645474724 +5135,22975.1,22978.617212732195 +5136,22783.4,22976.997152875498 +5137,22789.4,22975.44033003006 +5138,22763.8,22973.683978743502 +5139,22852.9,22972.681622073433 +5140,22818.4,22971.401276662036 +5141,22904.9,22970.849398847415 +5142,23028.6,22971.328656948266 +5143,22979.5,22971.3964689238 +5144,23284.5,22973.994838476297 +5145,23374.0,22977.31438338521 +5146,23397.7,22980.803060701517 +5147,23390.9,22984.20635480358 +5148,23255.3,22986.456094597743 +5149,23330.1,22989.307911240085 +5150,23291.9,22991.81904890614 +5151,23346.0,22994.75830991107 +5152,23426.2,22998.338738874467 +5153,23437.8,23001.985720294597 +5154,23477.0,23005.927747512073 +5155,23449.1,23009.605525541017 +5156,23308.5,23012.085977611216 +5157,23262.2,23014.161612651787 +5158,22920.1,23013.38101835592 +5159,22801.5,23011.622669655873 +5160,22990.0,23011.44322841392 +5161,23129.0,23012.418803281853 +5162,23124.6,23013.349767569973 +5163,23083.7,23013.93358692624 +5164,23074.1,23014.432893258803 +5165,23111.1,23015.235109912257 +5166,22928.3,23014.513656717965 +5167,22935.0,23013.853792346865 +5168,22791.2,23012.006043032787 +5169,22858.1,23010.7288144599 +5170,22852.2,23009.413222638657 +5171,22872.3,23008.27535357112 +5172,22884.1,23007.24485271161 +5173,23015.5,23007.313360157987 +5174,22911.0,23006.514079160825 +5175,22914.3,23005.7488170931 +5176,22578.9,23002.2065032583 +5177,22624.8,22999.074499081886 +5178,22530.6,22995.186743902785 +5179,22439.1,22990.571916152556 +5180,22498.3,22986.48667203511 +5181,22482.9,22982.307529528593 +5182,22607.1,22979.19377409682 +5183,22608.9,22976.120796718424 +5184,22619.7,22973.162947783 +5185,22875.8,22972.35495651509 +5186,23034.2,22972.868193390485 +5187,23158.5,22974.408706308404 +5188,23142.4,22975.80282492825 +5189,23184.0,22977.53060231474 +5190,23271.3,22979.968522627478 +5191,23176.9,22981.602808746753 +5192,23140.9,22982.924777138895 +5193,23121.9,22984.07809849044 +5194,23186.9,22985.761267797578 +5195,23393.0,22989.140842338675 +5196,23011.5,22989.326395514287 +5197,23043.2,22989.773479368938 +5198,23164.0,22991.21934261069 +5199,22979.1,22991.11876715334 +5200,23024.8,22991.39827945912 +5201,22819.1,22989.968418218796 +5202,22799.2,22988.385277818637 +5203,22922.7,22987.840171778647 +5204,22969.7,22987.68963093401 +5205,23096.8,22988.595111175222 +5206,23230.9,22990.605940128124 +5207,23298.1,22993.15775805237 +5208,23253.1,22995.314955080983 +5209,23215.1,22997.13889736247 +5210,23254.8,22999.2771637744 +5211,23161.8,23000.62590100449 +5212,23200.7,23002.286266971256 +5213,23188.6,23003.832439029586 +5214,23178.9,23005.28528185922 +5215,23196.5,23006.872125993166 +5216,23142.1,23007.99434901397 +5217,23162.3,23009.27489383543 +5218,23173.4,23010.636927911484 +5219,23148.1,23011.777700293962 +5220,23163.6,23013.03763639111 +5221,23181.5,23014.435664304874 +5222,23213.8,23016.09014011977 +5223,23191.2,23017.543333977697 +5224,23146.5,23018.613513778713 +5225,23121.1,23019.46402403781 +5226,23173.8,23020.744820518827 +5227,23175.1,23022.025776365143 +5228,23166.0,23023.220583200287 +5229,23171.4,23024.45028790402 +5230,23165.9,23025.624144435937 +5231,22943.3,23024.940956515307 +5232,22875.2,23023.69829297576 +5233,22899.8,23022.670091374304 +5234,22984.9,23022.35664663261 +5235,22979.7,23022.00264956512 +5236,22939.1,23021.31466077205 +5237,22956.0,23020.7726303922 +5238,22993.3,23020.544641758243 +5239,23000.0,23020.374146805894 +5240,22912.8,23019.48141529713 +5241,22966.0,23019.03758612454 +5242,22985.1,23018.75594640567 +5243,23026.1,23018.816892908526 +5244,23103.1,23019.516337780657 +5245,23142.2,23020.534459458828 +5246,23108.6,23021.265293820168 +5247,23012.1,23021.18923329054 +5248,23080.0,23021.677289445808 +5249,23235.1,23023.448432271984 +5250,23207.5,23024.97583117429 +5251,23154.9,23026.054040044215 +5252,23254.0,23027.94570776169 +5253,23301.0,23030.21171848566 +5254,23235.0,23031.91120629906 +5255,23167.8,23033.038914130604 +5256,23251.7,23034.853528951095 +5257,23211.9,23036.322794271004 +5258,23290.1,23038.428829173317 +5259,23294.5,23040.553901130388 +5260,23330.0,23042.95594344466 +5261,23416.5,23046.05589412147 +5262,23715.9,23051.614766369425 +5263,23796.8,23057.798876192086 +5264,24120.4,23066.617142779705 +5265,24089.4,23075.104967320953 +5266,24057.6,23083.258453069328 +5267,24160.0,23092.194067566677 +5268,24117.4,23100.70200061592 +5269,24148.1,23109.39410019587 +5270,24139.3,23117.941037123703 +5271,23986.9,23125.15231482392 +5272,23934.8,23131.871382750694 +5273,23957.7,23138.724732271436 +5274,23908.0,23145.108759389514 +5275,23918.7,23151.528603709932 +5276,24050.1,23158.985627745533 +5277,23840.0,23164.637199299515 +5278,23804.7,23169.94892378666 +5279,23804.4,23175.214077946104 +5280,23782.9,23180.25711464365 +5281,23725.2,23184.77946223997 +5282,23793.6,23189.83191483549 +5283,23819.0,23195.053226745567 +5284,23869.8,23200.652785029837 +5285,23862.2,23206.142803411334 +5286,23842.3,23211.422116246093 +5287,23866.2,23216.855957605047 +5288,23770.6,23221.451343848992 +5289,23465.1,23223.473324397964 +5290,23340.1,23224.44118062703 +5291,23257.8,23224.718017302326 +5292,23239.6,23224.84151923343 +5293,23063.5,23223.502585463855 +5294,23083.0,23222.336588903992 +5295,23061.7,23221.00350517865 +5296,23061.8,23219.682314264304 +5297,23044.0,23218.22436974759 +5298,23045.1,23216.7876529862 +5299,23048.3,23215.389415202084 +5300,23122.3,23214.61688893485 +5301,23177.5,23214.308864960287 +5302,23159.6,23213.85484948344 +5303,23140.2,23213.243605919262 +5304,22761.4,23209.493866451056 +5305,22870.8,23206.683128970137 +5306,22899.7,23204.13555113636 +5307,22850.2,23201.198326645605 +5308,22935.0,23198.98921190166 +5309,22893.9,23196.457351221976 +5310,22923.7,23194.193804738807 +5311,22951.2,23192.17725864139 +5312,23088.0,23191.312717075903 +5313,23071.5,23190.318420668635 +5314,23054.4,23189.19046697014 +5315,23108.3,23188.519176787817 +5316,23968.7,23194.993706441033 +5317,24012.2,23201.775501408327 +5318,23997.2,23208.37653459166 +5319,23983.6,23214.809924346086 +5320,23949.6,23220.907767297573 +5321,23904.3,23226.579072133278 +5322,23587.0,23229.570117177816 +5323,23626.3,23232.862481350618 +5324,23883.2,23238.259473206628 +5325,23948.7,23244.155245213213 +5326,23833.8,23249.048562680324 +5327,23940.9,23254.790068384224 +5328,24041.5,23261.318781509668 +5329,24428.3,23271.003272949423 +5330,24257.0,23279.185818402126 +5331,24313.7,23287.7709983324 +5332,24511.2,23297.923936105577 +5333,24546.8,23308.288052818396 +5334,24533.9,23318.459106321978 +5335,24429.5,23327.67936270105 +5336,24516.0,23337.54094475332 +5337,24427.2,23346.583758489807 +5338,24471.8,23355.921652610225 +5339,24547.3,23365.808609849973 +5340,24851.6,23378.138828855368 +5341,24712.3,23389.210705794325 +5342,24550.7,23398.849621098936 +5343,24336.6,23406.631781919692 +5344,24328.5,23414.28214057596 +5345,24336.0,23421.93125144255 +5346,24141.4,23427.901946451326 +5347,24213.7,23434.42309212393 +5348,24190.7,23440.699249035762 +5349,24170.0,23446.75153742551 +5350,23879.2,23450.340321347292 +5351,23917.2,23454.214675526982 +5352,23950.3,23458.331566186513 +5353,23893.2,23461.940432857165 +5354,23995.5,23466.368313082417 +5355,23997.7,23470.777704675096 +5356,23920.4,23474.509010030488 +5357,23936.4,23478.342130279198 +5358,23942.9,23482.197382310078 +5359,23949.2,23486.072922705844 +5360,23905.2,23489.551155712437 +5361,23967.4,23493.51670628744 +5362,23740.0,23495.562210799577 +5363,23663.2,23496.953395772198 +5364,23768.1,23499.203575060397 +5365,23815.7,23501.830101408446 +5366,23800.1,23504.30537027643 +5367,23966.7,23508.142670108162 +5368,23977.2,23512.03526205747 +5369,23992.6,23516.023351169028 +5370,24111.9,23520.96838559916 +5371,24162.0,23526.288150034023 +5372,24211.1,23531.97123592586 +5373,24100.0,23536.68516757793 +5374,24223.4,23542.38404585529 +5375,24387.5,23549.39745626313 +5376,24468.4,23557.024033389578 +5377,24545.9,23565.230472946514 +5378,24676.0,23574.448477320402 +5379,24786.4,23584.5061663053 +5380,24721.8,23593.9442894065 +5381,24702.1,23603.140602357485 +5382,24642.2,23611.763501922982 +5383,24566.1,23619.683306886276 +5384,24398.6,23626.14734583328 +5385,24570.1,23633.980977818068 +5386,24486.9,23641.059143977258 +5387,24426.0,23647.573175977446 +5388,24433.6,23654.09622016021 +5389,24449.9,23660.700400905767 +5390,24457.3,23667.31118596049 +5391,24501.8,23674.23640433426 +5392,24474.4,23680.87676612402 +5393,24561.9,23688.18816225577 +5394,24443.7,23694.45797003788 +5395,24389.2,23700.223464062467 +5396,24475.3,23706.655634485185 +5397,24486.2,23713.12488233178 +5398,24373.4,23718.604343889194 +5399,24429.0,23724.499743524968 +5400,24500.0,23730.935430300695 +5401,24573.7,23737.929327144673 +5402,24585.0,23744.95895928455 +5403,24554.3,23751.675482444018 +5404,24555.7,23758.347885079336 +5405,24570.0,23765.083587277848 +5406,24662.1,23772.527706885503 +5407,24867.1,23781.61129438023 +5408,24646.7,23788.790453762966 +5409,24733.9,23796.63368651182 +5410,24608.9,23803.374485793884 +5411,24554.2,23809.605402924226 +5412,24520.1,23815.501623646847 +5413,24463.0,23820.875054156004 +5414,24501.9,23826.526713457613 +5415,24512.1,23832.216118325185 +5416,24303.3,23836.125528131615 +5417,24213.9,23839.260585989447 +5418,24272.9,23842.859253325634 +5419,24273.8,23846.43552508227 +5420,24299.8,23850.19788586997 +5421,24331.0,23854.187944908394 +5422,24246.8,23857.446136236955 +5423,24294.1,23861.069819753662 +5424,24387.5,23865.438534942426 +5425,24632.7,23871.80585000515 +5426,24908.1,23880.40580145739 +5427,24856.1,23888.502848748198 +5428,24839.7,23896.396601040746 +5429,24810.0,23903.978371986464 +5430,24119.4,23905.766103339276 +5431,24010.8,23906.637753933974 +5432,24029.0,23907.653208258173 +5433,24155.8,23909.71251773321 +5434,24253.8,23912.56801551136 +5435,24037.1,23913.601475963547 +5436,23937.6,23913.800633839368 +5437,24142.3,23915.696894139455 +5438,24100.0,23917.226380495144 +5439,24211.8,23919.670974847882 +5440,24241.2,23922.33926551305 +5441,24104.5,23923.85097285319 +5442,24087.9,23925.212375568102 +5443,23972.2,23925.60231436007 +5444,24052.1,23926.65208768488 +5445,24015.2,23927.38692513148 +5446,24000.7,23927.9953323918 +5447,24085.2,23929.299935442494 +5448,24113.6,23930.829396559155 +5449,24099.9,23932.23247210638 +5450,24035.8,23933.091953665666 +5451,24072.7,23934.25052666429 +5452,23946.6,23934.35301192019 +5453,23936.4,23934.369999373135 +5454,23986.0,23934.79846410863 +5455,24054.1,23935.788518348392 +5456,23978.1,23936.13965097621 +5457,24069.6,23937.247205739895 +5458,24002.2,23937.786233078154 +5459,24027.1,23938.52742616464 +5460,23923.4,23938.401887358294 +5461,23890.0,23938.000211944534 +5462,23710.1,23936.108923878608 +5463,23886.3,23935.695571813227 +5464,23797.1,23934.545401092786 +5465,23849.6,23933.84046000488 +5466,23823.3,23932.923111789074 +5467,23919.6,23932.812546546014 +5468,23964.6,23933.07634284024 +5469,23933.6,23933.08068854281 +5470,23836.3,23932.277529301795 +5471,23843.0,23931.536636942445 +5472,23994.8,23932.061644104746 +5473,23929.9,23932.04370514952 +5474,23975.9,23932.407657803884 +5475,23996.9,23932.942863963188 +5476,24051.7,23933.928400361834 +5477,24151.1,23935.73065430074 +5478,24399.0,23939.57521318621 +5479,23774.2,23938.202804778026 +5480,23795.3,23937.01688938568 +5481,23818.0,23936.02919735758 +5482,23786.5,23934.78829115544 +5483,23737.2,23933.14855429938 +5484,23663.3,23930.909147209757 +5485,23416.2,23926.637702004697 +5486,23344.4,23921.80585385528 +5487,23385.7,23917.356842619967 +5488,23408.4,23913.133134382457 +5489,23461.1,23909.381822063933 +5490,23440.5,23905.490686611123 +5491,23268.5,23900.204456846717 +5492,23390.9,23895.97786384384 +5493,23322.0,23891.214562069203 +5494,23338.2,23886.6252296039 +5495,23333.1,23882.03165923374 +5496,23443.2,23878.389902725576 +5497,23460.1,23874.91861722578 +5498,23451.7,23871.40642953096 +5499,23423.8,23867.69185335228 +5500,23369.0,23863.5533317477 +5501,23382.0,23859.557038538176 +5502,23350.1,23855.329179297194 +5503,23402.5,23851.5712607968 +5504,23451.4,23848.250337470687 +5505,23519.9,23845.52543840454 +5506,23469.3,23842.403235596204 +5507,23521.3,23839.73847845433 +5508,23555.8,23837.382142533548 +5509,23440.4,23834.087684919163 +5510,23414.3,23830.60396969162 +5511,23439.4,23827.357463719076 +5512,23326.9,23823.204289746303 +5513,23263.9,23818.562760370816 +5514,23307.2,23814.319086010895 +5515,23340.0,23810.3828280357 +5516,23405.0,23807.01865518893 +5517,23355.1,23803.268292905206 +5518,23337.7,23799.404655619688 +5519,23185.1,23794.30669167264 +5520,22923.1,23787.076760621414 +5521,22751.2,23778.480272981404 +5522,22803.1,23770.385830881976 +5523,22759.0,23761.99258747217 +5524,22779.8,23753.84161164253 +5525,22802.5,23745.946660508565 +5526,21898.9,23730.618472454553 +5527,21892.8,23715.366866874017 +5528,21772.0,23699.239341007844 +5529,21736.5,23682.95104772147 +5530,21605.4,23665.709960188513 +5531,21453.7,23647.35303105832 +5532,21517.0,23629.673752792278 +5533,21390.6,23611.092227872843 +5534,21325.5,23592.12465751705 +5535,21431.1,23574.190842931846 +5536,21518.2,23557.128678260215 +5537,21450.2,23539.64379296345 +5538,21365.1,23521.597786382838 +5539,21319.4,23503.322286080907 +5540,21266.6,23484.76027540804 +5541,21070.9,23464.728239927477 +5542,20963.7,23443.97281884924 +5543,20824.9,23422.237774709414 +5544,20980.9,23401.977710189007 +5545,20985.3,23381.92229350694 +5546,21119.1,23363.14368526207 +5547,21128.5,23344.59892438853 +5548,21228.1,23327.034617962072 +5549,21116.7,23308.691592086867 +5550,21300.1,23292.02278219403 +5551,21295.1,23275.45080889782 +5552,21186.0,23258.11096816008 +5553,21195.3,23240.992204938833 +5554,21218.2,23224.205547636437 +5555,21220.1,23207.57396632825 +5556,21228.4,23191.14928610976 +5557,21308.3,23175.52398083084 +5558,21244.3,23159.4972258032 +5559,21291.7,23143.99683388782 +5560,21227.7,23128.0939555983 +5561,21242.6,23112.446702854744 +5562,21095.1,23095.705236441012 +5563,20981.7,23078.16162452034 +5564,20810.0,23059.33870647453 +5565,21106.5,23043.13257613034 +5566,21125.4,23027.217782967433 +5567,21127.8,23011.45497978928 +5568,21155.5,22996.052863774432 +5569,21249.5,22981.558649137303 +5570,21251.2,22967.19882632289 +5571,21198.3,22952.519168013154 +5572,21188.3,22937.87834504209 +5573,21219.2,22923.615454211864 +5574,21106.5,22908.53565791135 +5575,21241.7,22894.702996849846 +5576,21343.3,22881.828283183044 +5577,21423.1,22869.72265427696 +5578,21368.8,22857.26686461491 +5579,21514.3,22846.121911381593 +5580,21462.0,22834.63542249046 +5581,21385.7,22822.611062137843 +5582,21399.5,22810.801011829648 +5583,21407.7,22799.157020030234 +5584,21392.6,22787.48434766484 +5585,21449.7,22776.38240287094 +5586,21468.2,22765.526117369936 +5587,21498.8,22755.013867433256 +5588,21455.1,22744.2262004836 +5589,21527.6,22734.129717492036 +5590,21550.2,22724.304574608286 +5591,21505.6,22714.190843698674 +5592,21441.3,22703.6274342074 +5593,21435.0,22693.09940570775 +5594,21463.0,22682.89111188445 +5595,21406.5,22672.298654524413 +5596,21454.9,22662.19576112587 +5597,21415.3,22651.848078460924 +5598,21342.4,22640.981289428055 +5599,21089.1,22628.10260652824 +5600,21177.0,22616.060261245846 +5601,21167.6,22604.039844140072 +5602,21269.8,22592.967314313184 +5603,21238.6,22581.7277515388 +5604,21197.6,22570.241214181635 +5605,21295.9,22559.66576842079 +5606,21192.3,22548.31833465796 +5607,21388.0,22538.68913685997 +5608,21322.3,22528.594621201384 +5609,21221.6,22517.74819280967 +5610,21010.0,22505.235759674324 +5611,21047.1,22493.135047975782 +5612,21121.3,22481.75052475607 +5613,21094.2,22470.23558264191 +5614,21162.6,22459.38383506812 +5615,21387.0,22450.484384154694 +5616,21292.1,22440.871235738472 +5617,21296.6,22431.37520888587 +5618,21333.0,22422.260061924164 +5619,21228.5,22412.35333941857 +5620,21255.4,22402.752066892273 +5621,20955.1,22390.738356793583 +5622,21068.9,22379.768743874134 +5623,21297.3,22370.785600771447 +5624,21444.2,22363.0960937111 +5625,21430.2,22355.354217414744 +5626,21405.6,22347.47243967686 +5627,21444.4,22339.97806258411 +5628,21340.8,22331.686128454778 +5629,21506.4,22324.837280915737 +5630,21457.3,22317.637801406065 +5631,21460.6,22310.52545450643 +5632,21455.1,22303.426488078992 +5633,21401.1,22295.93830145593 +5634,21532.7,22289.60437364302 +5635,21512.5,22283.155374691625 +5636,21470.0,22276.40719730829 +5637,21479.4,22269.79302969577 +5638,21465.5,22263.118398743936 +5639,21518.2,22256.936503318677 +5640,21375.7,22249.623337316036 +5641,21251.6,22241.340985969015 +5642,21189.3,22232.61035538006 +5643,21253.2,22224.482468613423 +5644,21316.4,22216.946514517047 +5645,21407.3,22210.22745630529 +5646,21391.6,22203.433867456282 +5647,21305.6,22195.982963991915 +5648,21329.0,22188.788084622687 +5649,21302.5,22181.432996783496 +5650,21274.9,22173.909901374504 +5651,21426.1,22167.704010076788 +5652,21474.6,22161.952109578226 +5653,21426.2,22155.846282942723 +5654,21455.3,22150.032620843616 +5655,21605.6,22145.51450780757 +5656,21704.9,22141.857955875555 +5657,21624.9,22137.567848357918 +5658,21664.1,22133.638654595612 +5659,21695.8,22130.005138789842 +5660,21676.0,22126.23746128951 +5661,21584.5,22121.741714722797 +5662,21483.4,22116.44427310684 +5663,21354.9,22110.12440362048 +5664,21467.0,22104.787271640227 +5665,21543.2,22100.126796356904 +5666,21519.1,22095.304997217012 +5667,21460.8,22090.039395580356 +5668,21528.9,22085.38263711081 +5669,21550.0,22080.939627674208 +5670,21680.1,22077.613157735002 +5671,21732.2,22074.746658500688 +5672,21676.4,22071.440877102345 +5673,21697.5,22068.337633308965 +5674,21612.5,22064.554748385242 +5675,21713.1,22061.638111469183 +5676,21665.0,22058.346508884377 +5677,21697.8,22055.35442167372 +5678,21645.5,22051.953140166053 +5679,21557.0,22047.845645226917 +5680,21431.5,22042.73074360678 +5681,21558.3,22038.710571460666 +5682,21530.0,22034.488906967217 +5683,21594.3,22030.835886992387 +5684,21636.0,22027.559240627306 +5685,21574.7,22023.801072655297 +5686,21565.4,22019.996914376003 +5687,21548.3,22016.082417161262 +5688,21471.3,22011.561401251212 +5689,21549.5,22007.726866800997 +5690,21587.8,22004.241996537086 +5691,21542.0,22000.405963370806 +5692,21520.0,21996.419191890553 +5693,21395.2,21991.429821003494 +5694,21413.1,21986.630403401807 +5695,21438.4,21982.08077349806 +5696,21404.1,21977.28425255617 +5697,21427.0,21972.717578261098 +5698,21385.0,21967.840253960178 +5699,21179.7,21961.299670939763 +5700,21626.6,21958.522080309558 +5701,21747.6,21956.771689601595 +5702,21144.9,21950.03416520656 +5703,20770.1,21940.242180433062 +5704,20711.5,21930.045149890047 +5705,20638.6,21919.32776275403 +5706,20683.7,21909.07359044902 +5707,20635.5,21898.504515009612 +5708,20640.9,21888.06796301783 +5709,20629.7,21877.62507535793 +5710,20600.2,21867.024037388153 +5711,20228.7,21853.427987285348 +5712,20293.3,21840.480867058912 +5713,20264.5,21827.402187664233 +5714,20193.1,21813.839513907682 +5715,20189.9,21800.362837443718 +5716,20134.8,21786.54073920767 +5717,20117.4,21772.688948840805 +5718,20150.9,21759.230119389842 +5719,20255.0,21746.746881884534 +5720,20186.6,21733.799604856446 +5721,20140.1,21720.573881994565 +5722,20168.8,21707.696090442743 +5723,20198.6,21695.17247143492 +5724,20181.5,21682.6108741616 +5725,20160.9,21669.98256815196 +5726,20054.2,21656.573584183898 +5727,19966.1,21642.544757759137 +5728,19991.0,21628.838992134577 +5729,19975.8,21615.12082622475 +5730,19952.0,21601.318993641973 +5731,20007.1,21588.088960499714 +5732,19998.7,21574.899010620047 +5733,19862.0,21560.684081071337 +5734,20011.2,21547.825292016805 +5735,20023.9,21535.178609095507 +5736,19981.6,21522.285840555294 +5737,20010.6,21509.740729845293 +5738,19992.5,21497.14952046898 +5739,19978.7,21484.548279635215 +5740,19984.5,21472.09974619426 +5741,20047.0,21460.273192283938 +5742,20024.2,21448.355572430963 +5743,20045.0,21436.70946809544 +5744,20023.7,21424.983248443197 +5745,20006.6,21413.212433103417 +5746,19983.4,21401.346769758162 +5747,20021.9,21389.899078722825 +5748,19911.2,21377.627717073672 +5749,19927.2,21365.590972533642 +5750,19909.3,21353.505570271955 +5751,20007.5,21342.335399564305 +5752,19978.7,21331.018923219373 +5753,19973.5,21319.753206014233 +5754,19999.0,21308.79259849544 +5755,19962.5,21297.620045810832 +5756,19975.4,21286.64726534767 +5757,19954.1,21275.588781817813 +5758,19890.6,21264.09509898115 +5759,19547.5,21249.849496499977 +5760,19630.1,21236.407591964708 +5761,19650.8,21223.249022736785 +5762,19752.4,21211.042806780464 +5763,19854.6,21199.786020002204 +5764,19796.0,21188.136343487666 +5765,19745.3,21176.162597898558 +5766,19839.5,21165.069962231348 +5767,19885.3,21154.449464619473 +5768,19782.8,21143.0664815106 +5769,19831.9,21132.18543187151 +5770,19818.9,21121.28679758212 +5771,19812.0,21110.42134697978 +5772,19882.6,21100.231958208165 +5773,20066.8,21091.655759384863 +5774,20160.0,21083.92417631943 +5775,20249.1,21076.9961748562 +5776,20306.9,21070.605335230834 +5777,20266.9,21063.935581411493 +5778,20163.6,21056.463916835462 +5779,20090.5,21048.44761877044 +5780,20166.3,21041.12689164371 +5781,20190.4,21034.0669174392 +5782,20198.0,21027.128602771656 +5783,20275.5,21020.89102100592 +5784,20247.0,21014.46868888139 +5785,20151.1,21007.30380349648 +5786,20186.2,21000.48966404838 +5787,20261.3,20994.355309989885 +5788,20459.1,20989.913357209884 +5789,20404.1,20985.051835573286 +5790,20386.6,20980.08543029882 +5791,20423.3,20975.464804321236 +5792,20393.0,20970.631071505293 +5793,20413.3,20966.00591738492 +5794,20384.4,20961.179312261393 +5795,20395.8,20956.4873677613 +5796,20326.4,20951.258426950004 +5797,20264.2,20945.556697265773 +5798,19820.8,20936.22261679054 +5799,19743.8,20926.326993414685 +5800,19604.9,20915.360794299213 +5801,19674.1,20905.05987484445 +5802,19765.9,20895.60626592458 +5803,19937.1,20887.651857078734 +5804,19964.5,20879.99084581667 +5805,19874.7,20871.648183195783 +5806,19895.6,20863.548198272994 +5807,19803.1,20854.747798287328 +5808,19950.0,20847.239517803617 +5809,20228.8,20842.10723964757 +5810,20326.6,20837.829171268753 +5811,20392.6,20834.13432337441 +5812,20333.7,20829.98134143769 +5813,20344.2,20825.949961010825 +5814,20238.5,20821.074857599946 +5815,20247.5,20816.314900275465 +5816,20227.3,20811.42680981675 +5817,20169.0,20806.095466996692 +5818,20321.7,20802.075587602532 +5819,20308.0,20797.975375257283 +5820,20298.0,20793.826202018634 +5821,20295.5,20789.69071486495 +5822,20097.0,20783.94224420217 +5823,20130.5,20778.519486988876 +5824,20029.5,20772.30355763627 +5825,19972.3,20765.664523962943 +5826,19993.9,20759.25983911678 +5827,20217.7,20754.765566592992 +5828,20186.6,20750.050499650315 +5829,20272.2,20746.084935337865 +5830,20148.2,20741.123234629667 +5831,20041.5,20735.317232682533 +5832,20057.2,20729.6897037806 +5833,20116.7,20724.602652296944 +5834,20111.3,20719.513003730164 +5835,20016.9,20713.6821904212 +5836,20043.8,20708.123002118948 +5837,20030.7,20702.50123446651 +5838,19975.8,20696.470518827784 +5839,19885.7,20689.74213277942 +5840,19916.9,20683.328505121503 +5841,19925.7,20677.04113163502 +5842,20010.9,20671.512989463772 +5843,20060.9,20666.44566175038 +5844,19988.2,20660.817067047057 +5845,19964.0,20655.034352797702 +5846,19685.3,20646.98676480768 +5847,19775.8,20639.756999124627 +5848,19646.5,20631.514202451395 +5849,19715.5,20623.912424837694 +5850,19801.2,20617.084935834893 +5851,19857.4,20610.78049653336 +5852,20060.4,20606.213023533084 +5853,20073.6,20601.792998441524 +5854,20067.3,20597.357371898444 +5855,20122.5,20593.41664682045 +5856,20065.2,20589.033106182935 +5857,20042.4,20584.496731857766 +5858,20024.0,20579.84530669712 +5859,20145.1,20576.237461828267 +5860,20151.6,20572.713499489444 +5861,20114.9,20568.914217336005 +5862,20096.8,20564.996257026163 +5863,20100.5,20561.141516303956 +5864,20087.3,20557.209221562844 +5865,20123.1,20553.606655408796 +5866,20059.3,20549.50452548839 +5867,20082.4,20545.628139384757 +5868,20272.0,20543.357366443804 +5869,20257.4,20540.984276265848 +5870,20354.0,20539.43253953335 +5871,20344.8,20537.81733173639 +5872,20120.9,20534.35743686721 +5873,19791.3,20528.190985108977 +5874,19926.4,20523.19686904998 +5875,19899.3,20518.019301671975 +5876,19957.0,20513.363539832375 +5877,19962.0,20508.78790879642 +5878,19978.1,20504.383859760765 +5879,19941.0,20499.70847503246 +5880,19991.8,20495.493466940905 +5881,19945.6,20490.930035679987 +5882,19931.4,20486.286632894262 +5883,19890.3,20481.340685733318 +5884,19914.9,20476.639933154616 +5885,19935.5,20472.149145327607 +5886,19877.4,20467.213467773025 +5887,19808.2,20461.744476339227 +5888,19773.6,20456.033733796994 +5889,19787.7,20450.487395757184 +5890,19839.5,20445.416960937622 +5891,19790.0,20439.977816033577 +5892,19843.3,20435.026132912968 +5893,19850.4,20430.174463760162 +5894,19807.4,20425.006210948875 +5895,19764.3,20419.523171853867 +5896,19780.6,20414.22090486753 +5897,19808.1,20409.19085586448 +5898,19743.0,20403.662301044027 +5899,19702.9,20397.84684626358 +5900,19735.9,20392.353511439815 +5901,19739.7,20386.937299726622 +5902,19784.8,20381.940309687398 +5903,19820.7,20377.28271375638 +5904,19794.9,20372.449662189938 +5905,19770.1,20367.45090980662 +5906,19782.5,20362.596545409884 +5907,19766.0,20357.645536734286 +5908,19794.3,20352.970470039396 +5909,19758.8,20348.03959476936 +5910,19693.0,20342.60358153476 +5911,19618.5,20336.594423181778 +5912,19724.2,20331.51231178608 +5913,19764.7,20326.808475173748 +5914,19750.0,20322.021682848655 +5915,19843.0,20318.046399173563 +5916,19866.6,20314.299956026895 +5917,19702.4,20309.22194809306 +5918,19741.0,20304.506413254116 +5919,19781.6,20300.166940944953 +5920,19881.9,20296.69584599935 +5921,19829.7,20292.820361800186 +5922,19840.0,20289.06251647404 +5923,19872.7,20285.607225880896 +5924,19894.2,20282.359033135 +5925,19848.7,20278.7602029845 +5926,19825.4,20274.99787764853 +5927,19992.7,20272.655156672194 +5928,19906.4,20269.6156947911 +5929,19831.8,20265.982369523124 +5930,19842.1,20262.464673510483 +5931,19871.9,20259.223472900438 +5932,19878.0,20256.059792627406 +5933,19712.0,20251.544773601454 +5934,19772.0,20247.565148924266 +5935,19740.0,20243.352990012034 +5936,19767.6,20239.40483241857 +5937,19754.8,20235.38321555203 +5938,19686.3,20230.826508369024 +5939,19692.9,20226.362387967623 +5940,19715.9,20222.12618557785 +5941,19731.0,20218.05044959795 +5942,19834.6,20214.8682881905 +5943,19848.1,20211.824567956555 +5944,19839.4,20208.733907641563 +5945,19816.9,20205.48217396819 +5946,19731.2,20201.546222317003 +5947,19700.9,20197.391481882838 +5948,19729.1,20193.505245518667 +5949,19743.3,20189.76910240233 +5950,19793.7,20186.482221884467 +5951,19784.9,20183.14958933771 +5952,20093.6,20182.40643921873 +5953,19763.0,20178.92588785592 +5954,19805.9,20175.830237334296 +5955,19780.2,20172.546998850197 +5956,19754.1,20169.07440964812 +5957,19772.2,20165.780846082576 +5958,19848.2,20163.145320388943 +5959,19900.7,20160.967350925137 +5960,20008.3,20159.700401954808 +5961,19905.6,20157.591684926138 +5962,19904.9,20155.494658495216 +5963,19909.2,20153.450719420565 +5964,19841.1,20150.8585972677 +5965,19775.2,20147.74109853519 +5966,19828.3,20145.09013506187 +5967,19810.1,20142.310133941024 +5968,19737.7,20138.952373493383 +5969,19088.2,20130.232436783896 +5970,18947.4,20120.41639996411 +5971,18813.0,20109.566471333703 +5972,18972.9,20100.13355455915 +5973,18968.5,20090.742404728782 +5974,18895.1,20080.820061121074 +5975,18781.5,20070.037322024637 +5976,18821.5,20059.67601644767 +5977,18779.0,20049.047999713664 +5978,18603.7,20037.053410504424 +5979,18736.3,20026.258776392355 +5980,18714.9,20015.376130945115 +5981,18750.6,20004.88006346839 +5982,18785.6,19994.761556717614 +5983,18791.5,19984.775983632822 +5984,18795.2,19974.90398376865 +5985,18777.5,19964.96702124775 +5986,18738.3,19954.787211942792 +5987,18729.5,19944.61885333746 +5988,18771.3,19934.881767417646 +5989,18830.0,19925.712624119577 +5990,18892.3,19917.136585745142 +5991,18863.3,19908.391053913234 +5992,18903.5,19900.051709067484 +5993,18986.0,19892.46621770593 +5994,19063.6,19885.587659882643 +5995,18982.1,19878.089836979052 +5996,19374.8,19873.913157834 +5997,19350.5,19869.569480175625 +5998,19328.6,19865.08010689616 +5999,19287.0,19860.282761610717 +6000,19294.9,19855.59078848532 +6001,19172.8,19849.92447488793 +6002,19226.5,19844.750827793425 +6003,19209.1,19839.475717189332 +6004,19342.3,19835.349777627598 +6005,19295.9,19830.873015987538 +6006,19228.4,19825.87323992125 +6007,19164.0,19820.3805159385 +6008,19217.9,19815.380677631958 +6009,19177.6,19810.08789192547 +6010,19296.2,19805.82326211696 +6011,19303.1,19801.651284837983 +6012,19137.0,19796.135506540573 +6013,19215.1,19791.313635117003 +6014,19307.8,19787.301073829727 +6015,19240.4,19782.76247570666 +6016,19178.7,19777.749509103287 +6017,19191.1,19772.88104844683 +6018,19194.2,19768.078716094577 +6019,19341.7,19764.54030351288 +6020,19366.8,19761.239554106134 +6021,19322.1,19757.5952424538 +6022,19294.0,19753.74797903095 +6023,19309.3,19750.05961405974 +6024,19355.9,19746.788579918168 +6025,19325.0,19743.288259752873 +6026,19384.4,19740.309933945795 +6027,19936.0,19741.93391789645 +6028,20350.4,19746.983428951255 +6029,20526.3,19753.450786387344 +6030,20684.9,19761.180655379983 +6031,20623.6,19768.33766238928 +6032,20766.6,19776.621997141236 +6033,20742.3,19784.63592247616 +6034,21068.4,19795.28956627304 +6035,20931.7,19804.720358254177 +6036,21070.3,19815.223093870325 +6037,20998.8,19825.045308858953 +6038,21092.4,19835.562775175476 +6039,21258.5,19847.371382850368 +6040,21288.4,19859.330126561155 +6041,21237.4,19870.76639107102 +6042,21161.9,19881.481192804873 +6043,21280.0,19893.087158009814 +6044,21287.1,19904.65572931264 +6045,21241.7,19915.751532388884 +6046,21340.1,19927.57185162217 +6047,21352.0,19939.392832106634 +6048,21250.3,19950.2717297655 +6049,21281.9,19961.322586779894 +6050,21294.7,19972.38795950371 +6051,21294.5,19983.359843657207 +6052,21447.4,19995.509554498225 +6053,21532.2,20008.262172303217 +6054,21515.5,20020.77037004344 +6055,21274.9,20031.178084814863 +6056,21318.2,20041.858764608933 +6057,21313.7,20052.413463657824 +6058,21333.4,20063.044057320414 +6059,21279.0,20073.134978006554 +6060,21302.4,20083.336347483677 +6061,21167.0,20092.329406840658 +6062,21234.4,20101.807171099244 +6063,21213.3,20111.03117797809 +6064,21401.1,20121.737143306073 +6065,21281.4,20131.36090145291 +6066,21436.1,20142.188611814297 +6067,21569.4,20154.03268972455 +6068,21414.3,20164.491339602355 +6069,21594.0,20176.354482012295 +6070,21750.5,20189.417930294352 +6071,21639.5,20201.451806391495 +6072,21540.4,20212.563409657956 +6073,21600.7,20224.083215386934 +6074,21500.2,20234.673396172104 +6075,21537.4,20245.4844053325 +6076,21549.1,20256.30279201024 +6077,21579.9,20267.287001205175 +6078,21437.9,20277.00163190057 +6079,21599.9,20287.98004159434 +6080,21663.7,20299.396804734635 +6081,21591.4,20310.118822952605 +6082,21584.3,20320.69294060445 +6083,21601.9,20331.325364333876 +6084,21607.9,20341.91934471285 +6085,21531.5,20351.79138334594 +6086,21502.7,20361.34249219784 +6087,21633.9,20371.903135416116 +6088,21669.2,20382.6690844998 +6089,21621.4,20392.94900910976 +6090,21541.7,20402.482212353665 +6091,21610.5,20412.50725623455 +6092,21628.2,20422.595992697334 +6093,21502.7,20431.559511430136 +6094,21629.9,20441.50424577511 +6095,21820.3,20452.946534191913 +6096,21632.0,20462.731210256712 +6097,22036.5,20475.791532163297 +6098,21759.9,20486.44803397107 +6099,21649.2,20496.097427880028 +6100,21706.9,20506.145582005505 +6101,21775.9,20516.682963067702 +6102,21810.1,20527.41671441154 +6103,22180.9,20541.138567403978 +6104,22222.5,20555.09177431349 +6105,22160.3,20568.413004402173 +6106,22148.0,20581.521610174772 +6107,22301.0,20595.791140380792 +6108,22346.3,20610.31818485896 +6109,22383.6,20625.03421651988 +6110,22304.6,20638.972521776977 +6111,22106.0,20651.147023670943 +6112,22209.9,20664.082733018073 +6113,22348.5,20678.061299549045 +6114,22390.3,20692.27074934532 +6115,22415.0,20706.56725764951 +6116,22395.8,20720.58578663167 +6117,22330.7,20733.947730311076 +6118,22312.3,20747.046089395633 +6119,22386.0,20760.647366662062 +6120,22205.3,20772.63618519599 +6121,22230.3,20784.73298033959 +6122,22248.0,20796.876275108556 +6123,22225.7,20808.73373340641 +6124,22277.7,20820.924324830426 +6125,22378.2,20833.847774416896 +6126,22309.0,20846.089701600158 +6127,22267.8,20857.88812731302 +6128,22224.3,20869.22764492868 +6129,22377.7,20881.746087709354 +6130,22521.5,20895.35400399392 +6131,22516.3,20908.805837985674 +6132,21397.2,20912.858901570857 +6133,21188.3,20915.14471981508 +6134,21001.4,20915.8605312689 +6135,20823.9,20915.097373333057 +6136,20758.7,20913.79946981992 +6137,20754.2,20912.474992891955 +6138,20240.2,20906.89594730779 +6139,20272.0,20901.62710127204 +6140,20228.0,20896.036834871444 +6141,20119.0,20889.58839640778 +6142,20213.6,20883.97853419693 +6143,20162.5,20877.991160469155 +6144,20190.4,20872.285009759868 +6145,20290.6,20867.457748268083 +6146,20434.0,20863.86058853142 +6147,20313.2,20859.290791116222 +6148,20379.7,20855.310784550944 +6149,20305.8,20850.750529077493 +6150,20231.9,20845.614840039507 +6151,20235.3,20840.549986595197 +6152,20373.2,20836.671563469925 +6153,20339.9,20832.548977880964 +6154,20331.1,20828.3875755749 +6155,20219.8,20823.337056275526 +6156,20300.3,20818.996499791912 +6157,20297.2,20814.66623838285 +6158,20281.6,20810.242452172206 +6159,20165.6,20804.89272227866 +6160,20134.2,20799.326807570953 +6161,20094.6,20793.478452321404 +6162,19766.1,20784.952490061474 +6163,19935.1,20777.89977230163 +6164,19934.1,20770.897284564686 +6165,20191.4,20766.08817846871 +6166,20286.6,20762.10902346067 +6167,20217.0,20757.585297124897 +6168,20149.2,20752.536456484857 +6169,20184.8,20747.824950621914 +6170,20090.0,20742.365822400985 +6171,20015.9,20736.33706038936 +6172,20016.1,20730.359989348784 +6173,20138.5,20725.44828819236 +6174,20094.6,20720.21303268869 +6175,20195.2,20715.85607806057 +6176,20141.0,20711.085488201144 +6177,20120.0,20706.18021444014 +6178,20141.0,20701.48992220412 +6179,20140.9,20696.837723679608 +6180,20116.0,20692.01749360758 +6181,20109.9,20687.18664303822 +6182,19697.9,20678.976795378156 +6183,19728.8,20671.09151076921 +6184,19856.4,20664.330585368632 +6185,19820.3,20657.326182170553 +6186,19751.7,20649.810612194036 +6187,19774.9,20642.549943213173 +6188,19843.7,20635.92048310352 +6189,19818.2,20629.134421003077 +6190,19721.0,20621.598035766536 +6191,19690.8,20613.873570739426 +6192,19778.0,20606.936860608806 +6193,19721.4,20599.588006993796 +6194,19742.6,20592.476073325797 +6195,19738.4,20585.38830508243 +6196,19811.8,20578.968485123238 +6197,19745.7,20572.053393960392 +6198,19755.2,20565.27452762047 +6199,19730.5,20558.346938179635 +6200,19679.1,20551.050283091008 +6201,19746.2,20544.371027629673 +6202,19786.2,20538.07915188171 +6203,19856.0,20532.418743982278 +6204,19682.0,20525.36132701977 +6205,19519.3,20517.012270364008 +6206,19653.3,20509.844533680487 +6207,19653.3,20502.736280288947 +6208,19526.4,20494.633904518916 +6209,19435.3,20485.842751784323 +6210,19549.5,20478.072272516405 +6211,19605.9,20470.834328346147 +6212,19746.6,20464.82408495738 +6213,19736.5,20458.77990167973 +6214,19719.1,20452.64147925915 +6215,19792.5,20447.163126734176 +6216,19937.7,20442.935216968748 +6217,19990.6,20439.18139774079 +6218,19898.0,20434.69026580933 +6219,19875.0,20430.045533312987 +6220,19927.1,20425.871711459768 +6221,19849.9,20421.091863231886 +6222,19829.4,20416.18155731295 +6223,19834.0,20411.350175094587 +6224,19869.0,20406.849343766 +6225,19867.5,20402.37341560197 +6226,19855.1,20397.83172750569 +6227,19800.8,20392.87710736042 +6228,19849.6,20388.368583647887 +6229,19889.7,20384.230255152885 +6230,19912.8,20380.317970877757 +6231,19951.6,20376.760145393295 +6232,20118.8,20374.619397298746 +6233,20070.8,20372.098074499587 +6234,20060.0,20369.508048985066 +6235,20001.2,20366.45155065324 +6236,20068.8,20363.981413303423 +6237,20026.1,20361.177418172276 +6238,20116.4,20359.14607030363 +6239,20102.0,20357.012078018954 +6240,19974.0,20353.833554549918 +6241,19988.2,20350.799251192657 +6242,20026.5,20348.107971099773 +6243,19990.9,20345.14358959687 +6244,20004.0,20342.31252246329 +6245,20059.6,20339.966360451148 +6246,20031.1,20337.403154140353 +6247,20041.2,20334.945036678608 +6248,20042.6,20332.518936789158 +6249,19824.5,20328.30301200253 +6250,19909.5,20324.827468334457 +6251,19915.2,20321.428070256992 +6252,19976.6,20318.566426520418 +6253,19955.7,20315.555086881246 +6254,19800.1,20311.27745130547 +6255,19804.5,20307.071829302935 +6256,19739.8,20302.364179267228 +6257,19626.4,20296.754518028498 +6258,19630.0,20291.221285513737 +6259,19715.6,20286.444345384993 +6260,19714.0,20281.69376990462 +6261,19451.4,20274.803365175125 +6262,19435.4,20267.837362144626 +6263,19404.5,20260.67273673264 +6264,19449.0,20253.936863398758 +6265,19437.8,20247.163943370553 +6266,18754.9,20234.78001023055 +6267,18766.2,20222.59262425353 +6268,18804.5,20210.824220732757 +6269,18448.4,20196.198293589747 +6270,18464.4,20181.826523518463 +6271,18459.6,20167.534187223704 +6272,18406.4,20152.918965752968 +6273,18449.9,20138.786028277842 +6274,18766.0,20127.39361310541 +6275,18672.6,20115.320637063043 +6276,18806.6,20104.459884888245 +6277,19095.9,20096.09009331241 +6278,19295.4,20089.445362247578 +6279,19071.1,20080.994363390753 +6280,19198.6,20073.67158859083 +6281,18956.4,20064.399625200036 +6282,19147.9,20056.79381918178 +6283,19504.7,20052.21212773629 +6284,19509.9,20047.711612153416 +6285,19509.9,20043.24844524758 +6286,19594.6,20039.525221635566 +6287,19528.6,20035.28517830249 +6288,19509.9,20030.925135329027 +6289,19455.6,20026.150652878165 +6290,19461.1,20021.461435841833 +6291,19353.7,20015.919847162648 +6292,19325.0,20010.1860724974 +6293,19361.7,20004.804445339745 +6294,19373.0,19999.561254921988 +6295,19349.9,19994.16987521309 +6296,19295.0,19988.367635584764 +6297,19222.5,19982.011887571614 +6298,19301.7,19976.36614576604 +6299,19209.6,19970.00294123686 +6300,19117.2,19962.92573840502 +6301,18911.8,19954.202703231538 +6302,19061.3,19946.79272229186 +6303,19225.6,19940.807720447112 +6304,19090.2,19933.748735215187 +6305,18756.8,19923.981525794316 +6306,18962.5,19916.002425995193 +6307,18985.2,19908.277924534654 +6308,18962.9,19900.43246458001 +6309,18870.2,19891.88281757105 +6310,18953.2,19884.09291867005 +6311,18866.8,19875.65065378482 +6312,18925.0,19867.761436740966 +6313,18879.6,19859.560926892493 +6314,19003.4,19852.455856959776 +6315,19011.8,19845.479459806586 +6316,18994.0,19838.413240223126 +6317,18914.8,19830.7484000553 +6318,18866.0,19822.74218926646 +6319,18938.8,19815.406569438524 +6320,18865.7,19807.525187119532 +6321,18988.8,19800.730787226425 +6322,19094.9,19794.873270319982 +6323,19155.6,19789.568097952182 +6324,19234.5,19784.96172369532 +6325,19263.6,19780.635070386645 +6326,19245.5,19776.194115445676 +6327,19182.5,19771.267193325795 +6328,19227.0,19766.750453132223 +6329,19846.4,19767.411445222413 +6330,19627.5,19766.25035439069 +6331,18990.3,19759.81093236255 +6332,18916.3,19752.81084163755 +6333,18455.4,19742.043946686204 +6334,18472.6,19731.50914214939 +6335,18447.5,19720.85346462118 +6336,18377.5,19709.705303089057 +6337,18501.2,19699.676213436866 +6338,18733.8,19691.660643200878 +6339,18677.6,19683.245202178467 +6340,18694.8,19675.0423374301 +6341,18659.5,19666.614600190016 +6342,18759.2,19659.08418857018 +6343,18923.8,19652.982245096573 +6344,19195.0,19649.181562564652 +6345,19171.5,19645.21740021972 +6346,19140.1,19641.025554574742 +6347,19220.1,19637.532396445495 +6348,19191.9,19633.83420228412 +6349,18893.2,19627.68786035645 +6350,18869.3,19621.39418516677 +6351,18975.5,19616.034067447545 +6352,19030.2,19611.172373941754 +6353,19061.4,19606.60994760199 +6354,19185.6,19603.11608911567 +6355,19304.2,19600.635457670724 +6356,19241.0,19597.65093105105 +6357,19165.0,19594.060466892948 +6358,19387.0,19592.342122769354 +6359,19392.5,19590.683681916496 +6360,19300.1,19588.272199079016 +6361,19347.1,19586.270770041014 +6362,19286.0,19583.778896430715 +6363,19384.0,19582.120980277767 +6364,19402.4,19580.629519860522 +6365,19347.5,19578.6948350484 +6366,19323.5,19576.577035587423 +6367,19216.4,19573.588014545207 +6368,19061.7,19569.339981229477 +6369,19075.0,19565.237574746247 +6370,18954.0,19560.1650637525 +6371,18854.5,19554.3089221446 +6372,18888.3,19548.78187714755 +6373,18627.3,19541.134724640102 +6374,18665.0,19533.863897049727 +6375,18704.4,19526.98037923189 +6376,18757.7,19520.596309694698 +6377,18764.1,19514.318332020885 +6378,18626.2,19506.948055406603 +6379,18798.7,19501.070478183312 +6380,18841.1,19495.593544754403 +6381,18999.9,19491.47990537885 +6382,19325.4,19490.101648902677 +6383,19280.7,19488.363875882736 +6384,19138.3,19485.45878147707 +6385,19110.1,19482.343770842406 +6386,19118.0,19479.32017108438 +6387,19113.3,19476.282659291148 +6388,19154.9,19473.615583280436 +6389,19126.5,19470.734956033295 +6390,19068.5,19467.396906605634 +6391,18985.6,19463.398592027992 +6392,19072.3,19460.15296055888 +6393,19014.7,19456.456255492 +6394,19030.0,19452.917199429827 +6395,19044.7,19449.52950482875 +6396,19146.1,19447.01141765175 +6397,19108.7,19444.203854019786 +6398,19076.4,19441.151539878545 +6399,19115.2,19438.44654784636 +6400,19084.0,19435.505082719006 +6401,19076.2,19432.523297800177 +6402,19118.9,19429.920614830884 +6403,19093.0,19427.124593131044 +6404,19134.1,19424.69285376896 +6405,18941.4,19420.68212469204 +6406,18875.6,19416.158621582563 +6407,18911.0,19411.966433851587 +6408,18952.1,19408.15011489846 +6409,18968.0,19404.49741684951 +6410,18938.5,19400.63021836943 +6411,19044.5,19397.674780872592 +6412,18999.5,19394.370425844605 +6413,18983.0,19390.956563389464 +6414,19017.7,19387.858998548058 +6415,19089.2,19385.380500634797 +6416,19075.0,19382.804728845298 +6417,19033.3,19379.90427466401 +6418,19062.2,19377.2677246668 +6419,19108.0,19375.033137740105 +6420,19079.8,19372.58307020699 +6421,18925.2,19368.87034763266 +6422,18907.0,19365.03739868965 +6423,18978.3,19361.827959696373 +6424,18964.9,19358.53395173209 +6425,18996.2,19355.52703097083 +6426,18923.0,19351.93759502916 +6427,18906.5,19348.241017477052 +6428,18894.3,19344.4738721038 +6429,18736.0,19339.424296401696 +6430,18778.5,19334.769322987577 +6431,18799.7,19330.328913668178 +6432,18766.2,19325.647345919893 +6433,18871.7,19321.880148028442 +6434,18848.0,19317.947532692106 +6435,18862.7,19314.169544868935 +6436,18800.5,19309.906727069192 +6437,18760.2,19305.34484551675 +6438,18690.1,19300.23907916391 +6439,18902.3,19296.936680166702 +6440,19264.6,19296.668325974446 +6441,19076.6,19294.84203281283 +6442,19084.0,19293.09230639945 +6443,18864.7,19289.537183524764 +6444,19085.4,19287.843099014186 +6445,19180.8,19286.95477454104 +6446,19130.1,19285.653075167254 +6447,19103.9,19284.144750892006 +6448,19074.8,19282.40745005473 +6449,19104.2,19280.928550054276 +6450,19159.3,19279.91918449366 +6451,19191.0,19279.181265950145 +6452,19104.1,19277.728309386242 +6453,19085.6,19276.133883582206 +6454,19131.2,19274.931112764098 +6455,19216.3,19274.444547512943 +6456,19336.0,19274.95538114354 +6457,19758.7,19278.969859308323 +6458,20083.9,19285.64977748834 +6459,20091.9,19292.340650704202 +6460,20039.4,19298.54031335396 +6461,20199.6,19306.01798710206 +6462,20183.9,19313.303315009925 +6463,20174.2,19320.44768583972 +6464,20137.5,19327.228202969683 +6465,20173.2,19334.248715808113 +6466,20213.6,19341.546236838753 +6467,20221.1,19348.845438192788 +6468,20260.1,19356.40771671401 +6469,20185.2,19363.285660973645 +6470,20196.3,19370.198643040254 +6471,19885.3,19374.473343098012 +6472,19102.1,19372.21298340425 +6473,18946.9,19368.68341507724 +6474,19130.1,19366.703469723907 +6475,19068.7,19364.230411883873 +6476,19067.2,19361.765429212635 +6477,18984.0,19358.630446397594 +6478,19066.6,19356.206957215873 +6479,19070.1,19353.832625620722 +6480,19085.8,19351.60828847864 +6481,18997.9,19348.672949985044 +6482,18623.8,19342.657406831644 +6483,18736.0,19337.622905530137 +6484,18757.5,19332.80860755893 +6485,18814.8,19328.509780940185 +6486,18685.5,19323.173600185495 +6487,18770.6,19318.587927154913 +6488,18731.6,19313.716658049892 +6489,18643.9,19308.15801358475 +6490,18996.2,19305.569150401476 +6491,18947.6,19302.598452057897 +6492,19063.2,19300.611742912188 +6493,19113.6,19299.059778240717 +6494,19372.8,19299.67173028851 +6495,19477.9,19301.1508030662 +6496,19501.0,19302.809302625818 +6497,19337.9,19303.100511732657 +6498,19547.2,19305.12623362699 +6499,19534.7,19307.031410111416 +6500,19560.2,19309.132394259872 +6501,19606.8,19311.602664846927 +6502,19477.5,19312.97940621749 +6503,19407.3,19313.76214973436 +6504,19430.0,19314.726779197146 +6505,19488.7,19316.170540365634 +6506,19429.7,19317.11269355762 +6507,19497.3,19318.608023901543 +6508,19572.2,19320.7125216285 +6509,19446.6,19321.757230992578 +6510,19375.3,19322.201569324592 +6511,19353.1,19322.45798783642 +6512,19337.0,19322.57866843529 +6513,19471.7,19323.816189859066 +6514,19427.8,19324.67912604281 +6515,19454.3,19325.75481794287 +6516,19205.6,19324.757682524258 +6517,18981.0,19321.904921673435 +6518,19190.1,19320.811104896064 +6519,19280.7,19320.478232656264 +6520,19441.4,19321.481732800195 +6521,19257.4,19320.949934187745 +6522,19293.7,19320.72379365507 +6523,19399.3,19321.37587835503 +6524,19499.4,19322.853256957893 +6525,19425.5,19323.705097149115 +6526,19447.5,19324.732440741238 +6527,19582.5,19326.871590610604 +6528,19541.6,19328.65356911176 +6529,19500.7,19330.081340322453 +6530,19355.0,19330.2881341787 +6531,19394.0,19330.81686335564 +6532,19424.2,19331.59182714522 +6533,19439.9,19332.490650156466 +6534,19438.6,19333.37122567384 +6535,19560.8,19335.258601394387 +6536,19618.0,19337.60500304257 +6537,19521.6,19339.131932477903 +6538,19479.8,19340.29930233286 +6539,19430.3,19341.046196089435 +6540,19290.5,19340.62672558247 +6541,19465.3,19341.66135856519 +6542,19981.4,19346.970392933945 +6543,19749.4,19350.310057722876 +6544,19704.9,19353.252712845508 +6545,19669.0,19355.873022282474 +6546,19660.3,19358.399387242785 +6547,19467.0,19359.300637141187 +6548,19416.2,19359.772831023834 +6549,19352.0,19359.70832620206 +6550,19378.0,19359.860124324867 +6551,19411.7,19360.290330762007 +6552,19393.1,19360.562610174773 +6553,19413.2,19360.999434986603 +6554,19405.1,19361.365414779244 +6555,19408.0,19361.752423785227 +6556,19398.4,19362.05655304842 +6557,19312.8,19361.64778497333 +6558,19286.4,19361.023322027493 +6559,19305.5,19360.562547570833 +6560,19328.4,19360.29563846236 +6561,19323.5,19359.990280466824 +6562,19320.8,19359.66504992353 +6563,19304.2,19359.204759052795 +6564,19288.0,19358.61384818929 +6565,19323.5,19358.322446959504 +6566,19311.3,19357.93221918391 +6567,19322.8,19357.640665497736 +6568,19285.9,19357.045307277836 +6569,19266.8,19356.296383565987 +6570,19290.4,19355.74952561108 +6571,19257.0,19354.93002747323 +6572,19243.2,19354.002807328223 +6573,19281.3,19353.39946452882 +6574,19313.8,19353.07083826717 +6575,19304.0,19352.663611393582 +6576,19269.3,19351.971797191145 +6577,19299.2,19351.533856965496 +6578,19307.3,19351.166771015574 +6579,19301.2,19350.752109015448 +6580,19326.9,19350.554166202044 +6581,19322.9,19350.32467104684 +6582,19271.9,19349.673843901226 +6583,19276.5,19349.06659208462 +6584,19275.4,19348.45525107147 +6585,19253.9,19347.67056019121 +6586,19095.1,19345.574538944813 +6587,19180.9,19344.207945260627 +6588,19151.0,19342.60455982278 +6589,19152.0,19341.022779243336 +6590,19096.0,19338.989395183227 +6591,19137.5,19337.317284019882 +6592,19191.4,19336.10635220229 +6593,19189.3,19334.888042225506 +6594,19209.3,19333.8458178087 +6595,19253.6,19333.179877411945 +6596,19223.3,19332.26801120936 +6597,19189.9,19331.086533937912 +6598,19161.6,19329.680006685317 +6599,19049.4,19327.354031526105 +6600,19084.0,19325.33449599477 +6601,19210.9,19324.3848321276 +6602,19129.3,19322.76587086513 +6603,19159.4,19321.410137496954 +6604,19090.0,19319.48972141814 +6605,19235.3,19318.79105153085 +6606,19187.1,19317.698179733914 +6607,19190.0,19316.638443802512 +6608,19187.6,19315.567585347722 +6609,19170.9,19314.367024473468 +6610,19225.5,19313.62953879319 +6611,19240.5,19313.022654653825 +6612,19245.9,19312.46562017537 +6613,19170.3,19311.285822497568 +6614,19406.6,19312.076811522482 +6615,19329.9,19312.224721800307 +6616,19403.5,19312.982192988686 +6617,19540.0,19314.866158192097 +6618,19605.4,19317.27722741872 +6619,19537.7,19319.10646204595 +6620,19586.4,19321.324665680426 +6621,19546.5,19323.193340654034 +6622,19513.3,19324.770989279314 +6623,19622.0,19327.23762007368 +6624,19555.1,19329.12859418095 +6625,19500.1,19330.547444021773 +6626,19537.6,19332.265722494623 +6627,19588.1,19334.38882853201 +6628,19584.8,19336.466929540045 +6629,19601.8,19338.668863734732 +6630,19832.0,19342.762898060588 +6631,19957.0,19347.860301396187 +6632,19879.7,19352.27390885348 +6633,19932.9,19357.09238263893 +6634,19955.7,19362.060080708317 +6635,19930.0,19366.773275059284 +6636,19973.9,19371.811671116884 +6637,20089.9,19377.77091036073 +6638,20102.9,19383.788579154418 +6639,20092.2,19389.66751210749 +6640,19988.4,19394.636246446848 +6641,20017.5,19399.80524025227 +6642,20115.5,19405.744615851836 +6643,20213.5,19412.44798003564 +6644,20322.7,19420.001938707544 +6645,20281.2,19427.14881058549 +6646,20293.4,19434.33761713665 +6647,20333.0,19441.79539624755 +6648,20181.5,19447.934023664584 +6649,20209.0,19454.249923883133 +6650,20186.4,19460.32585812477 +6651,20184.9,19466.338921542818 +6652,20117.2,19471.7402582935 +6653,20190.7,19477.70672917903 +6654,20244.5,19484.0701588124 +6655,20218.9,19490.168331768313 +6656,20128.9,19495.469009512974 +6657,20085.7,19500.36719200664 +6658,20119.2,19505.502733981688 +6659,20026.1,19509.82304324325 +6660,20017.0,19514.031980643722 +6661,19915.3,19517.362005700623 +6662,19872.5,19520.309208972816 +6663,19994.6,19524.24523213487 +6664,20227.1,19530.0780517852 +6665,20156.1,19535.27325467495 +6666,20259.0,19541.279285756485 +6667,20112.5,19546.019706621577 +6668,19977.5,19549.60045594422 +6669,20042.5,19553.690908591987 +6670,20182.7,19558.910901051804 +6671,20150.0,19563.816204777515 +6672,20262.5,19569.614410546994 +6673,20363.9,19576.205992202205 +6674,20322.8,19582.401793096793 +6675,20346.6,19588.743686930015 +6676,20346.7,19595.033780814414 +6677,20295.1,19600.84345898193 +6678,20169.5,19605.562600401165 +6679,20214.7,19610.617682555512 +6680,20138.7,19615.000108426422 +6681,20092.9,19618.96608263035 +6682,20125.0,19623.16553422678 +6683,20238.0,19628.26789493859 +6684,20184.6,19632.884758881013 +6685,20104.7,19636.800238060427 +6686,20096.2,19640.6126842176 +6687,20004.4,19643.631666091314 +6688,20042.2,19646.939287119603 +6689,20108.3,19650.76800672857 +6690,20011.9,19653.76495273082 +6691,20005.9,19656.687235280773 +6692,20044.4,19659.904768597946 +6693,19903.0,19661.922156410412 +6694,19893.3,19663.842304489994 +6695,19954.0,19666.250252170576 +6696,19981.9,19668.86975215256 +6697,19985.9,19671.500708566233 +6698,20008.9,19674.300702686014 +6699,19976.0,19676.80443129443 +6700,19934.1,19678.939664229747 +6701,19960.7,19681.277924277634 +6702,19846.4,19682.648231959978 +6703,19916.8,19684.591400159483 +6704,19964.3,19686.912633353182 +6705,19971.5,19689.27435423822 +6706,19988.1,19691.75423511591 +6707,19990.3,19694.231793330717 +6708,19608.8,19693.522815792705 +6709,19594.5,19692.701049686544 +6710,19633.9,19692.213074170475 +6711,19574.1,19691.232882683584 +6712,19399.7,19688.813522661312 +6713,19412.0,19686.516315004374 +6714,19446.4,19684.523648489816 +6715,19453.0,19682.60229041106 +6716,19540.0,19681.418868913872 +6717,19561.8,19680.426181205043 +6718,19542.1,19679.278246091308 +6719,19520.9,19677.963903800093 +6720,19559.0,19676.976651486402 +6721,19584.1,19676.205890893158 +6722,19553.0,19675.183435367075 +6723,19494.5,19673.68398777067 +6724,19437.0,19671.719805299545 +6725,19499.8,19670.293084923615 +6726,19504.9,19668.920528202256 +6727,19489.8,19667.43405078979 +6728,19459.3,19665.706797256265 +6729,19492.2,19664.266906822602 +6730,19494.1,19662.854733321998 +6731,19526.3,19661.721499020572 +6732,19505.0,19660.420905667706 +6733,19470.1,19658.841479064653 +6734,19490.2,19657.44196471557 +6735,19480.0,19655.969417290544 +6736,19517.5,19654.820293495603 +6737,19491.7,19653.466598113897 +6738,19466.6,19651.915837963574 +6739,19460.1,19650.32400528338 +6740,19442.8,19648.601814368165 +6741,19309.4,19645.78686155183 +6742,19351.9,19643.34796643522 +6743,19406.2,19641.379933518747 +6744,19386.6,19639.265577223985 +6745,19394.9,19637.237647122543 +6746,19354.0,19634.887127229413 +6747,19357.9,19632.588478870664 +6748,19379.4,19630.48732966842 +6749,19398.8,19628.564613239636 +6750,19417.6,19626.813869561298 +6751,19398.2,19624.91665902552 +6752,19387.8,19622.948885921574 +6753,19406.0,19621.14848022928 +6754,19480.0,19619.97712354688 +6755,19461.0,19618.657811318277 +6756,19490.7,19617.59592076792 +6757,19457.5,19616.26732391507 +6758,19491.4,19615.231080563077 +6759,19524.5,19614.47812553766 +6760,19496.4,19613.498224080915 +6761,19476.9,19612.364628860327 +6762,19514.5,19611.552474263975 +6763,19459.8,19610.29311763108 +6764,19473.9,19609.161224538708 +6765,19471.0,19608.014658359964 +6766,19390.3,19606.20789770967 +6767,19430.0,19604.745591504612 +6768,19484.1,19603.74438327636 +6769,19491.9,19602.81621412054 +6770,19462.4,19601.650934335306 +6771,19441.2,19600.31939131178 +6772,19458.7,19599.14412665359 +6773,19403.5,19597.520523942774 +6774,19384.3,19595.751059013786 +6775,19401.3,19594.137357279233 +6776,19228.3,19591.10136261302 +6777,19320.6,19588.85653802702 +6778,19267.5,19586.18967879028 +6779,19323.2,19584.007191829365 +6780,19410.0,19582.563148743648 +6781,19308.3,19580.287106015487 +6782,19252.1,19577.56356156723 +6783,19261.7,19574.942287197377 +6784,19136.9,19571.307081494495 +6785,19317.2,19569.19830903396 +6786,19216.6,19566.272181988035 +6787,19204.9,19563.273242718427 +6788,19237.5,19560.56973033072 +6789,19240.0,19557.90940061843 +6790,19123.6,19554.305173227407 +6791,19124.0,19550.73417593921 +6792,19039.6,19546.492398545528 +6793,19028.3,19542.19204669038 +6794,19052.5,19538.128212278014 +6795,19039.7,19533.991878566165 +6796,19036.9,19529.866634760634 +6797,19024.5,19525.672720779217 +6798,19052.0,19521.741826830843 +6799,19075.1,19518.035255653824 +6800,19100.5,19514.57023278533 +6801,19057.4,19510.776288944788 +6802,19085.0,19507.242875758522 +6803,19149.9,19504.277374714886 +6804,19142.2,19501.27258322348 +6805,18982.5,19496.967416557723 +6806,19028.0,19493.07557077716 +6807,19137.6,19490.12556604042 +6808,19145.9,19487.26892233884 +6809,19122.4,19484.24096447711 +6810,18968.0,19479.956807095557 +6811,18991.3,19475.901563883144 +6812,19013.4,19472.063376631002 +6813,19002.1,19468.163265621617 +6814,19033.6,19464.556931467083 +6815,19055.0,19461.158118757812 +6816,19084.0,19458.028175863557 +6817,19057.1,19454.700971084607 +6818,19060.1,19451.426274229132 +6819,19041.5,19448.0243964347 +6820,19089.8,19445.051579866777 +6821,19106.9,19442.245342689457 +6822,19143.4,19439.765298351787 +6823,19103.4,19436.973885087456 +6824,19133.8,19434.45791923611 +6825,19162.3,19432.199347292244 +6826,19145.2,19429.81760997032 +6827,19107.2,19427.14028540625 +6828,19069.0,19424.16816685516 +6829,19110.0,19421.560962150972 +6830,19084.0,19418.75962636549 +6831,19130.5,19416.367430296064 +6832,19090.1,19413.659816766638 +6833,19083.4,19410.91907139928 +6834,19108.5,19408.40936956194 +6835,19132.3,19406.11800549919 +6836,19162.8,19404.09876893903 +6837,19170.5,19402.16018994369 +6838,19144.0,19400.01778172839 +6839,19148.0,19397.926347855126 +6840,19141.5,19395.798328370853 +6841,19100.8,19393.350209463213 +6842,19079.6,19390.746473285093 +6843,19080.7,19388.173473506795 +6844,19087.9,19385.681577461095 +6845,19083.5,19383.17384652781 +6846,19052.5,19380.429665228825 +6847,19000.1,19377.273402446845 +6848,19002.2,19374.160760102888 +6849,18987.6,19370.95278699 +6850,18682.4,19365.238655977635 +6851,18744.9,19360.090617338818 +6852,18377.9,19351.939657858828 +6853,18420.5,19344.209868167054 +6854,18404.8,19336.413935651148 +6855,18947.7,19333.18809386151 +6856,19077.5,19331.066200966397 +6857,19148.7,19329.552788510246 +6858,19409.0,19330.212101468667 +6859,19360.9,19330.466772825774 +6860,19373.5,19330.823895042988 +6861,19391.4,19331.326601308192 +6862,19421.1,19332.071608766215 +6863,19365.5,19332.34902280135 +6864,19398.3,19332.89633381545 +6865,19854.2,19337.222505319056 +6866,19818.6,19341.217339299812 +6867,19806.3,19345.07694644255 +6868,19803.7,19348.88294688701 +6869,19796.0,19352.593461850603 +6870,19783.6,19356.170279594582 +6871,19633.2,19358.469281423673 +6872,19609.7,19360.554183652523 +6873,19641.0,19362.88153482553 +6874,19678.7,19365.50243495146 +6875,19588.3,19367.3513774 +6876,19792.0,19370.875432359335 +6877,19657.5,19373.254059476683 +6878,19456.9,19373.948216659453 +6879,19352.9,19373.77354266228 +6880,19297.0,19373.136417826907 +6881,19355.1,19372.986738010917 +6882,19206.8,19371.607594956884 +6883,19160.0,19369.851515330687 +6884,19167.1,19368.16893014122 +6885,19098.3,19365.929353957476 +6886,19150.2,19364.139068862394 +6887,19164.6,19362.483142979716 +6888,19191.7,19361.065855486107 +6889,19166.0,19359.44705170614 +6890,19186.6,19358.01263633929 +6891,19190.7,19356.624149730662 +6892,19178.3,19355.144281268167 +6893,19167.4,19353.586237440217 +6894,19159.2,19351.973073644032 +6895,19159.0,19350.37163734823 +6896,19061.1,19347.971042847417 +6897,19099.0,19345.904893114242 +6898,19147.1,19344.255059976367 +6899,19160.8,19342.73261134586 +6900,19157.8,19341.19790087826 +6901,19161.0,19339.70248261371 +6902,19136.0,19338.01200557957 +6903,19119.5,19336.198627939906 +6904,19117.7,19334.385361318 +6905,19100.1,19332.44108446059 +6906,19108.4,19330.581822348886 +6907,19091.9,19328.601060337693 +6908,19088.3,19326.60686066684 +6909,19062.1,19324.41178298496 +6910,19049.7,19322.13201715106 +6911,19060.4,19319.959967216197 +6912,19130.7,19318.38934508162 +6913,19134.7,19316.86495217638 +6914,19106.1,19315.115865436328 +6915,19111.4,19313.425277341423 +6916,19127.0,19311.87817960415 +6917,19139.3,19310.445995541046 +6918,19123.0,19308.890427113318 +6919,19124.2,19307.357726473372 +6920,19157.9,19306.117413390606 +6921,19130.8,19304.66249709691 +6922,19119.1,19303.122559361665 +6923,19136.7,19301.741459283978 +6924,19123.9,19300.265596551333 +6925,19118.4,19298.756338488667 +6926,19120.3,19297.27537302403 +6927,19148.6,19296.04155250101 +6928,19127.9,19294.646186920087 +6929,19125.5,19293.242484124068 +6930,19123.3,19291.8321730525 +6931,19369.9,19292.480038836293 +6932,19320.0,19292.70842025674 +6933,19138.5,19291.428682329297 +6934,19289.8,19291.41516629337 +6935,19251.9,19291.087239602137 +6936,19220.0,19290.49730400378 +6937,19166.9,19289.471600236113 +6938,19193.5,19288.675155420875 +6939,19178.4,19287.76000890286 +6940,19220.2,19287.199344928562 +6941,19263.7,19287.004329617954 +6942,19229.6,19286.527945139795 +6943,19285.0,19286.515265097143 +6944,19251.0,19286.220532606712 +6945,19327.6,19286.563930676366 +6946,19386.4,19287.392445774487 +6947,19452.8,19288.76512257304 +6948,19509.3,19290.59528753094 +6949,19574.6,19292.9521731116 +6950,19464.9,19294.379126031836 +6951,19524.5,19296.288842828253 +6952,19501.9,19297.99515948528 +6953,19513.3,19299.781921647227 +6954,19508.6,19301.51485175804 +6955,19519.3,19303.322197386602 +6956,19520.0,19305.12035342489 +6957,19509.5,19306.816450076964 +6958,19507.3,19308.480213976738 +6959,19540.6,19310.40651925494 +6960,19587.9,19312.709369717555 +6961,19528.0,19314.496013952266 +6962,19485.1,19315.911814666357 +6963,19539.9,19317.77063778116 +6964,19572.4,19319.883744521565 +6965,19545.0,19321.751929214333 +6966,19634.3,19324.345689137866 +6967,19655.8,19327.096347319293 +6968,19526.2,19328.74865978967 +6969,19521.3,19330.346596222953 +6970,19521.9,19331.93625102608 +6971,19555.8,19333.794041473997 +6972,19620.6,19336.174173909898 +6973,19534.9,19337.8233508899 +6974,19409.5,19338.41817785347 +6975,19364.1,19338.631305008214 +6976,19403.9,19339.17295392931 +6977,19399.0,19339.669443938194 +6978,19211.6,19338.606626976052 +6979,19209.6,19337.536032561315 +6980,19357.7,19337.703368390685 +6981,19286.3,19337.27678442064 +6982,19312.2,19337.06867832586 +6983,19320.9,19336.934498422743 +6984,19226.1,19336.01471005409 +6985,19305.3,19335.75981619472 +6986,19285.6,19335.34355215991 +6987,19276.0,19334.851074548624 +6988,19269.1,19334.305422477682 +6989,19246.9,19333.580066274546 +6990,19183.1,19332.331269044054 +6991,19210.0,19331.316071790578 +6992,19188.1,19330.127556671985 +6993,19259.9,19329.544755371804 +6994,19207.7,19328.53359557619 +6995,19193.2,19327.410495197964 +6996,19156.6,19325.992980714997 +6997,19117.0,19324.2585991323 +6998,19145.0,19322.770975902986 +6999,19259.8,19322.248395190098 +7000,19173.1,19321.010649171923 +7001,19174.9,19319.798112664274 +7002,19180.6,19318.64294160482 +7003,19223.6,19317.854203500214 +7004,19187.1,19316.769106375734 +7005,19185.8,19315.682225824898 +7006,19136.0,19314.191087021372 +7007,19116.7,19312.552156838625 +7008,18963.4,19309.654628566106 +7009,19036.2,19307.38529554896 +7010,19053.2,19305.275874009138 +7011,19033.2,19303.01798293852 +7012,19173.9,19301.94646440791 +7013,19129.5,19300.515373416973 +7014,19125.6,19299.063793554593 +7015,19138.0,19297.72716456244 +7016,19104.2,19296.121130001757 +7017,19178.0,19295.140871661493 +7018,19190.9,19294.275802187123 +7019,19199.0,19293.485131629557 +7020,19126.6,19292.10019277786 +7021,19163.0,19291.02882188344 +7022,19316.3,19291.23854120391 +7023,19244.3,19290.84900974164 +7024,19173.4,19289.87432916287 +7025,19091.6,19288.228899045338 +7026,19054.7,19286.290899883137 +7027,19055.8,19284.37811233224 +7028,19021.3,19282.19489148301 +7029,19063.0,19280.37584674041 +7030,19026.1,19278.265673738417 +7031,19034.7,19276.244381840173 +7032,19039.6,19274.28052804897 +7033,19057.8,19272.484009144 +7034,19101.8,19271.067544337824 +7035,19044.0,19269.183166376515 +7036,19040.0,19267.281231385838 +7037,19034.9,19265.35275643658 +7038,19027.4,19263.378044764908 +7039,19019.4,19261.35333070047 +7040,18973.0,19258.96035700171 +7041,19002.2,19256.829565657295 +7042,18993.4,19254.64342818296 +7043,18934.3,19251.984976496795 +7044,18870.1,19248.81580656736 +7045,19054.0,19247.199077882156 +7046,18966.4,19244.868795078157 +7047,19158.7,19244.153701343068 +7048,19116.9,19243.097654029018 +7049,19140.0,19242.242071837907 +7050,19130.3,19241.31309198863 +7051,19178.8,19240.794311142254 +7052,19168.1,19240.191038850615 +7053,19146.0,19239.40937047841 +7054,19179.9,19238.915516781497 +7055,19150.9,19238.185097555095 +7056,19156.8,19237.50970255464 +7057,19148.7,19236.772692574934 +7058,19118.8,19235.793666080535 +7059,19142.0,19235.01529540767 +7060,19137.9,19234.209359346194 +7061,19133.3,19233.371937276934 +7062,19136.7,19232.569680536046 +7063,19155.3,19231.92843837392 +7064,19147.9,19231.231106935134 +7065,19132.0,19230.407612271774 +7066,19156.6,19229.795100966618 +7067,19168.9,19229.289747431627 +7068,19173.5,19228.826761975764 +7069,19182.9,19228.44562702161 +7070,19226.4,19228.428650863756 +7071,19223.7,19228.389408947874 +7072,19185.0,19228.02933086532 +7073,19141.9,19227.314564634074 +7074,19160.6,19226.76091679479 +7075,19175.1,19226.332195493585 +7076,19206.8,19226.170102584925 +7077,19195.0,19225.91142953443 +7078,19185.1,19225.572745471905 +7079,19195.1,19225.319859617364 +7080,19186.6,19224.998532981535 +7081,19170.5,19224.546262998287 +7082,19186.3,19224.228866624857 +7083,19188.9,19223.935681009712 +7084,19176.3,19223.54036415486 +7085,19167.4,19223.074469016643 +7086,19167.2,19222.610780477087 +7087,19151.6,19222.02147939429 +7088,19173.3,19221.61715176446 +7089,19172.9,19221.212860048574 +7090,19160.0,19220.70486950875 +7091,19142.0,19220.051717064696 +7092,19142.9,19219.411453852543 +7093,19169.2,19218.994761289454 +7094,19181.6,19218.68443132025 +7095,19180.6,19218.368377948296 +7096,19168.9,19217.95785199022 +7097,19468.7,19220.038699691548 +7098,19434.5,19221.818461519833 +7099,19492.2,19224.06229171469 +7100,19490.3,19226.271733277226 +7101,19533.1,19228.818025947126 +7102,19584.1,19231.766424072048 +7103,19560.7,19234.49616329137 +7104,19510.4,19236.785821687292 +7105,19406.5,19238.194238104825 +7106,19406.2,19239.588476792753 +7107,19384.0,19240.786912669995 +7108,19328.8,19241.517311734973 +7109,19327.7,19242.23252076622 +7110,19284.9,19242.586607730816 +7111,19318.4,19243.215764513134 +7112,19330.0,19243.935965637505 +7113,19375.1,19245.02446384798 +7114,19395.7,19246.274883235135 +7115,19412.6,19247.65517466057 +7116,19364.9,19248.62816076297 +7117,19341.8,19249.401371047097 +7118,19264.7,19249.52833062347 +7119,19291.7,19249.87830298344 +7120,19263.1,19249.988026610135 +7121,19276.4,19250.2072131113 +7122,19325.8,19250.83453914357 +7123,19335.9,19251.540476578066 +7124,19371.0,19252.53184191767 +7125,19357.3,19253.401287212957 +7126,19348.7,19254.192147899987 +7127,19318.1,19254.722503519075 +7128,19301.7,19255.112358261653 +7129,19288.7,19255.39109387774 +7130,19303.0,19255.786188534355 +7131,19332.7,19256.424477426186 +7132,19331.6,19257.04834068406 +7133,19330.6,19257.658727898302 +7134,19319.0,19258.167784098317 +7135,19296.9,19258.489213275927 +7136,19284.9,19258.708389929237 +7137,19254.7,19258.675125282523 +7138,19286.4,19258.905207230386 +7139,19291.0,19259.17155405835 +7140,19315.0,19259.634860663675 +7141,19469.4,19261.375650201735 +7142,19478.7,19263.179171776825 +7143,19740.3,19267.138680724736 +7144,19806.0,19271.610558892997 +7145,20108.6,19278.556529358615 +7146,20261.9,19286.71705608593 +7147,20266.8,19294.850524500154 +7148,20180.0,19302.19616330098 +7149,20219.5,19309.808643273587 +7150,20053.1,19315.977036275468 +7151,20068.3,19322.220380372768 +7152,20155.4,19329.13473406262 +7153,20219.7,19336.525317182433 +7154,20199.8,19343.689422434032 +7155,20238.6,19351.11606623126 +7156,20192.1,19358.095186013576 +7157,20180.1,19364.91680272716 +7158,20208.7,19371.919152911996 +7159,20333.7,19379.900736705258 +7160,20658.7,19390.5131787243 +7161,20586.0,19400.434231183022 +7162,20655.2,19410.847225115114 +7163,20595.9,19420.68168797723 +7164,20539.3,19429.964827496093 +7165,20542.9,19439.200804031392 +7166,20820.1,19450.660548396274 +7167,20835.7,19462.154651729088 +7168,20753.7,19472.87287038694 +7169,20811.1,19483.97848971983 +7170,20687.0,19493.962070718004 +7171,20754.4,19504.422136521174 +7172,20736.2,19514.64435945461 +7173,20729.3,19524.72448925167 +7174,20808.2,19535.37573830352 +7175,20758.3,19545.524487363244 +7176,20742.4,19555.4570642316 +7177,20715.1,19565.080657059552 +7178,20672.9,19574.27417857773 +7179,20744.3,19583.983936431854 +7180,20789.0,19593.984069739476 +7181,20791.5,19603.92196127691 +7182,20708.7,19613.090243755938 +7183,20703.6,19622.14011725174 +7184,20700.1,19631.085842419776 +7185,20602.5,19639.14737069845 +7186,20540.0,19646.623326128338 +7187,20613.1,19654.643879438474 +7188,20713.0,19663.42691778338 +7189,20512.2,19670.470677801775 +7190,20559.4,19677.847684624998 +7191,20554.0,19685.118658196578 +7192,20564.3,19692.414768916937 +7193,20573.0,19699.722530170737 +7194,20571.1,19706.953878468077 +7195,20620.0,19714.53102470486 +7196,20383.2,19720.08014483179 +7197,20395.7,19725.68694860912 +7198,20258.7,19730.110293433943 +7199,20285.5,19734.719336641963 +7200,20273.6,19739.19137534203 +7201,20312.3,19743.94746351347 +7202,20214.5,19747.852463816263 +7203,20284.3,19752.30431058957 +7204,20242.5,19756.372324609576 +7205,20280.2,19760.71944224767 +7206,20248.0,19764.763264303707 +7207,20069.9,19767.295519371724 +7208,20169.0,19770.629166513867 +7209,20193.0,19774.13431865898 +7210,20165.7,19777.38382638795 +7211,20163.4,19780.587280110874 +7212,20199.8,19784.066223844395 +7213,20460.4,19789.67895227722 +7214,20487.1,19795.46667881434 +7215,20473.2,19801.091021728746 +7216,20691.9,19808.483627357557 +7217,20620.5,19815.222352441728 +7218,20620.1,19821.901834994078 +7219,20597.3,19828.336674537695 +7220,20617.8,19834.8882374046 +7221,20583.9,19841.104102654353 +7222,20621.5,19847.580417155146 +7223,20582.8,19853.68182448166 +7224,20613.6,19859.988199382227 +7225,20619.0,19866.28705249939 +7226,20724.6,19873.40998152429 +7227,20745.2,19880.644753461853 +7228,20670.1,19887.196249283745 +7229,20647.6,19893.506653854005 +7230,20674.0,19899.983777058536 +7231,20765.7,19907.168144053903 +7232,20912.7,19915.512806758852 +7233,20944.4,19924.051289690313 +7234,20765.9,19931.03758604143 +7235,20688.7,19937.325240929054 +7236,20757.5,19944.131670464914 +7237,20772.1,19951.002776934085 +7238,20799.0,19958.040098287333 +7239,20913.2,19965.96673647582 +7240,20851.7,19973.317219990546 +7241,20800.1,19980.17848787444 +7242,20841.8,19987.328873867184 +7243,20827.1,19994.297928855838 +7244,20840.2,20001.317863056203 +7245,20835.0,20008.236387014244 +7246,20747.7,20014.3730145079 +7247,20796.7,20020.865354636466 +7248,20703.2,20026.527882813756 +7249,20768.2,20032.682838143104 +7250,20745.3,20038.596673511212 +7251,20767.7,20044.647323523568 +7252,20735.1,20050.377221253664 +7253,20792.9,20056.539236015044 +7254,20882.3,20063.392022438158 +7255,20842.5,20069.857648807967 +7256,20773.4,20075.696174544002 +7257,20683.7,20080.741849444054 +7258,20674.9,20085.672622477712 +7259,20760.2,20091.270360050512 +7260,20715.6,20096.45151888827 +7261,20639.8,20100.960634914096 +7262,20730.5,20106.18502798535 +7263,20667.6,20110.844073396263 +7264,20592.4,20114.840388139863 +7265,20650.1,20119.28237662003 +7266,20667.3,20123.830240714473 +7267,20637.3,20128.09140054257 +7268,20671.3,20132.59935572479 +7269,20679.0,20137.1338009055 +7270,20575.8,20140.774184300477 +7271,20615.6,20144.714647501303 +7272,20532.8,20147.93527283324 +7273,20512.0,20150.95655687612 +7274,20460.1,20153.522062628184 +7275,20507.9,20156.46295837401 +7276,20551.6,20159.74210394767 +7277,20535.5,20162.860426736483 +7278,20507.2,20165.718016556097 +7279,20537.2,20168.800854592973 +7280,20478.8,20171.3734616088 +7281,20704.7,20175.79940798549 +7282,20741.6,20180.494848583123 +7283,20722.1,20184.9894971426 +7284,20688.9,20189.171327041833 +7285,20315.4,20190.21886789626 +7286,20326.5,20191.349831648156 +7287,20390.4,20193.001700265188 +7288,20366.3,20194.439860428964 +7289,20363.3,20195.841189388062 +7290,20424.2,20197.736283252063 +7291,20358.8,20199.072911606818 +7292,20392.5,20200.678115659874 +7293,20397.1,20202.30817279133 +7294,20467.3,20204.507275091815 +7295,20482.0,20206.810119281923 +7296,20445.4,20208.790118292032 +7297,20502.0,20211.223395318655 +7298,20568.1,20214.185026892774 +7299,20471.3,20216.318761109433 +7300,20484.3,20218.542671805622 +7301,20519.0,20221.036093616363 +7302,20616.9,20224.321271262703 +7303,20590.2,20227.357609260525 +7304,20572.9,20230.225180967907 +7305,20583.8,20233.159411831246 +7306,20606.9,20236.2609934758 +7307,20511.7,20238.54679435982 +7308,20533.0,20240.990389427374 +7309,20411.3,20242.403747191463 +7310,20397.0,20243.686703646308 +7311,20435.6,20245.279345109822 +7312,20401.5,20246.575782079868 +7313,20431.8,20248.112912519042 +7314,20456.1,20249.838946440046 +7315,20438.6,20251.40542821233 +7316,20471.8,20253.23442880808 +7317,20443.0,20254.80924682627 +7318,20467.0,20256.570165939745 +7319,20472.1,20258.358795268046 +7320,20507.7,20260.428016884078 +7321,20496.1,20262.383800976328 +7322,20505.3,20264.399703042913 +7323,20511.0,20266.44617853633 +7324,20508.7,20268.456583693704 +7325,20475.0,20270.170636941057 +7326,20465.0,20271.78747812827 +7327,20414.2,20272.96932478281 +7328,20495.9,20274.81937188005 +7329,20454.9,20276.31381692669 +7330,20401.1,20277.3493869107 +7331,20423.0,20278.558105691525 +7332,20389.7,20279.48044506338 +7333,20389.7,20280.395130166587 +7334,20424.3,20281.58936145151 +7335,20400.2,20282.573682103368 +7336,20513.2,20284.487593455207 +7337,20431.2,20285.705123799977 +7338,20445.1,20287.027902855578 +7339,20241.1,20286.64675843354 +7340,20164.5,20285.633092388452 +7341,20142.0,20284.441116518006 +7342,20100.8,20282.91712384981 +7343,20140.1,20281.73191950251 +7344,20191.1,20280.979787390454 +7345,20294.3,20281.090328573937 +7346,20275.3,20281.042276054653 +7347,20299.4,20281.194622311465 +7348,20348.4,20281.752343288135 +7349,20366.6,20282.456473219358 +7350,20297.0,20282.577166387662 +7351,20293.8,20282.670301936312 +7352,20289.8,20282.7294695551 +7353,20309.8,20282.954121260038 +7354,20260.0,20282.763630627174 +7355,20124.3,20281.448579750602 +7356,20099.3,20279.936973279644 +7357,20096.7,20278.41633449724 +7358,20308.4,20278.665161596848 +7359,20241.0,20278.352587641686 +7360,20286.4,20278.419371146734 +7361,20218.4,20277.921285079126 +7362,20233.0,20277.548494331582 +7363,20253.7,20277.350581515555 +7364,20226.6,20276.929414863975 +7365,20248.5,20276.693486109918 +7366,20185.0,20275.932544316474 +7367,20199.7,20275.299909093934 +7368,20270.7,20275.26173557448 +7369,20276.7,20275.273671378844 +7370,20301.1,20275.487997757446 +7371,20309.2,20275.76776541091 +7372,20317.1,20276.110771507087 +7373,20431.9,20277.403628175078 +7374,20596.8,20280.05422047238 +7375,20603.3,20282.73675806182 +7376,20572.2,20285.138942642217 +7377,20557.8,20287.401690006183 +7378,20641.6,20290.341095068372 +7379,20547.0,20292.471044486894 +7380,20749.0,20296.259666524347 +7381,20749.8,20300.023486719165 +7382,21209.0,20307.566860273364 +7383,20829.3,20311.896595872753 +7384,20773.8,20315.72981914352 +7385,20762.7,20319.43911525021 +7386,20938.9,20324.579869480498 +7387,21097.3,20330.99248467153 +7388,21119.5,20337.536115504132 +7389,21161.4,20344.373160188745 +7390,21165.0,20351.18334143199 +7391,21137.6,20357.70962075621 +7392,21426.5,20366.57925045948 +7393,21351.9,20374.756186140316 +7394,21373.9,20383.047836047866 +7395,21436.5,20391.790177657425 +7396,21354.9,20399.782790290978 +7397,21382.1,20407.93480033006 +7398,21391.1,20416.093847630225 +7399,21374.4,20424.04659578267 +7400,21283.4,20431.17815930315 +7401,21281.1,20438.23145258694 +7402,21312.4,20445.485963353854 +7403,21395.5,20453.36989726793 +7404,21304.0,20460.42906824496 +7405,21261.7,20467.07861954583 +7406,21274.0,20473.775062537152 +7407,21289.7,20480.546223843896 +7408,21318.7,20487.501856841045 +7409,21310.6,20494.332546825768 +7410,21282.1,20500.87003606373 +7411,21321.5,20507.680243233324 +7412,21341.5,20514.599909264583 +7413,21325.0,20521.325221220894 +7414,21286.8,20527.67770901159 +7415,21289.9,20534.003205202367 +7416,21314.7,20540.482016777452 +7417,21184.6,20545.82739423158 +7418,21229.9,20551.504345316796 +7419,21226.9,20557.10928850919 +7420,21219.7,20562.60796661285 +7421,21145.3,20567.443585147183 +7422,21164.9,20572.401729668785 +7423,21185.3,20577.48802236863 +7424,21243.6,20583.015922597937 +7425,21230.2,20588.38674481704 +7426,21254.9,20593.917975150507 +7427,21247.1,20599.3385728671 +7428,21218.9,20604.48016147401 +7429,21210.5,20609.509371752236 +7430,21215.0,20614.53419024392 +7431,21233.7,20619.672495719075 +7432,21248.8,20624.893470858333 +7433,21237.0,20629.973193091875 +7434,21212.4,20634.80661057659 +7435,21186.7,20639.386638704585 +7436,21197.4,20644.017454980898 +7437,21117.3,20647.945110956163 +7438,21128.5,20651.933118334124 +7439,20900.0,20653.991764655002 +7440,20894.4,20655.986853745002 +7441,20979.2,20658.669120518905 +7442,20977.4,20661.31419005817 +7443,20907.6,20663.358055700843 +7444,20885.6,20665.2023871888 +7445,20851.6,20666.74925534491 +7446,20851.5,20668.282456545367 +7447,20628.1,20667.948992175698 +7448,20708.0,20668.281365684616 +7449,20673.2,20668.322184226654 +7450,20749.8,20668.998348672907 +7451,20724.4,20669.45811341421 +7452,20753.8,20670.158046082972 +7453,20712.8,20670.51192121921 +7454,20648.6,20670.33007954934 +7455,20756.2,20671.042692997064 +7456,20650.0,20670.868064839415 +7457,20696.2,20671.078288367717 +7458,20703.0,20671.343198837694 +7459,20836.4,20672.712964822444 +7460,20792.1,20673.70372859985 +7461,20673.4,20673.701208030554 +7462,20450.0,20671.844766470134 +7463,20578.6,20671.070950980753 +7464,20505.4,20669.696088317014 +7465,20618.3,20669.269564762515 +7466,20435.4,20667.328738498927 +7467,20140.6,20662.957545648314 +7468,19626.0,20654.352088837957 +7469,19690.9,20646.356635818556 +7470,19771.9,20639.099734276493 +7471,19785.6,20632.0167489298 +7472,19721.0,20624.45644395943 +7473,19765.7,20617.32983446599 +7474,19681.1,20609.56029227125 +7475,19689.0,20601.920787771072 +7476,19712.6,20594.540532270898 +7477,19547.1,20585.848079720934 +7478,19404.8,20576.046850843584 +7479,19513.8,20567.231524280567 +7480,20363.2,20565.53831661019 +7481,19327.7,20555.2657994599 +7482,18584.5,20538.910896559817 +7483,18238.0,20519.81620032281 +7484,18135.8,20500.031833515153 +7485,18691.7,20485.024930332456 +7486,18448.6,20468.125138379488 +7487,18534.0,20452.074307355593 +7488,18322.4,20434.400661651398 +7489,18310.7,20416.776589770474 +7490,18116.8,20397.689647116775 +7491,18303.3,20380.308820169746 +7492,18245.6,20362.593394276224 +7493,18386.2,20346.191789344473 +7494,18288.8,20329.117998561534 +7495,18224.9,20311.65560853198 +7496,18139.3,20293.62776115827 +7497,17755.1,20272.56114073372 +7498,17677.0,20251.0212142546 +7499,17800.4,20230.684108742116 +7500,17603.4,20208.8809211177 +7501,17570.3,20186.98398401299 +7502,17633.7,20165.794905307488 +7503,17098.4,20140.339345927343 +7504,17068.9,20114.850222724628 +7505,16941.6,20088.516195980024 +7506,16587.3,20059.4604599138 +7507,16764.5,20032.116389707044 +7508,16216.8,20000.454013028975 +7509,15701.8,19964.78053574243 +7510,15848.3,19930.618871545397 +7511,15905.0,19897.211246055394 +7512,16237.9,19866.843517872363 +7513,16114.9,19835.707057143132 +7514,16194.4,19805.488741316218 +7515,16351.7,19776.826594085378 +7516,16467.9,19749.36662235023 +7517,16689.4,19723.972708471803 +7518,16674.0,19698.661731638014 +7519,16739.5,19674.104372869235 +7520,16827.9,19650.484419567416 +7521,16684.1,19625.86712147972 +7522,16396.4,19599.06656445499 +7523,16388.1,19572.41953902383 +7524,16665.9,19548.29904492405 +7525,17649.3,19532.539716750405 +7526,17621.6,19516.681295864513 +7527,17541.6,19500.29057971626 +7528,17774.1,19485.965346689572 +7529,17179.7,19466.826215181776 +7530,17345.8,19449.224337877364 +7531,17438.6,19432.538658724858 +7532,18101.2,19421.49020512548 +7533,17848.3,19408.434684750995 +7534,17812.3,19395.188753757215 +7535,17588.0,19380.191336713586 +7536,17543.2,19364.94659533007 +7537,17259.0,19347.469860099118 +7538,17194.7,19329.6045500568 +7539,17052.3,19310.70575711027 +7540,17077.4,19292.17209937492 +7541,17352.1,19276.07191597762 +7542,17261.3,19259.35181708984 +7543,17397.3,19243.899104914817 +7544,17382.7,19228.453469189382 +7545,17382.9,19213.137672764573 +7546,17325.7,19197.474289588103 +7547,17339.9,19182.058735317663 +7548,17394.1,19167.220903489302 +7549,17295.0,19151.68380055578 +7550,16904.2,19133.032482708844 +7551,16864.8,19114.208976628273 +7552,16827.9,19095.235458150033 +7553,16899.4,19077.01275725252 +7554,16831.1,19058.374477109348 +7555,16842.6,19039.986307174833 +7556,16619.9,19019.90260338085 +7557,16796.3,19001.449469742834 +7558,16915.3,18984.13702601053 +7559,17058.3,18968.154976002144 +7560,16896.7,18950.964478275986 +7561,16884.6,18933.816225344235 +7562,16882.0,18916.788704801962 +7563,16899.0,18900.04357032228 +7564,16757.5,18882.26312575529 +7565,16695.8,18864.118203549853 +7566,16750.3,18846.576143769358 +7567,16811.3,18829.68588531484 +7568,16830.5,18813.095131079863 +7569,16852.4,18796.823802191233 +7570,16834.3,18780.537297608735 +7571,16859.6,18764.595909246837 +7572,16855.7,18748.754449419062 +7573,16833.1,18732.85690212098 +7574,16892.0,18717.58008135649 +7575,16925.4,18702.707217610794 +7576,16883.0,18687.605912900333 +7577,16884.5,18672.642378353445 +7578,16861.9,18657.615470649267 +7579,16885.4,18642.908288320228 +7580,16809.9,18627.696601280226 +7581,16812.0,18612.62857969284 +7582,16828.8,18597.82502301489 +7583,16806.9,18582.96257469112 +7584,16833.6,18568.445042950945 +7585,16869.9,18554.349233465873 +7586,16887.6,18540.517289619685 +7587,16906.1,18526.953660660187 +7588,16855.5,18513.082675924416 +7589,16850.8,18499.28779894579 +7590,16675.0,18484.148481112214 +7591,16765.5,18469.88583811543 +7592,16643.7,18454.730768919453 +7593,16534.1,18438.791924364108 +7594,16578.5,18423.353817107974 +7595,16651.8,18408.65212567969 +7596,16602.5,18393.663311358698 +7597,16662.8,18379.299300476054 +7598,16626.8,18364.755737816504 +7599,16568.4,18349.8482213201 +7600,16578.1,18335.14491657885 +7601,16546.3,18320.29973054915 +7602,16521.1,18305.36861245331 +7603,16547.6,18290.781321063656 +7604,16480.0,18275.75409018346 +7605,16382.9,18260.045757484844 +7606,16434.0,18244.891850783722 +7607,16321.2,18228.927603059376 +7608,16254.0,18212.538162370085 +7609,16081.1,18194.84987886494 +7610,16168.1,18178.030377795523 +7611,16130.0,18161.03427507523 +7612,15978.8,18142.924447066307 +7613,15854.8,18123.93586244335 +7614,16628.1,18111.52228682142 +7615,16813.0,18100.746168258585 +7616,16754.4,18089.573171011627 +7617,16744.1,18078.4074185551 +7618,16774.3,18067.584950351324 +7619,16740.8,18056.57428686293 +7620,16814.9,18046.269935934608 +7621,16716.3,18035.232841030585 +7622,16597.1,18023.29812865689 +7623,16582.7,18011.34295746472 +7624,16462.4,17998.488659062525 +7625,16573.7,17986.664686788146 +7626,16604.7,17975.196100175795 +7627,16245.0,17960.837626315417 +7628,16265.7,17946.770094146825 +7629,16396.6,17933.90561203772 +7630,16398.0,17921.159507373508 +7631,16605.2,17910.238681586176 +7632,16551.7,17898.964501656003 +7633,16705.0,17889.056082555122 +7634,16750.0,17879.60333498205 +7635,16808.8,17870.71700025191 +7636,16758.1,17861.483664150233 +7637,16647.8,17851.411600547326 +7638,16740.9,17842.19573664237 +7639,16834.9,17833.83643592335 +7640,16911.1,17826.178872139753 +7641,16788.0,17817.56327983984 +7642,16873.0,17809.724580422084 +7643,16774.5,17801.133505065885 +7644,16826.0,17793.04111083297 +7645,17002.6,17786.481433564648 +7646,16926.6,17779.34548805789 +7647,16978.0,17772.69531803251 +7648,17000.7,17766.288717882864 +7649,16987.2,17759.823251344416 +7650,16723.3,17751.221398636164 +7651,16847.4,17743.720806116362 +7652,16827.6,17736.11814382494 +7653,16896.3,17729.148698647972 +7654,16865.9,17721.984809032638 +7655,16888.3,17715.066262899585 +7656,16777.6,17707.28645988797 +7657,16848.8,17700.16209092625 +7658,16934.1,17693.804729175823 +7659,16959.8,17687.713403622496 +7660,16899.6,17681.17304342646 +7661,16911.7,17674.787375016283 +7662,16859.5,17668.0215046842 +7663,16814.0,17660.934189292628 +7664,16781.0,17653.631830875263 +7665,16736.1,17646.017458834805 +7666,16722.3,17638.351753782237 +7667,16695.2,17630.52476827367 +7668,16648.5,17622.375185134468 +7669,16505.0,17613.10236202132 +7670,16478.3,17603.68491503359 +7671,16424.1,17593.895828601777 +7672,16492.0,17584.751464878944 +7673,16565.9,17576.29626600028 +7674,16539.8,17567.694637236797 +7675,16548.0,17559.232441077158 +7676,16573.8,17551.05457849561 +7677,16540.1,17542.66491394378 +7678,16659.1,17535.332425031385 +7679,16650.6,17527.990247230297 +7680,16691.3,17521.046759701418 +7681,16683.0,17514.09201480763 +7682,16567.7,17506.23813916607 +7683,16517.4,17498.03201352984 +7684,16495.3,17489.710586031666 +7685,16570.8,17482.084772039703 +7686,16556.6,17474.404400487507 +7687,16602.0,17467.164529944042 +7688,16557.4,17459.614616832474 +7689,16511.2,17451.743956111874 +7690,16520.0,17444.011641123394 +7691,16587.8,17436.90615032569 +7692,16571.4,17429.723526671532 +7693,16485.7,17421.88930653318 +7694,16498.4,17414.225494860708 +7695,16526.8,17406.860967932404 +7696,16643.6,17400.52685201595 +7697,16685.4,17394.59218934362 +7698,16691.2,17388.754909764004 +7699,16614.7,17382.331217566792 +7700,16684.2,17376.537597503997 +7701,16681.0,17370.76550125915 +7702,16700.2,17365.200642327538 +7703,16682.1,17359.53175732897 +7704,16938.6,17356.038547724576 +7705,16881.2,17352.097978863792 +7706,16907.9,17348.411688582764 +7707,16845.6,17344.238977474193 +7708,16799.3,17339.716662308434 +7709,16798.5,17335.22523772496 +7710,16786.0,17330.667351934713 +7711,16739.8,17325.76388843318 +7712,16730.6,17320.824769027095 +7713,16731.5,17315.934107043468 +7714,16760.0,17311.320545989165 +7715,16744.0,17306.612491665604 +7716,16767.5,17302.138529079166 +7717,16743.3,17297.500864937432 +7718,16690.5,17292.46351336119 +7719,16686.8,17287.437260138275 +7720,16646.5,17282.11827872634 +7721,16588.6,17276.362940313673 +7722,16580.3,17270.586484377458 +7723,16648.0,17265.419791561046 +7724,16631.3,17260.157386651827 +7725,16645.2,17255.05400584974 +7726,16659.2,17250.109159328164 +7727,16689.7,17245.4584609105 +7728,16657.0,17240.574988205848 +7729,16657.7,17235.737851374262 +7730,16617.4,17230.606416923027 +7731,16622.2,17225.55740101495 +7732,16626.8,17220.58845992769 +7733,16594.2,17215.39021544696 +7734,16603.9,17210.31560784989 +7735,16592.2,17205.18601774325 +7736,16636.0,17200.462482326297 +7737,16626.2,17195.69681857255 +7738,16630.5,17191.0063885429 +7739,16669.3,17186.676874945035 +7740,16644.1,17182.174162289888 +7741,16639.3,17177.668982519845 +7742,16641.9,17173.222766897274 +7743,16648.5,17168.868221113895 +7744,16647.0,17164.53736450714 +7745,16607.0,17159.91049841165 +7746,16614.8,17155.38675983562 +7747,16619.7,17150.941226558978 +7748,16624.8,17146.574909326126 +7749,16671.0,17142.62822958068 +7750,16728.6,17139.192310663 +7751,16692.9,17135.488640035088 +7752,16714.2,17131.99246874849 +7753,16674.7,17128.197510501614 +7754,16676.6,17124.449813319028 +7755,16672.8,17120.701682088165 +7756,16676.8,17117.01785070154 +7757,16679.7,17113.388656919786 +7758,16680.6,17109.797049808418 +7759,16710.2,17106.480891718722 +7760,16605.5,17102.323373945124 +7761,16619.9,17098.319860468404 +7762,16616.1,17094.318035900204 +7763,16517.5,17089.531164232983 +7764,16517.1,17084.780698139763 +7765,16573.9,17080.54102429628 +7766,16540.1,17076.056036542785 +7767,16570.3,17071.85889101131 +7768,16575.8,17067.742219716612 +7769,16538.3,17063.34850834967 +7770,16579.9,17059.336487533492 +7771,16582.0,17055.375188881764 +7772,16533.4,17051.043444575695 +7773,16277.4,17044.62316702735 +7774,16287.7,17038.34164696903 +7775,16272.6,17031.986944504555 +7776,16261.5,17025.592861977548 +7777,16204.3,17018.777153579395 +7778,16005.2,17010.365724918986 +7779,16067.2,17002.538623467375 +7780,16142.7,16995.40303323113 +7781,16221.3,16988.978941669047 +7782,16085.0,16981.477041738184 +7783,16032.2,16973.599223964422 +7784,16027.3,16965.746118371357 +7785,16102.3,16958.580590418067 +7786,16124.1,16951.655440290117 +7787,16077.6,16944.401868171528 +7788,16108.6,16937.465753082968 +7789,16167.7,16931.07765554701 +7790,16158.6,16924.667052596415 +7791,16122.7,16918.0117243591 +7792,16021.8,16910.574282663176 +7793,16005.0,16903.059143387964 +7794,15965.1,16895.275250081842 +7795,15712.8,16885.462177467056 +7796,15774.8,16876.245063961105 +7797,15637.9,16865.968341438605 +7798,15729.1,16856.53374939347 +7799,15773.0,16847.541768070703 +7800,15859.3,16839.34059157219 +7801,15807.5,16830.777599111007 +7802,15893.4,16822.99853189847 +7803,15841.0,16814.849166488522 +7804,15814.1,16806.54419415252 +7805,15795.3,16798.152126151253 +7806,15679.2,16788.866216390663 +7807,15754.9,16780.285583889494 +7808,15723.1,16771.512259541865 +7809,15680.8,16762.46070552077 +7810,15791.1,16754.39962082765 +7811,15742.3,16746.00045384983 +7812,15828.2,16738.383852573068 +7813,16162.4,16733.60390358906 +7814,16111.6,16728.44204546799 +7815,16232.8,16724.328833472406 +7816,16160.6,16719.650585891723 +7817,16073.4,16714.287510490132 +7818,16134.0,16709.47184650266 +7819,16160.5,16704.916063544133 +7820,16128.2,16700.130038120533 +7821,16142.6,16695.50323282493 +7822,16177.8,16691.206940436343 +7823,16220.6,16687.30148864849 +7824,16174.6,16683.04670451033 +7825,16215.8,16679.169138497797 +7826,16503.9,16677.714622825617 +7827,16577.9,16676.886285706732 +7828,16501.7,16675.43245760958 +7829,16455.7,16673.6089517373 +7830,16492.2,16672.103483258154 +7831,16572.9,16671.280217836924 +7832,16531.4,16670.11938615363 +7833,16554.2,16669.157399546548 +7834,16600.9,16668.590948098026 +7835,16577.0,16667.830857242443 +7836,16503.9,16666.47043519064 +7837,16388.9,16664.16694610192 +7838,16456.3,16662.44190920481 +7839,16415.9,16660.39591825705 +7840,16327.1,16657.629977026703 +7841,16384.2,16655.360848586646 +7842,16521.0,16654.245820797547 +7843,16466.9,16652.691083695492 +7844,16609.2,16652.330161839098 +7845,16478.4,16650.886758006407 +7846,16504.8,16649.67441976569 +7847,16595.8,16649.22732914523 +7848,16556.3,16648.456147990495 +7849,16737.5,16649.195101119203 +7850,16683.7,16649.481448827755 +7851,16703.2,16649.927245932922 +7852,16688.3,16650.245692024768 +7853,16645.6,16650.207138563983 +7854,16704.8,16650.660191355983 +7855,16613.8,16650.354297651782 +7856,16582.5,16649.791191447206 +7857,16593.6,16649.324874505735 +7858,16548.7,16648.489813306518 +7859,16567.1,16647.814379171195 +7860,16532.2,16646.854923742387 +7861,16578.4,16646.28683308892 +7862,16524.6,16645.27698385167 +7863,16549.0,16644.47800473257 +7864,16612.8,16644.21511672649 +7865,16576.8,16643.655655176895 +7866,16575.4,16643.089218204474 +7867,16569.0,16642.47436992062 +7868,16558.7,16641.779146933728 +7869,16546.3,16640.98678886789 +7870,16604.7,16640.68565369056 +7871,16592.4,16640.284942871556 +7872,16519.4,16639.281748324905 +7873,16517.4,16638.270281533827 +7874,16519.3,16637.28297629288 +7875,16480.3,16635.980213004146 +7876,16381.9,16633.871663518636 +7877,16353.0,16631.540778344206 +7878,16417.9,16629.767825826824 +7879,16453.8,16628.30751191955 +7880,16440.2,16626.746453729345 +7881,16432.8,16625.136939590513 +7882,16468.9,16623.84036747773 +7883,16523.5,16623.007667332684 +7884,16497.5,16621.96610992743 +7885,16483.6,16620.817843455003 +7886,16531.4,16620.07578666285 +7887,16505.4,16619.12412038349 +7888,16477.9,16617.952135981966 +7889,16502.5,16616.994026969667 +7890,16488.0,16615.9235371193 +7891,16516.0,16615.094296147356 +7892,16518.9,16614.29600323327 +7893,16493.6,16613.29437665042 +7894,16521.4,16612.531767715565 +7895,16516.1,16611.73150408307 +7896,16570.6,16611.390163800224 +7897,16624.0,16611.494809743792 +7898,16619.5,16611.56124285795 +7899,16615.0,16611.58978026162 +7900,16621.9,16611.675342251154 +7901,16614.6,16611.699613269815 +7902,16550.6,16611.192562537286 +7903,16576.9,16610.907976956063 +7904,16561.7,16610.49961200207 +7905,16581.3,16610.257291570517 +7906,16587.6,16610.06926425458 +7907,16572.8,16609.759975754543 +7908,16573.9,16609.462382594753 +7909,16590.4,16609.304188548325 +7910,16561.8,16608.90996291722 +7911,16506.4,16608.05925783077 +7912,16501.0,16607.17079925956 +7913,16511.2,16606.374361091428 +7914,16470.9,16605.250092534654 +7915,16480.7,16604.216481808224 +7916,16491.7,16603.28273507123 +7917,16506.7,16602.481218597608 +7918,16418.9,16600.957723007585 +7919,16449.2,16599.69832281665 +7920,16456.5,16598.509954992445 +7921,16475.4,16597.488295614916 +7922,16520.1,16596.84606909529 +7923,16520.0,16596.208342380807 +7924,16556.6,16595.879642444037 +7925,16530.3,16595.335413046163 +7926,16547.1,16594.93511916196 +7927,16553.9,16594.59457875398 +7928,16522.6,16593.99711337013 +7929,16513.8,16593.33157715959 +7930,16535.1,16592.84832755661 +7931,16544.4,16592.44626674701 +7932,16551.4,16592.10563382795 +7933,16530.5,16591.594383754687 +7934,16531.1,16591.092355673736 +7935,16566.2,16590.88578010798 +7936,16530.8,16590.38714292866 +7937,16523.7,16589.833722655392 +7938,16556.1,16589.55377474954 +7939,16546.9,16589.199801515104 +7940,16550.4,16588.877811461036 +7941,16569.5,16588.716999747667 +7942,16565.4,16588.523497675073 +7943,16422.6,16587.146539188143 +7944,16436.5,16585.896360439692 +7945,16135.1,16582.155311805338 +7946,16109.3,16578.231201333925 +7947,16168.9,16574.83426190377 +7948,16201.3,16571.734392510378 +7949,16153.0,16568.259418298676 +7950,16205.7,16565.250626445573 +7951,16215.1,16562.344812118223 +7952,16225.9,16559.552738988612 +7953,16208.5,16556.639438250117 +7954,16188.1,16553.58101967543 +7955,16205.0,16550.688231130403 +7956,16178.1,16547.5962126148 +7957,16170.4,16544.465953588955 +7958,16209.5,16541.686153144234 +7959,16136.7,16538.32527220528 +7960,16303.8,16536.379004386148 +7961,16202.2,16533.605734640205 +7962,16154.0,16530.455479580953 +7963,16215.1,16527.83842165912 +7964,16251.4,16525.544326873565 +7965,16192.4,16522.779643662998 +7966,16211.2,16520.193920479072 +7967,16204.5,16517.57405391908 +7968,16169.3,16514.68381280772 +7969,16242.9,16512.428345481516 +7970,16255.9,16510.29947954391 +7971,16279.1,16508.38081166388 +7972,16422.9,16507.67142733472 +7973,16476.9,16507.416062792523 +7974,16455.5,16506.98522409715 +7975,16455.9,16506.561280328708 +7976,16489.1,16506.416373438013 +7977,16507.5,16506.425366189567 +7978,16495.6,16506.335529125754 +7979,16488.0,16506.183367058322 +7980,16480.7,16505.971886833773 +7981,16407.0,16505.150543374573 +7982,16355.7,16503.910289902586 +7983,16384.8,16502.921822766464 +7984,16403.9,16502.100064901184 +7985,16372.2,16501.02205606383 +7986,16413.3,16500.29407219608 +7987,16418.2,16499.612793588643 +7988,16486.6,16499.504803600357 +7989,16455.9,16499.142938010315 +7990,16489.4,16499.062083752968 +7991,16433.2,16498.515510443816 +7992,16839.4,16501.344427369593 +7993,16874.9,16504.44447361549 +7994,16952.1,16508.15945723694 +7995,16836.7,16510.885934770242 +7996,16871.8,16513.881072241027 +7997,16858.0,16516.736830977618 +7998,16854.1,16519.53652532635 +7999,16874.1,16522.47896080082 +8000,16883.7,16525.47664577343 +8001,16870.8,16528.342399750414 +8002,16858.4,16531.081466972402 +8003,16871.4,16533.9056871635 +8004,16771.2,16535.87493457293 +8005,16802.9,16538.09091021963 +8006,16838.5,16540.583931711586 +8007,16856.8,16543.20813144842 +8008,16853.4,16545.782337826444 +8009,16782.1,16547.743480251123 +8010,16913.4,16550.777974190947 +8011,17051.5,16554.933343699737 +8012,17090.9,16559.381199768617 +8013,17095.8,16563.83280806929 +8014,17139.2,16568.607639537597 +8015,17153.3,16573.45985829662 +8016,17153.7,16578.275129182126 +8017,17107.9,16582.670356325845 +8018,17118.3,16587.11541560945 +8019,17143.8,16591.73520469153 +8020,17133.0,16596.227028718986 +8021,17105.3,16600.451700679823 +8022,17114.0,16604.71351229244 +8023,17062.6,16608.513400157233 +8024,17106.6,16612.646898911116 +8025,17106.1,16616.741945393183 +8026,17087.9,16620.651970742616 +8027,17096.9,16624.604236545583 +8028,17130.0,16628.798392258897 +8029,17082.0,16632.55940145177 +8030,17099.7,16636.436086916903 +8031,16970.5,16639.2084015483 +8032,16974.4,16641.9900745645 +8033,16960.4,16644.63248058471 +8034,16925.9,16646.966650870316 +8035,16945.4,16649.443276174297 +8036,16910.6,16651.61055189069 +8037,16917.5,16653.81710332728 +8038,16955.2,16656.318206204232 +8039,16970.5,16658.92552399507 +8040,17028.3,16661.99087234366 +8041,16917.0,16664.10713066446 +8042,16913.1,16666.173461530314 +8043,16893.9,16668.063308322595 +8044,16921.0,16670.162368004563 +8045,16927.1,16672.29463051075 +8046,16900.9,16674.19177050651 +8047,16947.1,16676.45656909152 +8048,16955.1,16678.76896270902 +8049,16953.8,16681.051377956246 +8050,16960.7,16683.372113408892 +8051,16987.1,16685.892676783093 +8052,17021.3,16688.676140046304 +8053,16881.5,16690.27633805422 +8054,16902.0,16692.033380891946 +8055,16928.9,16693.99907897583 +8056,16926.0,16695.924397822502 +8057,16926.4,16697.837058421486 +8058,16945.3,16699.890692791432 +8059,16979.3,16702.209442228846 +8060,17020.5,16704.850857646034 +8061,17003.0,16707.325124387564 +8062,17035.4,16710.04773746319 +8063,17083.0,16713.142776986315 +8064,17041.2,16715.865243567343 +8065,17041.6,16718.568436566784 +8066,17021.3,16721.08073169901 +8067,17012.3,16723.4974891123 +8068,17007.1,16725.851036920496 +8069,17004.5,16728.163476448128 +8070,16970.0,16730.170418552294 +8071,16954.7,16732.033734580906 +8072,16939.6,16733.75627620264 +8073,16970.1,16735.717634906352 +8074,16955.3,16737.539895197588 +8075,16938.5,16739.207613909643 +8076,16957.8,16741.02165860749 +8077,16945.4,16742.717744428173 +8078,16938.9,16744.345812939144 +8079,16953.8,16746.084021960396 +8080,16946.9,16747.75054459973 +8081,16963.9,16749.54431601384 +8082,16961.8,16751.30577397223 +8083,16945.3,16752.915684561674 +8084,16927.6,16754.36534693046 +8085,16941.5,16755.918331603236 +8086,16894.7,16757.070046693665 +8087,16879.5,16758.086062903676 +8088,16940.4,16759.599041634767 +8089,16959.4,16761.25714087431 +8090,16965.4,16762.951272485312 +8091,16951.8,16764.518481842282 +8092,16999.3,16766.466876183844 +8093,17010.1,16768.48872783377 +8094,16993.7,16770.357701046767 +8095,16987.9,16772.163031328535 +8096,17006.0,16774.103587085145 +8097,16992.1,16775.91268594751 +8098,16994.7,16777.728348304794 +8099,16938.4,16779.06172300766 +8100,16950.9,16780.487766800128 +8101,16939.6,16781.808200270665 +8102,16998.8,16783.608962094146 +8103,17022.2,16785.58897070747 +8104,17006.0,16787.41810788002 +8105,17044.3,16789.549907814628 +8106,17055.5,16791.756962521562 +8107,17077.6,16794.129103911426 +8108,17080.6,16796.50645574619 +8109,17101.8,16799.040012130037 +8110,17094.7,16801.49362198788 +8111,17099.5,16803.966703963084 +8112,17205.8,16807.30142011277 +8113,17256.9,16811.032528659554 +8114,17235.0,16814.550930911344 +8115,17190.1,16817.667520696312 +8116,17346.0,16822.052022599248 +8117,17329.7,16826.264868884733 +8118,17298.9,16830.187152130504 +8119,17303.7,16834.116719332742 +8120,17349.1,16838.390439504255 +8121,17294.2,16842.17309145858 +8122,17289.0,16845.881198583404 +8123,17302.2,16849.66807660346 +8124,17247.0,16852.965436963594 +8125,17223.5,16856.04041259045 +8126,17198.3,16858.88074111667 +8127,17073.8,16860.664303431055 +8128,17035.7,16862.11688182582 +8129,17047.3,16863.65367118826 +8130,17040.0,16865.117126199148 +8131,16919.5,16865.56843635517 +8132,16930.7,16866.108947256787 +8133,16964.8,16866.927960142624 +8134,16937.3,16867.51196047339 +8135,16958.3,16868.265388187305 +8136,17035.0,16869.649077911894 +8137,17022.4,16870.91672041885 +8138,17024.9,16872.19458995894 +8139,16995.4,16873.21704149455 +8140,16994.9,16874.226858577582 +8141,16967.6,16875.001739419262 +8142,16993.7,16875.986787224912 +8143,16993.6,16876.962830484455 +8144,17024.4,16878.18637545969 +8145,16964.3,16878.901011347993 +8146,16958.7,16879.563243618963 +8147,16974.6,16880.35193039391 +8148,16986.6,16881.233657112636 +8149,16976.3,16882.022589418753 +8150,16962.2,16882.68796212067 +8151,16974.9,16883.4532072483 +8152,16977.1,16884.23035905537 +8153,16952.4,16884.79608221674 +8154,16965.3,16885.464164521996 +8155,16964.1,16886.11674406953 +8156,16984.5,16886.933202624972 +8157,16986.9,16887.76280260319 +8158,16967.2,16888.422032457103 +8159,17078.6,16890.000272851652 +8160,17056.5,16891.382013325914 +8161,17035.1,16892.57469371325 +8162,17046.7,16893.8537418982 +8163,17017.4,16894.879022048426 +8164,17030.7,16896.006167093667 +8165,16997.4,16896.847609690398 +8166,16945.7,16897.253023717865 +8167,16755.4,16896.075820201535 +8168,16785.8,16895.16066816667 +8169,16826.0,16894.586720713003 +8170,16817.7,16893.948656640696 +8171,16787.1,16893.061945797202 +8172,16785.7,16892.17097529266 +8173,16846.3,16891.790303298534 +8174,16827.5,16891.25677381058 +8175,16829.1,16890.740949961528 +8176,16800.4,16889.991232534463 +8177,16798.7,16889.233628944967 +8178,16775.2,16888.287291775297 +8179,16807.5,16887.616857818655 +8180,16811.7,16886.986842401075 +8181,16821.1,16886.440063625963 +8182,16833.9,16886.00404650044 +8183,16828.0,16885.522685118693 +8184,16804.6,16884.851127565842 +8185,16818.9,16884.303815303883 +8186,16835.6,16883.89963426402 +8187,16825.6,16883.41581987179 +8188,16805.5,16882.7692155575 +8189,16810.2,16882.166981403494 +8190,16794.4,16881.43862471135 +8191,16816.7,16880.901374713747 +8192,16825.4,16880.44078239247 +8193,16792.2,16879.708493741913 +8194,16813.3,16879.15738591003 +8195,16842.0,16878.849025860985 +8196,16818.8,16878.350693696164 +8197,16848.9,16878.1062895991 +8198,16890.6,16878.209971843095 +8199,16907.7,16878.45470236722 +8200,16936.5,16878.936406082015 +8201,16956.9,16879.58340686142 +8202,17239.9,16882.57358605759 +8203,17241.9,16885.55554799902 +8204,17186.2,16888.050522704427 +8205,17174.6,16890.428526665386 +8206,17194.9,16892.955260883933 +8207,17216.0,16895.636130088216 +8208,17197.1,16898.137904942258 +8209,17226.8,16900.8653912083 +8210,17207.3,16903.408417007402 +8211,17194.0,16905.81996541398 +8212,17214.9,16908.384944954112 +8213,17210.1,16910.888804332084 +8214,17208.0,16913.354457408168 +8215,17203.7,16915.763963985693 +8216,17196.3,16918.09206386963 +8217,17231.8,16920.69544923171 +8218,17207.7,16923.07722973601 +8219,17231.9,16925.640074302515 +8220,17250.4,16928.335177420337 +8221,17143.3,16930.119117856684 +8222,17146.8,16931.917299451237 +8223,17170.4,16933.89640899936 +8224,17148.5,16935.677351663267 +8225,17156.5,16937.509904761497 +8226,17138.2,16939.175382730282 +8227,17134.9,16940.79965341302 +8228,17092.4,16942.05774757557 +8229,17105.2,16943.411625189052 +8230,17123.2,16944.90364489703 +8231,17122.0,16946.373324192493 +8232,17145.3,16948.02416797513 +8233,17143.9,16949.6496935521 +8234,17133.3,16951.173762485276 +8235,17140.0,16952.74078520324 +8236,17143.0,16954.319699848857 +8237,17125.3,16955.738623501562 +8238,17140.4,16957.271083057563 +8239,17144.6,16958.82567987866 +8240,17133.2,16960.272769672196 +8241,17137.0,16961.739385691515 +8242,17155.5,16963.34735759449 +8243,17159.9,16964.97849985512 +8244,17149.9,16966.513118113584 +8245,17149.7,16968.033341199778 +8246,17157.5,16969.605678617205 +8247,17200.3,16971.52015431333 +8248,17180.0,16973.25027751405 +8249,17158.6,16974.788449484888 +8250,17177.0,16976.46655363854 +8251,17166.3,16978.041934936147 +8252,17163.4,16979.58017614 +8253,17116.1,16980.71312073635 +8254,17126.1,16981.91965085472 +8255,17120.0,16983.065545868376 +8256,17134.4,16984.321433454534 +8257,17156.6,16985.751131102214 +8258,17138.3,16987.017096819207 +8259,17147.7,16988.350564895398 +8260,17161.0,16989.783340290454 +8261,17164.4,16991.232441200907 +8262,17163.1,16992.65872799592 +8263,17173.4,16994.158655564417 +8264,17166.9,16995.592193692515 +8265,17158.7,16996.945785446107 +8266,17156.7,16998.271546562737 +8267,17160.8,16999.62033040869 +8268,17153.6,17000.89816999036 +8269,17147.5,17002.114782687535 +8270,17148.6,17003.330427644483 +8271,17145.5,17004.510258120463 +8272,17153.7,17005.748347264693 +8273,17167.9,17007.09400413387 +8274,17197.1,17008.670817377573 +8275,17169.5,17010.005499391038 +8276,17093.9,17010.701719313103 +8277,17105.2,17011.48593741009 +8278,17091.8,17012.152444153573 +8279,17077.3,17012.693087770556 +8280,16943.5,17012.11887127454 +8281,16946.1,17011.570996824128 +8282,16893.7,17010.59281427787 +8283,16909.0,17009.749720383446 +8284,16924.5,17009.04225382425 +8285,16938.1,17008.453521427367 +8286,16931.0,17007.810753614693 +8287,16916.2,17007.050498397974 +8288,16928.2,17006.396137415417 +8289,16953.2,17005.954675694127 +8290,16978.7,17005.72849581285 +8291,16977.8,17005.496724063367 +8292,16952.9,17005.060236726742 +8293,16961.1,17004.695421484197 +8294,17007.5,17004.7186959947 +8295,16999.9,17004.67870681632 +8296,16992.0,17004.573489332368 +8297,17000.0,17004.53553506405 +8298,17016.7,17004.63648498053 +8299,17035.7,17004.894273486916 +8300,17114.4,17005.803034702793 +8301,17167.5,17007.14491823223 +8302,17149.2,17008.32379857885 +8303,17200.0,17009.914472449567 +8304,17148.5,17011.06455981513 +8305,17135.6,17012.098048945296 +8306,17144.0,17013.19267094575 +8307,17143.2,17014.27156994205 +8308,17161.2,17015.490893013073 +8309,17179.2,17016.849474813796 +8310,17143.7,17017.902176267624 +8311,17157.0,17019.056515053784 +8312,17149.5,17020.139033601055 +8313,17430.6,17023.545348674907 +8314,17385.0,17026.544972337357 +8315,17436.8,17029.94957837605 +8316,17416.8,17033.159955318988 +8317,17896.0,17040.320453615095 +8318,17869.0,17047.197462298787 +8319,17749.6,17053.0265290017 +8320,17754.0,17058.843736229905 +8321,17641.9,17063.682377423018 +8322,17714.1,17069.080034041915 +8323,17740.6,17074.6528138424 +8324,17747.5,17080.2366079184 +8325,17767.3,17085.938378807045 +8326,17774.9,17091.65590263437 +8327,17777.1,17097.34423539259 +8328,17817.8,17103.323121405927 +8329,17781.4,17108.950315419155 +8330,17795.1,17114.64450367294 +8331,17782.4,17120.186043061545 +8332,17772.1,17125.596117392986 +8333,17789.4,17131.104863306737 +8334,17787.1,17136.548806349834 +8335,17807.1,17142.1135465461 +8336,17840.5,17147.90928474904 +8337,17815.3,17153.447796908797 +8338,17827.0,17159.03744174773 +8339,17823.8,17164.55414347596 +8340,17910.0,17170.74041614421 +8341,17889.9,17176.708545470818 +8342,18055.8,17184.003910238695 +8343,18054.8,17191.230433805176 +8344,18096.9,17198.746363815095 +8345,18089.3,17206.136850422437 +8346,18215.0,17214.509158717687 +8347,17789.5,17219.280866944096 +8348,17762.1,17223.785590039995 +8349,17827.9,17228.798987633025 +8350,17829.7,17233.785718026113 +8351,17803.9,17238.516956880667 +8352,17788.7,17243.0827912634 +8353,17631.6,17246.307000464534 +8354,17714.1,17250.189100045744 +8355,17724.9,17254.128609588934 +8356,17708.0,17257.89517714421 +8357,17713.4,17261.67530015546 +8358,17715.1,17265.438160735084 +8359,17686.0,17268.928300480024 +8360,17621.3,17271.85254694907 +8361,17691.9,17275.338417928746 +8362,17658.5,17278.518182095315 +8363,17712.1,17282.116371455522 +8364,17674.7,17285.374326879126 +8365,17497.8,17287.13719553573 +8366,17448.0,17288.47215656863 +8367,17424.0,17289.596868962253 +8368,17391.7,17290.444197850535 +8369,17392.1,17291.287814465883 +8370,17395.5,17292.152645881106 +8371,17435.1,17293.33893097753 +8372,17410.2,17294.308732380206 +8373,17388.9,17295.093722152986 +8374,17334.7,17295.4224049567 +8375,17351.8,17295.89026881598 +8376,17418.2,17296.90528733203 +8377,17378.7,17297.58408162803 +8378,17393.2,17298.377574726554 +8379,17418.9,17299.377760828407 +8380,17382.0,17300.06342256427 +8381,17402.9,17300.916838144654 +8382,17415.1,17301.864416251337 +8383,17496.6,17303.480479187012 +8384,17215.0,17302.746201351438 +8385,17029.1,17300.475278518647 +8386,16984.1,17297.849757535092 +8387,17018.6,17295.532332161358 +8388,17025.1,17293.288080442177 +8389,16938.9,17290.347100521496 +8390,17019.2,17288.09691711468 +8391,16961.8,17285.389058881363 +8392,16801.7,17281.37504179521 +8393,16788.0,17277.280643108115 +8394,16800.1,17273.320637771118 +8395,16856.5,17269.861545341482 +8396,16863.9,17266.49256986147 +8397,16825.6,17262.833710360548 +8398,16715.1,17258.28820239075 +8399,16624.1,17253.025229756804 +8400,16697.7,17248.416721626043 +8401,16660.8,17243.540234309647 +8402,16652.5,17238.635336099607 +8403,16667.5,17233.89562376683 +8404,16700.0,17229.46495468993 +8405,16681.8,17224.920017306613 +8406,16702.4,17220.583751602826 +8407,16738.0,17216.578907191182 +8408,16712.9,17212.39899924769 +8409,16686.8,17208.03718182655 +8410,16724.9,17204.027744632967 +8411,16700.6,17199.849921026056 +8412,16685.9,17195.584776453226 +8413,16661.4,17191.151707768968 +8414,16643.8,17186.60936994516 +8415,16692.4,17182.508047373 +8416,16686.8,17178.394287643765 +8417,16665.4,17174.137073638423 +8418,16691.5,17170.131786720263 +8419,16684.2,17166.099157784825 +8420,16692.6,17162.16970419325 +8421,16704.3,17158.36995561073 +8422,16732.3,17154.8341053567 +8423,16768.0,17151.623863818466 +8424,16741.2,17148.217856649848 +8425,16707.9,17144.56376655317 +8426,16724.3,17141.076100440696 +8427,16736.5,17137.71862242874 +8428,16756.3,17134.55332265755 +8429,16732.0,17131.212631183214 +8430,16728.0,17127.866468268832 +8431,16739.9,17124.646829528014 +8432,16746.3,17121.50702181409 +8433,16689.6,17117.92273117663 +8434,16698.5,17114.44204461085 +8435,16702.2,17111.02094880495 +8436,16716.0,17107.742766657193 +8437,16704.3,17104.39469390485 +8438,16676.0,17100.839551216843 +8439,16676.4,17097.317231289733 +8440,16686.5,17093.907959660773 +8441,16707.3,17090.69959485031 +8442,16740.0,17087.789224768563 +8443,16745.6,17084.949480164676 +8444,16750.6,17082.174795681985 +8445,16739.0,17079.326872066365 +8446,16753.9,17076.626234123905 +8447,16730.3,17073.75215749217 +8448,16766.9,17071.205666558628 +8449,16778.0,17068.7724245125 +8450,16715.3,17065.839043396212 +8451,16673.6,17062.583947600393 +8452,16702.3,17059.59403932155 +8453,16696.5,17056.580810779462 +8454,16729.5,17053.86644720453 +8455,16714.4,17051.049298265076 +8456,16747.7,17048.53187670271 +8457,16743.6,17046.001321709326 +8458,16760.5,17043.632016134976 +8459,16725.3,17040.990256664976 +8460,16734.4,17038.44593918228 +8461,16701.8,17035.6521969484 +8462,16727.5,17033.094917305676 +8463,16660.2,17030.000353676583 +8464,16627.1,17026.65678227678 +8465,16593.6,17023.062950058717 +8466,16538.1,17019.03836126155 +8467,16549.3,17015.14011759963 +8468,16593.8,17011.643519113324 +8469,16582.2,17008.07967248168 +8470,16407.4,17003.094778934115 +8471,16429.2,16998.332166660803 +8472,16454.8,16993.82152627358 +8473,16571.9,16990.320102819027 +8474,16712.9,16988.017861301858 +8475,16718.0,16985.77704917487 +8476,16783.6,16984.099231339394 +8477,16778.9,16982.396333154004 +8478,16830.6,16981.136612546918 +8479,16796.1,16979.60103899881 +8480,16817.3,16978.254142409613 +8481,16806.6,16976.82962670497 +8482,16769.6,16975.10987876551 +8483,16805.1,16973.699008402313 +8484,16809.6,16972.3371909052 +8485,16786.1,16970.791654051216 +8486,16893.6,16970.151059411786 +8487,16910.9,16969.659349375175 +8488,16825.3,16968.461346475797 +8489,16808.8,16967.136356048613 +8490,16828.9,16965.989166371863 +8491,16894.7,16965.397555032676 +8492,16851.3,16964.450687356057 +8493,16870.0,16963.666864224473 +8494,16862.2,16962.82481555871 +8495,16887.2,16962.197223728348 +8496,16834.5,16961.137495730603 +8497,16859.2,16960.29154140919 +8498,16841.4,16959.304889613264 +8499,16829.8,16958.23016023888 +8500,16786.0,16956.800864303288 +8501,16826.5,16955.719529329817 +8502,16829.0,16954.66791497853 +8503,16809.2,16953.460712364602 +8504,16856.8,16952.658548776515 +8505,16861.3,16951.900386546004 +8506,16853.5,16951.08378582778 +8507,16872.4,16950.43080835203 +8508,16827.2,16949.408146042057 +8509,16837.8,16948.48193736121 +8510,16789.0,16947.15843580635 +8511,16854.6,16946.390316007124 +8512,16773.7,16944.957201351463 +8513,16807.9,16943.819797190867 +8514,16782.1,16942.477724185137 +8515,16746.8,16940.85384265663 +8516,16773.4,16939.46418421135 +8517,16786.4,16938.193942018723 +8518,16758.3,16936.70104623434 +8519,16819.6,16935.72925331953 +8520,16849.0,16935.009508478703 +8521,16809.9,16933.971255296306 +8522,16844.1,16933.22543575028 +8523,16844.5,16932.489125080152 +8524,16827.6,16931.61867590936 +8525,16816.0,16930.659184822976 +8526,16800.9,16929.582345114904 +8527,16820.0,16928.67294806001 +8528,16829.7,16927.851595793956 +8529,16839.7,16927.120047281143 +8530,16815.3,16926.19208008379 +8531,16827.6,16925.373888547827 +8532,16808.3,16924.402321008012 +8533,16775.3,16923.164957348195 +8534,16667.3,16921.041596706305 +8535,16631.5,16918.63876187887 +8536,16655.9,16916.458357215975 +8537,16586.6,16913.720943463148 +8538,16611.2,16911.210396214494 +8539,16658.3,16909.111554752133 +8540,16775.4,16908.00191529361 +8541,16791.6,16907.035924295324 +8542,16795.2,16906.107825338517 +8543,16815.5,16905.35589317803 +8544,16775.2,16904.275761284436 +8545,16793.6,16903.357290236432 +8546,16844.9,16902.87216749588 +8547,16823.5,16902.213477309193 +8548,16832.1,16901.631622725716 +8549,16812.5,16900.89194120932 +8550,16835.1,16900.345949995964 +8551,16842.0,16899.861751240813 +8552,16830.0,16899.28198567035 +8553,16834.4,16898.743545955247 +8554,16851.3,16898.34982358217 +8555,16837.4,16897.844015917588 +8556,16864.7,16897.568961843583 +8557,16833.5,16897.037269214175 +8558,16781.0,16896.074304324433 +8559,16825.9,16895.491944952446 +8560,16819.6,16894.86213628064 +8561,16834.3,16894.359545938063 +8562,16828.8,16893.81548331617 +8563,16838.5,16893.356433662095 +8564,16802.6,16892.603268237515 +8565,16808.0,16891.901166426414 +8566,16784.3,16891.008210688437 +8567,16776.4,16890.057105205546 +8568,16800.0,16889.30974333662 +8569,16805.8,16888.616716420966 +8570,16818.8,16888.03732458345 +8571,16833.3,16887.583072927155 +8572,16822.3,16887.041304687093 +8573,16815.8,16886.450090540315 +8574,16810.9,16885.823118834585 +8575,16840.3,16885.445333616044 +8576,16819.3,16884.896409685618 +8577,16823.4,16884.386066036775 +8578,16819.6,16883.848422335224 +8579,16819.2,16883.311920905056 +8580,16816.2,16882.754975503354 +8581,16809.8,16882.149540022 +8582,16824.0,16881.66697122514 +8583,16830.2,16881.23985943074 +8584,16829.6,16880.81131287945 +8585,16834.8,16880.429476258043 +8586,16835.2,16880.05412790735 +8587,16834.6,16879.676915227625 +8588,16833.0,16879.28955493528 +8589,16817.0,16878.772629168183 +8590,16819.4,16878.279910253925 +8591,16828.9,16877.870118467585 +8592,16835.1,16877.515179725116 +8593,16837.6,16877.183933420347 +8594,16818.4,16876.696099947978 +8595,16824.0,16876.258787915216 +8596,16816.2,16875.760374737496 +8597,16825.2,16875.3407865654 +8598,16823.0,16874.906423191416 +8599,16816.8,16874.424212210575 +8600,16822.0,16873.989156507585 +8601,16824.0,16873.57430873574 +8602,16817.2,16873.106472148724 +8603,16814.9,16872.623430886077 +8604,16810.4,16872.107053866275 +8605,16804.2,16871.543509850788 +8606,16823.6,16871.145638399743 +8607,16784.1,16870.42326795659 +8608,16805.8,16869.886975276455 +8609,16786.8,16869.197456809434 +8610,16771.4,16868.38585965749 +8611,16786.7,16867.707968705978 +8612,16760.1,16866.814956517544 +8613,16823.9,16866.458815799557 +8614,16814.0,16866.023472929854 +8615,16824.4,16865.678049917988 +8616,16822.1,16865.316406350204 +8617,16845.5,16865.151954845223 +8618,16881.2,16865.28513364319 +8619,16887.1,16865.466169878517 +8620,16869.5,16865.49964564716 +8621,16843.5,16865.31707597374 +8622,16844.5,16865.14432015653 +8623,16836.6,16864.90743783158 +8624,16831.5,16864.6301976836 +8625,16825.0,16864.301316375022 +8626,16840.5,16864.103795077306 +8627,16854.0,16864.019946155506 +8628,16849.9,16863.902768179112 +8629,16829.9,16863.620587530324 +8630,16823.6,16863.288466471982 +8631,16801.2,16862.773209488812 +8632,16831.4,16862.512850903844 +8633,16827.0,16862.21813844821 +8634,16811.3,16861.795581282666 +8635,16834.9,16861.572381438 +8636,16834.2,16861.345224745568 +8637,16826.2,16861.053563129422 +8638,16831.5,16860.808305344115 +8639,16912.9,16861.240601565325 +8640,16863.0,16861.25520238221 +8641,16859.8,16861.243126013895 +8642,16872.6,16861.337373930794 +8643,16868.1,16861.393495308963 +8644,16883.6,16861.577781654945 +8645,16862.3,16861.583775168183 +8646,16882.4,16861.756523921973 +8647,16859.6,16861.73862745789 +8648,16840.4,16861.561543412594 +8649,16869.7,16861.62908247141 +8650,16852.2,16861.550832824345 +8651,16823.7,16861.236718029122 +8652,16803.2,16860.755085514356 +8653,16811.1,16860.3430101159 +8654,16764.3,16859.545972687552 +8655,16790.7,16858.974636814626 +8656,16769.7,16858.23376845932 +8657,16673.0,16856.696558762564 +8658,16630.9,16854.822728399387 +8659,16681.3,16853.38270575707 +8660,16658.3,16851.763762140832 +8661,16696.3,16850.47360643842 +8662,16682.0,16849.075485223162 +8663,16698.2,16847.823406507618 +8664,16680.7,16846.43649027104 +8665,16689.4,16845.133282882896 +8666,16676.4,16843.7330066764 +8667,16643.3,16842.069662222653 +8668,16653.7,16840.50642851126 +8669,16597.3,16838.488117901208 +8670,16604.7,16836.547967545182 +8671,16639.0,16834.90856532489 +8672,16635.1,16833.25040295705 +8673,16650.5,16831.733802102633 +8674,16658.1,16830.292857686847 +8675,16672.0,16828.9792240131 +8676,16646.5,16827.46487360635 +8677,16642.3,16825.92823565111 +8678,16729.5,16825.128001330355 +8679,16571.3,16823.021544887783 +8680,16602.2,16821.189000946804 +8681,16635.0,16819.64386400949 +8682,16625.0,16818.028562233478 +8683,16589.6,16816.13288951785 +8684,16591.2,16814.266226534295 +8685,16520.9,16811.831652040237 +8686,16511.4,16809.33844330961 +8687,16539.0,16807.094970751026 +8688,16575.7,16805.174680537326 +8689,16541.7,16802.988168665648 +8690,16535.9,16800.771669340622 +8691,16553.2,16798.7171326656 +8692,16564.3,16796.771762270033 +8693,16570.5,16794.893988309286 +8694,16560.2,16792.94632035651 +8695,16553.0,16790.95506458592 +8696,16526.0,16788.756267369437 +8697,16587.0,16787.08194149915 +8698,16600.3,16785.53188389335 +8699,16593.8,16783.94074792743 +8700,16612.5,16782.518003131354 +8701,16610.2,16781.087978209103 +8702,16625.6,16779.79762154347 +8703,16610.1,16778.38934252651 +8704,16625.0,16777.116401924628 +8705,16610.8,16775.736182821518 +8706,16628.8,16774.51679541221 +8707,16608.1,16773.135743168128 +8708,16592.9,16771.640010859683 +8709,16590.9,16770.140093757116 +8710,16614.0,16768.844325344195 +8711,16630.3,16767.694579905656 +8712,16623.9,16766.501263889844 +8713,16603.4,16765.147726430176 +8714,16600.0,16763.777205878887 +8715,16593.9,16762.367436535496 +8716,16608.5,16761.090528348483 +8717,16548.2,16759.323801972147 +8718,16536.0,16757.470492412212 +8719,16467.5,16755.06409828431 +8720,16493.0,16752.88929248942 +8721,16509.8,16750.871953962538 +8722,16482.1,16748.641481315546 +8723,16489.5,16746.490929603387 +8724,16490.9,16744.369843050663 +8725,16462.0,16742.026524851073 +8726,16407.0,16739.246221740275 +8727,16547.7,16737.656626539112 +8728,16540.2,16736.017982335467 +8729,16527.2,16734.285053021482 +8730,16560.5,16732.842853411345 +8731,16520.8,16731.08316168179 +8732,16564.6,16729.70155868028 +8733,16578.9,16728.450093463016 +8734,16580.0,16727.218142479924 +8735,16601.2,16726.1723487664 +8736,16574.0,16724.909507697797 +8737,16562.9,16723.565030455495 +8738,16573.1,16722.31635800358 +8739,16549.1,16720.87887785417 +8740,16545.8,16719.425941108497 +8741,16536.0,16717.903734128344 +8742,16541.8,16716.44229235135 +8743,16564.4,16715.1805305891 +8744,16550.0,16713.80973780413 +8745,16544.0,16712.400528361773 +8746,16564.0,16711.16898870732 +8747,16565.6,16709.96094730726 +8748,16576.1,16708.850068076492 +8749,16597.7,16707.927660872538 +8750,16584.8,16706.90585455824 +8751,16586.0,16705.90248647062 +8752,16596.0,16704.99043264099 +8753,16573.4,16703.898395855587 +8754,16571.7,16702.801313732303 +8755,16564.3,16701.6519252366 +8756,16563.5,16700.505436230487 +8757,16544.0,16699.20663592982 +8758,16515.1,16697.678780029986 +8759,16537.6,16696.35032542393 +8760,16527.0,16694.944928532444 diff --git a/x/common/omap/impl.go b/x/common/omap/impl.go new file mode 100644 index 00000000..5acca617 --- /dev/null +++ b/x/common/omap/impl.go @@ -0,0 +1,49 @@ +package omap + +import ( + "github.com/archway-network/archway/x/common/asset" +) + +func stringIsLess(a, b string) bool { + return a < b +} + +// --------------------------------------------------------------------------- +// OrderedMap[string, V]: OrderedMap_String +// --------------------------------------------------------------------------- + +func OrderedMap_String[V any](data map[string]V) OrderedMap[string, V] { + omap := OrderedMap[string, V]{} + return omap.BuildFrom(data, stringSorter{}) +} + +// stringSorter is a Sorter implementation for keys of type string . It uses +// the built-in string comparison to determine order. +type stringSorter struct{} + +var _ Sorter[string] = (*stringSorter)(nil) + +func (sorter stringSorter) Less(a, b string) bool { + return stringIsLess(a, b) +} + +// --------------------------------------------------------------------------- +// OrderedMap[asset.Pair, V]: OrderedMap_Pair +// --------------------------------------------------------------------------- + +func OrderedMap_Pair[V any]( + data map[asset.Pair]V, +) OrderedMap[asset.Pair, V] { + omap := OrderedMap[asset.Pair, V]{} + return omap.BuildFrom(data, pairSorter{}) +} + +// pairSorter is a Sorter implementation for keys of type asset.Pair. It uses +// the built-in string comparison to determine order. +type pairSorter struct{} + +var _ Sorter[asset.Pair] = (*pairSorter)(nil) + +func (sorter pairSorter) Less(a, b asset.Pair) bool { + return stringIsLess(a.String(), b.String()) +} diff --git a/x/common/omap/omap.go b/x/common/omap/omap.go new file mode 100644 index 00000000..d40a3ed3 --- /dev/null +++ b/x/common/omap/omap.go @@ -0,0 +1,127 @@ +// Package omap defines a generic-based type for creating ordered maps. It +// exports a "Sorter" interface, allowing the creation of ordered maps with +// custom key and value types. +// +// Specifically, omap supports ordered maps with keys of type string or +// asset.Pair and values of any type. See impl.go for examples. +// +// ## Motivation +// +// Ensuring deterministic behavior is crucial in blockchain systems, as all +// nodes must reach a consensus on the state of the blockchain. Every action, +// given the same input, should consistently yield the same result. A +// divergence in state could impede the ability of nodes to validate a block, +// prohibiting the addition of the block to the chain, which could lead to +// chain halts. +package omap + +import ( + "sort" +) + +// OrderedMap is a wrapper struct around the built-in map that has guarantees +// about order because it sorts its keys with a custom sorter. It has a public +// API that mirrors that functionality of `map`. OrderedMap is built with +// generics, so it can hold various combinations of key-value types. +type OrderedMap[K comparable, V any] struct { + Data map[K]V + orderedKeys []K + keyIndexMap map[K]int // useful for delete operation + isOrdered bool + sorter Sorter[K] +} + +// Sorter is an interface used for ordering the keys in the OrderedMap. +type Sorter[K any] interface { + // Returns true if 'a' is less than 'b' Less needs to be defined for the + // key type, K, to provide a comparison operation. + Less(a K, b K) bool +} + +// ensureOrder is a method on the OrderedMap that sorts the keys in the map +// and rebuilds the index map. +func (om *OrderedMap[K, V]) ensureOrder() { + keys := make([]K, 0, len(om.Data)) + for key := range om.Data { + keys = append(keys, key) + } + + // Sort the keys using the Sort function + lessFunc := func(i, j int) bool { + return om.sorter.Less(keys[i], keys[j]) + } + sort.Slice(keys, lessFunc) + + om.orderedKeys = keys + om.keyIndexMap = make(map[K]int) + for idx, key := range om.orderedKeys { + om.keyIndexMap[key] = idx + } + om.isOrdered = true +} + +// BuildFrom is a method that builds an OrderedMap from a given map and a +// sorter for the keys. This function is useful for creating new OrderedMap +// types with typed keys. +func (om OrderedMap[K, V]) BuildFrom( + data map[K]V, sorter Sorter[K], +) OrderedMap[K, V] { + om.Data = data + om.sorter = sorter + om.ensureOrder() + return om +} + +// Range returns a channel of keys in their sorted order. This allows you +// to iterate over the map in a deterministic order. Using a channel here +// makes it so that the iteration is done lazily rather loading the entire +// map (OrderedMap.data) into memory and then iterating. +func (om OrderedMap[K, V]) Range() <-chan (K) { + iterChan := make(chan K) + go func() { + defer close(iterChan) + // Generate or compute values on-demand + for _, key := range om.orderedKeys { + iterChan <- key + } + }() + return iterChan +} + +// Has checks whether a key exists in the map. +func (om OrderedMap[K, V]) Has(key K) bool { + _, exists := om.Data[key] + return exists +} + +// Len returns the number of items in the map. +func (om OrderedMap[K, V]) Len() int { + return len(om.Data) +} + +// Keys returns a slice of the keys in their sorted order. +func (om *OrderedMap[K, V]) Keys() []K { + if !om.isOrdered { + om.ensureOrder() + } + return om.orderedKeys +} + +// Set adds a key-value pair to the map, or updates the value if the key +// already exists. It ensures the keys are ordered after the operation. +func (om *OrderedMap[K, V]) Set(key K, val V) { + om.Data[key] = val + om.ensureOrder() // TODO perf: make this more efficient with a clever insert. +} + +// Delete removes a key-value pair from the map if the key exists. +func (om *OrderedMap[K, V]) Delete(key K) { + idx, keyExists := om.keyIndexMap[key] + if keyExists { + delete(om.Data, key) + + orderedKeys := om.orderedKeys + orderedKeys = append(orderedKeys[:idx], orderedKeys[idx+1:]...) + om.orderedKeys = orderedKeys + } +} diff --git a/x/common/omap/omap_test.go b/x/common/omap/omap_test.go new file mode 100644 index 00000000..67fb53c5 --- /dev/null +++ b/x/common/omap/omap_test.go @@ -0,0 +1,114 @@ +package omap_test + +import ( + "fmt" + "sort" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/omap" +) + +// TestLenHasKeys checks the length of the ordered map and verifies if the map +// contains certain keys. +func TestLenHasKeys(t *testing.T) { + type HasCheck struct { + key string + has bool + } + + testCases := []struct { + dataMap map[string]int + len int + hasChecks []HasCheck + }{ + { + dataMap: map[string]int{"xyz": 420, "abc": 69}, + len: 2, + hasChecks: []HasCheck{ + {key: "foo", has: false}, + {key: "xyz", has: true}, + {key: "bar", has: false}, + }, + }, + { + dataMap: map[string]int{"aaa": 420, "bbb": 69, "ccc": 69, "ddd": 28980}, + len: 4, + hasChecks: []HasCheck{ + {key: "foo", has: false}, + {key: "xyz", has: false}, + {key: "bbb", has: true}, + }, + }, + } + + for idx, tc := range testCases { + t.Run(fmt.Sprintf("case-%d", idx), func(t *testing.T) { + om := omap.OrderedMap_String[int](tc.dataMap) + + require.Equal(t, tc.len, om.Len()) + + orderedKeys := om.Keys() + definitelyOrderedKeys := []string{} + definitelyOrderedKeys = append(definitelyOrderedKeys, orderedKeys...) + sort.Strings(definitelyOrderedKeys) + + require.Equal(t, definitelyOrderedKeys, orderedKeys) + + idx := 0 + for key := range om.Range() { + require.Equal(t, orderedKeys[idx], key) + idx++ + } + }) + } +} + +// TestGetSetDelete checks the Get, Set, and Delete operations on the OrderedMap. +func TestGetSetDelete(t *testing.T) { + om := omap.OrderedMap_String[string](make(map[string]string)) + require.Equal(t, 0, om.Len()) + + om.Set("foo", "fooval") + require.True(t, om.Has("foo")) + require.Equal(t, 1, om.Len()) + + om.Delete("bar") // shouldn't cause any problems + om.Delete("foo") + require.False(t, om.Has("foo")) + require.Equal(t, 0, om.Len()) +} + +// TestOrderedMap_Pair tests an OrderedMap where the key is an asset.Pair, a +// type that isn't built-in. +func TestOrderedMap_Pair(t *testing.T) { + pairStrs := []string{ + "abc:xyz", "abc:abc", "aaa:bbb", "xyz:xyz", "bbb:ccc", "xyz:abc", + } + orderedKeyStrs := []string{} + orderedKeyStrs = append(orderedKeyStrs, pairStrs...) + sort.Strings(orderedKeyStrs) + + orderedKeys := asset.MustNewPairs(orderedKeyStrs...) + pairs := asset.MustNewPairs(pairStrs...) + + type ValueType struct{} + unorderedMap := make(map[asset.Pair]ValueType) + for _, pair := range pairs { + unorderedMap[pair] = ValueType{} + } + + om := omap.OrderedMap_Pair[ValueType](unorderedMap) + require.Equal(t, 6, om.Len()) + require.EqualValues(t, orderedKeys, om.Keys()) + require.NotEqualValues(t, asset.PairsToStrings(orderedKeys), pairStrs) + + var pairsFromLoop []asset.Pair + for pair := range om.Range() { + pairsFromLoop = append(pairsFromLoop, pair) + } + require.EqualValues(t, orderedKeys, pairsFromLoop) + require.NotEqualValues(t, pairsFromLoop, pairs) +} diff --git a/x/common/paginate.go b/x/common/paginate.go new file mode 100644 index 00000000..9c50abfd --- /dev/null +++ b/x/common/paginate.go @@ -0,0 +1,55 @@ +package common + +import ( + "fmt" + + sdkquery "github.com/cosmos/cosmos-sdk/types/query" +) + +const DefaultPageItemsLimit uint64 = 50 + +// ParsePagination: Validates and cleans a PageRequest to make setting values +// less error-prone and use Nibiru-specific defaults. +// 1. This fn is intended to be used with sdkquery.Paginate, which paginates +// all of the results in a PrefixStore based on the provided PageRequest. +// 2. This fn is panic-safe, so it can be used freely throughout the base app. +// 3. A "page" value of -1 means that a key is given for the prefix store. +// This means that the PageRequest.Offset will be ignored. +func ParsePagination( + pageReq *sdkquery.PageRequest, +) (newPageReq *sdkquery.PageRequest, page int, err error) { + newPageReq = new(sdkquery.PageRequest) + if pageReq == nil { + newPageReq = &sdkquery.PageRequest{ + Offset: 0, // only offset should be set on nil request + Limit: DefaultPageItemsLimit, // using default limit 50 instead of 100. + CountTotal: false, + Reverse: false, + } + } else { + *newPageReq = *pageReq + } + + // Clean limit + if newPageReq.Limit <= 0 || newPageReq.Limit > DefaultPageItemsLimit { + newPageReq.Limit = DefaultPageItemsLimit + } + + // Clean offset and compute page + haveKey := newPageReq.Key != nil + haveOffset := newPageReq.Offset > 0 + switch { + case haveKey && haveOffset: + newPageReq = nil + page = -1 + err = fmt.Errorf("invalid page request, either offset or key is expected, not both.") + case haveOffset: + page = (int(newPageReq.Offset) / int(newPageReq.Limit)) + 1 + case haveKey: + page = -1 + default: // neither key nor offset given + newPageReq.Offset = 0 + page = (int(newPageReq.Offset) / int(newPageReq.Limit)) + 1 + } + return newPageReq, page, err +} diff --git a/x/common/paginate_test.go b/x/common/paginate_test.go new file mode 100644 index 00000000..08718c62 --- /dev/null +++ b/x/common/paginate_test.go @@ -0,0 +1,106 @@ +package common_test + +import ( + "testing" + + sdkquery "github.com/cosmos/cosmos-sdk/types/query" + "github.com/stretchr/testify/suite" + + "github.com/archway-network/archway/x/common" +) + +type paginateSuite struct { + suite.Suite +} + +func TestPaginateTestSuite(t *testing.T) { + suite.Run(t, new(paginateSuite)) +} + +func (s *paginateSuite) TestParsePagination() { + for _, tc := range []struct { + name string + pageReq *sdkquery.PageRequest + wantErr bool + wantPage int + wantOffset int + }{ + { + name: "blank (default): no key, no offset", + pageReq: &sdkquery.PageRequest{}, + wantErr: false, + wantPage: 1, + wantOffset: 0, + }, + + { + name: "nil (default)", + pageReq: nil, + wantErr: false, + wantPage: 1, + wantOffset: 0, + }, + + { + name: "custom: has key, no offset", + pageReq: &sdkquery.PageRequest{ + Key: []byte("haskey"), + Limit: 25, + }, + wantErr: false, + wantPage: -1, + wantOffset: 0, + }, + + { + name: "custom: no key, has offset", + pageReq: &sdkquery.PageRequest{ + Offset: 256, + Limit: 12, + }, + wantErr: false, + wantPage: 22, // floor(256 / 12) + 1 = 22 + wantOffset: 256, + }, + + { + name: "custom: has key, has offset", + pageReq: &sdkquery.PageRequest{ + Key: []byte("haskey-and-offset"), + Offset: 99, + }, + wantErr: true, + wantPage: -1, + wantOffset: 99, + }, + } { + s.T().Run(tc.name, func(t *testing.T) { + gotPageReq, gotPage, gotErr := common.ParsePagination(tc.pageReq) + + s.EqualValues(tc.wantPage, gotPage) + + if tc.wantErr { + s.Error(gotErr) + return + } + s.NoError(gotErr) + + s.EqualValues(tc.wantOffset, int(gotPageReq.Offset)) + + // ---------------------------- + // Checks on fields of tc.pageReq + // ---------------------------- + if tc.pageReq == nil { + return + } + + var wantLimit uint64 + if tc.pageReq.Limit > 0 && tc.pageReq.Limit < 50 { + wantLimit = tc.pageReq.Limit + } else { + wantLimit = common.DefaultPageItemsLimit + } + s.EqualValues(wantLimit, gotPageReq.Limit) + }) + } +} diff --git a/x/common/set/set.go b/x/common/set/set.go new file mode 100644 index 00000000..6b05a905 --- /dev/null +++ b/x/common/set/set.go @@ -0,0 +1,36 @@ +package set + +type Set[T comparable] map[T]struct{} + +func (set Set[T]) Add(s T) { + set[s] = struct{}{} +} + +func (set Set[T]) Remove(s T) { + delete(set, s) +} + +func (set Set[T]) Has(s T) bool { + _, ok := set[s] + return ok +} + +func (set Set[T]) Len() int { + return len(set.ToSlice()) +} + +func (set Set[T]) ToSlice() []T { + var slice []T + for s := range set { + slice = append(slice, s) + } + return slice +} + +func New[T comparable](strs ...T) Set[T] { + set := Set[T]{} + for _, s := range strs { + set.Add(s) + } + return set +} diff --git a/x/common/set/set_test.go b/x/common/set/set_test.go new file mode 100644 index 00000000..f2665b98 --- /dev/null +++ b/x/common/set/set_test.go @@ -0,0 +1,58 @@ +package set + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var elementSlice = []string{"fire", "earth", "water", "air"} + +func TestAdd(t *testing.T) { + elements := New(elementSlice...) + + assert.False(t, elements.Has("lava")) + assert.False(t, elements.Has("mud")) + + elements.Add("lava") + elements.Add("mud") + assert.True(t, elements.Has("lava")) + assert.True(t, elements.Has("mud")) + + assert.Equal(t, 6, elements.Len()) + + // Add blank string + elements.Add("") + assert.True(t, elements.Has("")) + assert.Equal(t, 7, elements.Len()) +} + +func TestRemove(t *testing.T) { + elements := New(elementSlice...) + elem := "water" + assert.True(t, elements.Has(elem)) + + elements.Remove(elem) + assert.False(t, elements.Has(elem)) +} + +func TestHas(t *testing.T) { + elements := New(elementSlice...) + + assert.True(t, elements.Has("fire")) + assert.True(t, elements.Has("water")) + assert.True(t, elements.Has("air")) + assert.True(t, elements.Has("earth")) + assert.False(t, elements.Has("")) + assert.False(t, elements.Has("foo")) + assert.False(t, elements.Has("bar")) +} + +func TestLen(t *testing.T) { + elements := New(elementSlice...) + assert.Equal(t, elements.Len(), 4) + + elements.Remove("fire") + elements.Remove("water") + assert.Equal(t, elements.Len(), 2) +} diff --git a/x/common/testutil/README.md b/x/common/testutil/README.md new file mode 100644 index 00000000..63f75ecf --- /dev/null +++ b/x/common/testutil/README.md @@ -0,0 +1,4 @@ +# Testutil Directory + +The x/common/testutil directory is not a cosmos-sdk module perse, but a collection of +test utilities to make testing the other x/modules. \ No newline at end of file diff --git a/x/common/testutil/action/account.go b/x/common/testutil/action/account.go new file mode 100644 index 00000000..accad5f4 --- /dev/null +++ b/x/common/testutil/action/account.go @@ -0,0 +1,54 @@ +package action + +// import ( +// sdk "github.com/cosmos/cosmos-sdk/types" + +// "github.com/archway-network/archway/app" +// // inflationtypes "github.com/archway-network/archway/x/inflation/types" +// ) + +// type fundAccount struct { +// Account sdk.AccAddress +// Amount sdk.Coins +// } + +// func FundAccount(account sdk.AccAddress, amount sdk.Coins) Action { +// return &fundAccount{Account: account, Amount: amount} +// } + +// func (c fundAccount) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// err := app.BankKeeper.MintCoins(ctx, inflationtypes.ModuleName, c.Amount) +// if err != nil { +// return ctx, err +// } + +// err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, inflationtypes.ModuleName, c.Account, c.Amount) +// if err != nil { +// return ctx, err +// } + +// return ctx, nil +// } + +// type fundModule struct { +// Module string +// Amount sdk.Coins +// } + +// func FundModule(module string, amount sdk.Coins) Action { +// return fundModule{Module: module, Amount: amount} +// } + +// func (c fundModule) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// err := app.BankKeeper.MintCoins(ctx, inflationtypes.ModuleName, c.Amount) +// if err != nil { +// return ctx, err +// } + +// err = app.BankKeeper.SendCoinsFromModuleToModule(ctx, inflationtypes.ModuleName, c.Module, c.Amount) +// if err != nil { +// return ctx, err +// } + +// return ctx, nil +// } diff --git a/x/common/testutil/action/block.go b/x/common/testutil/action/block.go new file mode 100644 index 00000000..e12060fd --- /dev/null +++ b/x/common/testutil/action/block.go @@ -0,0 +1,150 @@ +package action + +// import ( +// "time" + +// "github.com/cometbft/cometbft/abci/types" +// sdk "github.com/cosmos/cosmos-sdk/types" + +// "github.com/archway-network/archway/app" +// ) + +// type increaseBlockNumberBy struct { +// numBlocks int64 +// } + +// func (i increaseBlockNumberBy) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// app.EndBlocker(ctx, types.RequestEndBlock{Height: ctx.BlockHeight()}) + +// ctx = ctx.WithBlockHeight(ctx.BlockHeight() + i.numBlocks) + +// return ctx, nil +// } + +// // IncreaseBlockNumberBy increases the block number by the given number of blocks +// func IncreaseBlockNumberBy(numBlocks int64) Action { +// return increaseBlockNumberBy{numBlocks: numBlocks} +// } + +// type increaseBlockTimeBy struct { +// seconds time.Duration +// } + +// func (i increaseBlockTimeBy) Do(_ *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Second * i.seconds)) + +// return ctx, nil +// } + +// func IncreaseBlockTimeBy(seconds time.Duration) Action { +// return increaseBlockTimeBy{seconds: seconds} +// } + +// type setBlockTime struct { +// blockTime time.Time +// } + +// func (s setBlockTime) Do(_ *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// return ctx.WithBlockTime(s.blockTime), nil +// } + +// // SetBlockTime sets the block time to the given value +// func SetBlockTime(blockTime time.Time) Action { +// return setBlockTime{blockTime: blockTime} +// } + +// type setBlockNumber struct { +// blockNumber int64 +// } + +// func (s setBlockNumber) Do(_ *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// return ctx.WithBlockHeight(s.blockNumber), nil +// } + +// // SetBlockNumber sets the block number to the given value +// func SetBlockNumber(blockNumber int64) Action { +// return setBlockNumber{blockNumber: blockNumber} +// } + +// // type moveToNextBlock struct{} + +// // func (m moveToNextBlock) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// // app.EndBlock(types.RequestEndBlock{}) +// // app.Commit() + +// // newHeader := tmproto.Header{ +// // Height: ctx.BlockHeight() + 1, +// // Time: ctx.BlockTime().Add(time.Second * 5), +// // } + +// // app.BeginBlock(types.RequestBeginBlock{ +// // Header: newHeader, +// // }) + +// // return app.NewContext( +// // false, +// // newHeader, +// // ).WithBlockTime(newHeader.Time), nil +// // } + +// // func MoveToNextBlock() Action { +// // return moveToNextBlock{} +// // } + +// // type moveToNextBlockWithDuration struct { +// // blockDuration time.Duration +// // } + +// // func (m moveToNextBlockWithDuration) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// // app.EndBlock(types.RequestEndBlock{Height: ctx.BlockHeight()}) +// // app.Commit() + +// // newHeader := tmproto.Header{ +// // Height: ctx.BlockHeight() + 1, +// // Time: ctx.BlockTime().Add(m.blockDuration), +// // } + +// // app.BeginBlock(types.RequestBeginBlock{ +// // Header: newHeader, +// // }) + +// // return app.NewContext( +// // false, +// // newHeader, +// // ).WithBlockTime(newHeader.Time), nil +// // } + +// // func MoveToNextBlockWithDuration(blockDuration time.Duration) Action { +// // return moveToNextBlockWithDuration{ +// // blockDuration: blockDuration, +// // } +// // } + +// // type moveToNextBlockWithTime struct { +// // blockTime time.Time +// // } + +// // func (m moveToNextBlockWithTime) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { +// // app.EndBlock(types.RequestEndBlock{Height: ctx.BlockHeight()}) +// // app.Commit() + +// // newHeader := tmproto.Header{ +// // Height: ctx.BlockHeight() + 1, +// // Time: m.blockTime, +// // } + +// // app.BeginBlock(types.RequestBeginBlock{ +// // Header: newHeader, +// // }) + +// // return app.NewContext( +// // false, +// // newHeader, +// // ).WithBlockTime(newHeader.Time), nil +// // } + +// // func MoveToNextBlockWithTime(blockTime time.Time) Action { +// // return moveToNextBlockWithTime{ +// // blockTime: blockTime, +// // } +// // } diff --git a/x/common/testutil/action/testcase.go b/x/common/testutil/action/testcase.go new file mode 100644 index 00000000..be058b6e --- /dev/null +++ b/x/common/testutil/action/testcase.go @@ -0,0 +1,131 @@ +package action + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/testutil/testapp" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Action is a type of operation or task that can be performed in the +// Nibiru application. +type Action interface { + // Do is a specific implementation of the `Action`. When `Do` is called, + // the action is performed and some feedback is provided about the action's + // success. `Do` can mutate the app. + // + // Returns: + // - outCtx: The new context after stateful changes + // - err: The error if one was raised. + // - isMandatory: Whether an error should have been raised. + Do(app *app.ArchwayApp, ctx sdk.Context) ( + outCtx sdk.Context, err error, + ) +} + +// IsNotMandatory is a marker interface for actions that are not mandatory, and it does not stop the test when there is an error. +type IsNotMandatory interface { + IsNotMandatory() +} + +func ActionResp(ctx sdk.Context, respErr error) (outCtx sdk.Context, err error) { + return ctx, respErr +} + +type TestCases []TestCase + +type TestCase struct { + Name string + + given []Action + when []Action + then []Action +} + +// TC creates a new test case +func TC(name string) TestCase { + return TestCase{Name: name} +} + +func (tc TestCase) Given(action ...Action) TestCase { + tc.given = append(tc.given, action...) + return tc +} + +func (tc TestCase) When(action ...Action) TestCase { + tc.when = append(tc.when, action...) + return tc +} + +func (tc TestCase) Then(action ...Action) TestCase { + tc.then = append(tc.then, action...) + return tc +} + +func (tc TestCase) Run(t *testing.T) { + t.Run(tc.Name, func(t *testing.T) { + app, ctx := testapp.NewNibiruTestAppAndContextAtTime(time.UnixMilli(0)) + var err error + var isNotMandatory bool + + for _, action := range tc.given { + _, isNotMandatory = action.(IsNotMandatory) + + ctx, err = action.Do(app, ctx) + if isNotMandatory { + assert.NoError(t, err, "failed to execute given action: %s", tc.Name) + } else { + require.NoError(t, err, "failed to execute given action: %s", tc.Name) + } + } + + for _, action := range tc.when { + _, isNotMandatory = action.(IsNotMandatory) + + ctx, err = action.Do(app, ctx) + if isNotMandatory { + assert.NoError(t, err, "failed to execute when action: %s", tc.Name) + } else { + require.NoError(t, err, "failed to execute when action: %s", tc.Name) + } + } + + for _, action := range tc.then { + _, isNotMandatory = action.(IsNotMandatory) + + ctx, err = action.Do(app, ctx) + if isNotMandatory { + assert.NoError(t, err, "failed to execute then action: %s", tc.Name) + } else { + require.NoError(t, err, "failed to execute then action: %s", tc.Name) + } + } + }) +} + +type TestSuite struct { + t *testing.T + + testCases []TestCase +} + +func NewTestSuite(t *testing.T) *TestSuite { + return &TestSuite{t: t} +} + +func (ts *TestSuite) WithTestCases(testCase ...TestCase) *TestSuite { + ts.testCases = append(ts.testCases, testCase...) + return ts +} + +func (ts *TestSuite) Run() { + for _, testCase := range ts.testCases { + testCase.Run(ts.t) + } +} diff --git a/x/common/testutil/assertion/balances.go b/x/common/testutil/assertion/balances.go new file mode 100644 index 00000000..e4e5c560 --- /dev/null +++ b/x/common/testutil/assertion/balances.go @@ -0,0 +1,88 @@ +package assertion + +import ( + "fmt" + + "github.com/archway-network/archway/x/common/testutil/action" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/app" +) + +func AllBalancesEqual(account sdk.AccAddress, amount sdk.Coins) action.Action { + return &allBalancesEqual{Account: account, Amount: amount} +} + +type allBalancesEqual struct { + Account sdk.AccAddress + Amount sdk.Coins +} + +func (b allBalancesEqual) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + coins := app.BankKeeper.GetAllBalances(ctx, b.Account) + if !coins.Equal(b.Amount) { + return ctx, fmt.Errorf( + "account %s balance not equal, expected %s, got %s", + b.Account.String(), + b.Amount.String(), + coins.String(), + ) + } + + return ctx, nil +} + +func BalanceEqual(account sdk.AccAddress, denom string, amount sdkmath.Int) action.Action { + return &balanceEqual{Account: account, Denom: denom, Amount: amount} +} + +type balanceEqual struct { + Account sdk.AccAddress + Denom string + Amount sdkmath.Int +} + +func (b balanceEqual) IsNotMandatory() {} + +func (b balanceEqual) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + coin := app.BankKeeper.GetBalance(ctx, b.Account, b.Denom) + if !coin.Amount.Equal(b.Amount) { + return ctx, fmt.Errorf( + "account %s balance not equal, expected %s, got %s", + b.Account.String(), + b.Amount.String(), + coin.String(), + ) + } + + return ctx, nil +} + +func ModuleBalanceEqual(moduleName string, denom string, amount sdkmath.Int) action.Action { + return &moduleBalanceEqual{ModuleName: moduleName, Denom: denom, Amount: amount} +} + +type moduleBalanceEqual struct { + ModuleName string + Denom string + Amount sdkmath.Int +} + +func (b moduleBalanceEqual) IsNotMandatory() {} + +func (b moduleBalanceEqual) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + coin := app.BankKeeper.GetBalance(ctx, app.AccountKeeper.GetModuleAddress(b.ModuleName), b.Denom) + if !coin.Amount.Equal(b.Amount) { + return ctx, fmt.Errorf( + "module %s balance not equal, expected %s, got %s", + b.ModuleName, + b.Amount.String(), + coin.String(), + ) + } + + return ctx, nil +} diff --git a/x/common/testutil/assertion/gas.go b/x/common/testutil/assertion/gas.go new file mode 100644 index 00000000..492c762f --- /dev/null +++ b/x/common/testutil/assertion/gas.go @@ -0,0 +1,27 @@ +package assertion + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/testutil/action" +) + +type gasConsumedShouldBe struct { + gasConsumed uint64 +} + +func (g gasConsumedShouldBe) Do(_ *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + gasUsed := ctx.GasMeter().GasConsumed() + if g.gasConsumed != gasUsed { + return ctx, fmt.Errorf("gas consumed should be %d, but got %d", g.gasConsumed, gasUsed) + } + + return ctx, nil +} + +func GasConsumedShouldBe(gasConsumed uint64) action.Action { + return &gasConsumedShouldBe{gasConsumed: gasConsumed} +} diff --git a/x/common/testutil/cases.go b/x/common/testutil/cases.go new file mode 100644 index 00000000..547738d0 --- /dev/null +++ b/x/common/testutil/cases.go @@ -0,0 +1,36 @@ +package testutil + +import ( + "testing" +) + +type FunctionTestCase struct { + Name string + Test func() +} + +type FunctionTestCases = []FunctionTestCase + +func RunFunctionTests(t *testing.T, testCases []FunctionTestCase) { + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + tc.Test() + }) + } +} + +/* +BeforeIntegrationSuite: Skips a test if the `-short` flag is used: + +All tests: `go test ./...` +Unit tests only: `go test ./... -short` +Integration tests only: `go test ./... -run Integration` + +See: https://stackoverflow.com/a/41407042/13305627 +*/ +func BeforeIntegrationSuite(suiteT *testing.T) { + if testing.Short() { + suiteT.Skip("skipping integration test suite") + } + suiteT.Log("setting up integration test suite") +} diff --git a/x/common/testutil/cases_test.go b/x/common/testutil/cases_test.go new file mode 100644 index 00000000..3a9b19f8 --- /dev/null +++ b/x/common/testutil/cases_test.go @@ -0,0 +1,29 @@ +package testutil_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/testutil" +) + +func TestRunFunctionTests(t *testing.T) { + testutil.RunFunctionTests(t, testutil.FunctionTestCases{ + { + Name: "test case A", + Test: func() { + }, + }, + }) +} + +func TestBeforeIntegrationSuite(t *testing.T) { + testutil.BeforeIntegrationSuite(t) + + if testing.Short() { + require.True(t, t.Skipped()) + } else { + require.False(t, t.Skipped()) + } +} diff --git a/x/common/testutil/cli/doc.go b/x/common/testutil/cli/doc.go new file mode 100644 index 00000000..be9e8442 --- /dev/null +++ b/x/common/testutil/cli/doc.go @@ -0,0 +1,65 @@ +/* +Package network implements and exposes a fully operational in-process Tendermint +test network that consists of at least one or potentially many validators. This +test network can be used primarily for integration tests or unit test suites. + +The test network utilizes SimApp as the ABCI application and uses all the modules +defined in the Cosmos SDK. An in-process test network can be configured with any +number of validators as well as account funds and even custom genesis state. + +when creating a test network, a series of Validator objects are returned. Each +Validator object has useful information such as their address and public key. A +Validator will also provide its RPC, P2P, and API addresses that can be useful +for integration testing. In addition, a Tendermint local RPC client is also provided +which can be handy for making direct RPC calls to Tendermint. + +Note, due to limitations in concurrency and the design of the RPC layer in +Tendermint, only the first Validator object will have an RPC and API client +exposed. Due to this exact same limitation, only a single test network can exist +at a time. A caller must be certain it calls Cleanup after it no longer needs +the network. + +A typical testing flow might look like the following: + + type IntegrationTestSuite struct { + suite.Suite + + cfg testutil.Config + network *testutil.Network + } + + func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := testutil.DefaultConfig() + cfg.NumValidators = 1 + + s.cfg = cfg + s.network = testutil.New(s.T(), cfg) + + _, err := s.network.WaitForHeight(1) + s.Require().NoError(err) + } + + func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + + // This is important and must be called to ensure other tests can create + // a network! + s.network.Cleanup() + } + + func (s *IntegrationTestSuite) TestQueryBalancesRequestHandlerFn() { + val := s.network.Validators[0] + baseURL := val.APIAddress + + // Use baseURL to make API HTTP requests or use val.RPCClient to make direct + // Tendermint RPC calls. + // ... + } + + func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) + } +*/ +package cli diff --git a/x/common/testutil/cli/logger.go b/x/common/testutil/cli/logger.go new file mode 100644 index 00000000..0a55a913 --- /dev/null +++ b/x/common/testutil/cli/logger.go @@ -0,0 +1,14 @@ +package cli + +import ( + "testing" +) + +// Logger is a network logger interface that exposes testnet-level Log() methods for an in-process testing network +// This is not to be confused with logging that may happen at an individual node or validator level +type Logger interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) +} + +var _ Logger = (*testing.T)(nil) diff --git a/x/common/testutil/cli/network.go b/x/common/testutil/cli/network.go new file mode 100644 index 00000000..b3f13c10 --- /dev/null +++ b/x/common/testutil/cli/network.go @@ -0,0 +1,730 @@ +package cli + +import ( + "bufio" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "os/signal" + "path/filepath" + "strings" + + "sync" + "syscall" + "time" + + "cosmossdk.io/log" + "cosmossdk.io/store/pruning/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/testutil" + net "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/testutil/sims" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + "golang.org/x/sync/errgroup" + + "cosmossdk.io/math" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/baseapp" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" + + tmrand "github.com/cometbft/cometbft/libs/rand" + "github.com/cometbft/cometbft/node" + tmclient "github.com/cometbft/cometbft/rpc/client" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/server" + serverapi "github.com/cosmos/cosmos-sdk/server/api" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "google.golang.org/grpc" + + "github.com/archway-network/archway/x/common/denoms" + + "github.com/archway-network/archway/app" +) + +// package-wide network lock to only allow one test network at a time +var lock = new(sync.Mutex) + +// AppConstructor defines a function which accepts a network configuration and +// creates an ABCI Application to provide to Tendermint. +type AppConstructor = func(val Validator) servertypes.Application + +type ( + // Network defines a in-process testing network. It is primarily intended + // for client and integration testing. The Network struct can spawn any + // number of validators, each with its own RPC and API clients. + // + // ### Constraints + // + // 1. Only the first validator will have a functional RPC and API + // server/client. + // 2. Due to constraints in Tendermint's JSON-RPC implementation, only one + // test network can run at a time. For this reason, it's essential to + // invoke `Network.Cleanup` after testing to allow other tests to create + // networks. + Network struct { + BaseDir string + Config Config + Validators []*Validator + Logger Logger + } + + // Validator defines an in-process Tendermint validator node. Through this + // object, a client can make RPC and API calls and interact with any client + // command or handler. + Validator struct { + AppConfig *serverconfig.Config + ClientCtx client.Context + Ctx *server.Context + // Dir is the root directory of the validator node data and config. Passed to the Tendermint config. + Dir string + + // NodeID is a unique ID for the validator generated when the + // 'cli.Network' is started. + NodeID string + PubKey cryptotypes.PubKey + + // Moniker is a human-readable name that identifies a validator. A + // moniker is optional and may be empty. + Moniker string + + // APIAddress is the endpoint that the validator API server binds to. + // Only the first validator of a 'cli.Network' exposes the full API. + APIAddress string + + // RPCAddress is the endpoint that the RPC server binds to. Only the + // first validator of a 'cli.Network' exposes the full API. + RPCAddress string + + // P2PAddress is the endpoint that the RPC server binds to. The P2P + // server handles Tendermint peer-to-peer (P2P) networking and is + // critical for blockchain replication and consensus. It allows nodes + // to gossip blocks, transactions, and consensus messages. Only the + // first validator of a 'cli.Network' exposes the full API. + P2PAddress string + + // Address - account address + Address sdk.AccAddress + + // ValAddress - validator operator (valoper) address + ValAddress sdk.ValAddress + + // RPCClient wraps most important rpc calls a client would make to + // listen for events, test if it also implements events.EventSwitch. + // + // RPCClient implementations in "github.com/cometbft/cometbft/rpc" v0.37.2: + // - rcp.HTTP + // - rpc.Local + RPCClient tmclient.Client + + tmNode *node.Node + + // API exposes the app's REST and gRPC interfaces, allowing clients to + // read from state and broadcast txs. The API server connects to the + // underlying ABCI application. + api *serverapi.Server + grpc *grpc.Server + grpcWeb *http.Server + secretMnemonic string + errGroup *errgroup.Group + cancelFn context.CancelFunc + } +) + +// NewAppConstructor returns a new simapp AppConstructor +func NewAppConstructor(encodingCfg app.EncodingConfig, chainID string) AppConstructor { + return func(val Validator) servertypes.Application { + return app.NewNibiruApp( + val.Ctx.Logger, + dbm.NewMemDB(), + nil, + true, + encodingCfg, + sims.EmptyAppOptions{}, + baseapp.SetPruning(types.NewPruningOptionsFromString(val.AppConfig.Pruning)), + baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices), + baseapp.SetChainID(chainID), + ) + } +} + +// BuildNetworkConfig returns a configuration for a local in-testing network +func BuildNetworkConfig(appGenesis app.GenesisState) Config { + encCfg := app.MakeEncodingConfig() + + chainID := "chain-" + tmrand.NewRand().Str(6) + return Config{ + Codec: encCfg.Codec, + TxConfig: encCfg.TxConfig, + LegacyAmino: encCfg.Amino, + InterfaceRegistry: encCfg.InterfaceRegistry, + AccountRetriever: authtypes.AccountRetriever{}, + AppConstructor: NewAppConstructor(encCfg, chainID), + GenesisState: appGenesis, + TimeoutCommit: time.Second / 2, + ChainID: chainID, + NumValidators: 1, + BondDenom: denoms.NIBI, + MinGasPrices: fmt.Sprintf("0.000006%s", denoms.NIBI), + AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), + StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), + BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), + StartingTokens: sdk.NewCoins( + sdk.NewCoin(denoms.NUSD, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), + sdk.NewCoin(denoms.NIBI, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), + sdk.NewCoin(denoms.USDC, sdk.TokensFromConsensusPower(1e12, sdk.DefaultPowerReduction)), + ), + PruningStrategy: types.PruningOptionNothing, + CleanupDir: true, + SigningAlgo: string(hd.Secp256k1Type), + KeyringOptions: []keyring.Option{}, + } +} + +// New creates a new Network for integration tests. +func New(logger Logger, baseDir string, cfg Config) (*Network, error) { + // only one caller/test can create and use a network at a time + logger.Log("acquiring test network lock") + lock.Lock() + + network := &Network{ + Logger: logger, + BaseDir: baseDir, + Validators: make([]*Validator, cfg.NumValidators), + Config: cfg, + } + + logger.Log("preparing test network...") + + monikers := make([]string, cfg.NumValidators) + nodeIDs := make([]string, cfg.NumValidators) + valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators) + + var ( + genAccounts []authtypes.GenesisAccount + genBalances []banktypes.Balance + genFiles []string + ) + + buf := bufio.NewReader(os.Stdin) + + // generate private keys, node IDs, and initial transactions + for i := 0; i < cfg.NumValidators; i++ { + appCfg := serverconfig.DefaultConfig() + appCfg.Pruning = cfg.PruningStrategy + appCfg.MinGasPrices = cfg.MinGasPrices + appCfg.API.Enable = true + appCfg.API.Swagger = false + appCfg.Telemetry.Enabled = false + + ctx := server.NewDefaultContext() + tmCfg := ctx.Config + tmCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit + + // Only allow the first validator to expose an RPC, API and gRPC + // server/client due to Tendermint in-process constraints. + apiAddr := "" + tmCfg.RPC.ListenAddress = "" + appCfg.GRPC.Enable = false + appCfg.GRPCWeb.Enable = false + apiListenAddr := "" + if i == 0 { + if cfg.APIAddress != "" { + apiListenAddr = cfg.APIAddress + } else { + var err error + apiListenAddr, _, _, err = net.FreeTCPAddr() + if err != nil { + return nil, err + } + } + + appCfg.API.Address = apiListenAddr + apiURL, err := url.Parse(apiListenAddr) + if err != nil { + return nil, err + } + apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port()) + + if cfg.RPCAddress != "" { + tmCfg.RPC.ListenAddress = cfg.RPCAddress + } else { + rpcAddr, _, _, err := net.FreeTCPAddr() + if err != nil { + return nil, err + } + tmCfg.RPC.ListenAddress = rpcAddr + } + + if cfg.GRPCAddress != "" { + appCfg.GRPC.Address = cfg.GRPCAddress + } else { + _, grpcPort, _, err := net.FreeTCPAddr() + if err != nil { + return nil, err + } + appCfg.GRPC.Address = fmt.Sprintf("0.0.0.0:%s", grpcPort) + } + appCfg.GRPC.Enable = true + + // GRPCWeb now uses the same address than + appCfg.GRPCWeb.Enable = true + } + + loggerNoOp := log.NewNopLogger() + if cfg.EnableTMLogging { + loggerNoOp = log.NewLogger(os.Stdout) + } + + ctx.Logger = loggerNoOp + + nodeDirName := fmt.Sprintf("node%d", i) + nodeDir := filepath.Join(network.BaseDir, nodeDirName, "simd") + clientDir := filepath.Join(network.BaseDir, nodeDirName, "simcli") + gentxsDir := filepath.Join(network.BaseDir, "gentxs") + + err := os.MkdirAll(filepath.Join(nodeDir, "config"), 0o755) + if err != nil { + return nil, err + } + + err = os.MkdirAll(clientDir, 0o755) + if err != nil { + return nil, err + } + + tmCfg.SetRoot(nodeDir) + tmCfg.Moniker = nodeDirName + monikers[i] = nodeDirName + + proxyAddr, _, _, err := net.FreeTCPAddr() + if err != nil { + return nil, err + } + tmCfg.ProxyApp = proxyAddr + + p2pAddr, _, _, err := net.FreeTCPAddr() + if err != nil { + return nil, err + } + + tmCfg.P2P.ListenAddress = p2pAddr + tmCfg.P2P.AddrBookStrict = false + tmCfg.P2P.AllowDuplicateIP = true + + nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg) + if err != nil { + return nil, err + } + + nodeIDs[i] = nodeID + valPubKeys[i] = pubKey + + kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.Codec, cfg.KeyringOptions...) + if err != nil { + return nil, err + } + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) + if err != nil { + return nil, err + } + + var mnemonic string + if i < len(cfg.Mnemonics) { + mnemonic = cfg.Mnemonics[i] + } + + addr, secret, err := sdktestutil.GenerateSaveCoinKey(kb, nodeDirName, mnemonic, true, algo) + if err != nil { + return nil, err + } + + info := map[string]string{"secret": secret} + infoBz, err := json.Marshal(info) + if err != nil { + return nil, err + } + + // save private key seed words + err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz) + if err != nil { + return nil, err + } + + balances := sdk.NewCoins( + sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens), + sdk.NewCoin(cfg.BondDenom, cfg.StakingTokens), + ) + + balances = balances.Add(cfg.StartingTokens...) + + genFiles = append(genFiles, tmCfg.GenesisFile()) + genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()}) + genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) + + commission, err := math.LegacyNewDecFromStr("0.05") + if err != nil { + return nil, err + } + + interfaceRegistry := testutil.CodecOptions{}.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes) + + valAddrCodec := txConfig.SigningContext().ValidatorAddressCodec() + valStr, err := valAddrCodec.BytesToString(sdk.ValAddress(addr)) + if err != nil { + return nil, err + } + + createValMsg, err := stakingtypes.NewMsgCreateValidator( + valStr, + valPubKeys[i], + sdk.NewCoin(cfg.BondDenom, cfg.BondedTokens), + stakingtypes.NewDescription(nodeDirName, "", "", "", ""), + stakingtypes.NewCommissionRates(commission, math.LegacyOneDec(), math.LegacyOneDec()), + math.OneInt(), + ) + if err != nil { + return nil, err + } + + p2pURL, err := url.Parse(p2pAddr) + if err != nil { + return nil, err + } + + memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port()) + fee := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), math.ZeroInt())) + txBuilder := cfg.TxConfig.NewTxBuilder() + err = txBuilder.SetMsgs(createValMsg) + if err != nil { + return nil, err + } + txBuilder.SetFeeAmount(fee) // Arbitrary fee + txBuilder.SetGasLimit(1000000) // Need at least 100386 + txBuilder.SetMemo(memo) + + txFactory := tx.Factory{} + txFactory = txFactory. + WithChainID(cfg.ChainID). + WithMemo(memo). + WithKeybase(kb). + WithTxConfig(cfg.TxConfig) + + err = tx.Sign(nil, txFactory, nodeDirName, txBuilder, true) + if err != nil { + return nil, err + } + + txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) + if err != nil { + return nil, err + } + err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz) + if err != nil { + return nil, err + } + + serverconfig.WriteConfigFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg) + + clientCtx := client.Context{}. + WithKeyringDir(clientDir). + WithKeyring(kb). + WithHomeDir(tmCfg.RootDir). + WithChainID(cfg.ChainID). + WithInterfaceRegistry(cfg.InterfaceRegistry). + WithCodec(cfg.Codec). + WithLegacyAmino(cfg.LegacyAmino). + WithTxConfig(cfg.TxConfig). + WithAccountRetriever(cfg.AccountRetriever) + + network.Validators[i] = &Validator{ + AppConfig: appCfg, + ClientCtx: clientCtx, + Ctx: ctx, + Dir: filepath.Join(network.BaseDir, nodeDirName), + NodeID: nodeID, + PubKey: pubKey, + Moniker: nodeDirName, + RPCAddress: tmCfg.RPC.ListenAddress, + P2PAddress: tmCfg.P2P.ListenAddress, + APIAddress: apiAddr, + Address: addr, + ValAddress: sdk.ValAddress(addr), + secretMnemonic: secret, + } + } + + err := initGenFiles(cfg, genAccounts, genBalances, genFiles) + if err != nil { + return nil, err + } + err = collectGenFiles(cfg, network.Validators, network.BaseDir) + if err != nil { + return nil, err + } + + logger.Log("starting test network...") + for idx, v := range network.Validators { + err := startInProcess(cfg, v) + if err != nil { + return nil, err + } + logger.Log("started validator", idx) + } + + height, err := network.LatestHeight() + if err != nil { + return nil, err + } + + logger.Log("started test network at height:", height) + + // Ensure we cleanup incase any test was abruptly halted (e.g. SIGINT) as + // any defer in a test would not be called. + trapSignal(network.Cleanup) + + return network, err +} + +// LatestHeight returns the latest height of the network or an error if the +// query fails or no validators exist. +func (n *Network) LatestHeight() (int64, error) { + if len(n.Validators) == 0 { + return 0, errors.New("no validators available") + } + + status, err := n.Validators[0].RPCClient.Status(context.Background()) + if err != nil { + return 0, err + } + + return status.SyncInfo.LatestBlockHeight, nil +} + +// WaitForHeight performs a blocking check where it waits for a block to be +// committed after a given block. If that height is not reached within a timeout, +// an error is returned. Regardless, the latest height queried is returned. +func (n *Network) WaitForHeight(h int64) (int64, error) { + return n.WaitForHeightWithTimeout(h, 40*time.Second) +} + +// WaitForHeightWithTimeout is the same as WaitForHeight except the caller can +// provide a custom timeout. +func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, error) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + timeout := time.NewTimer(t) + defer timeout.Stop() + + if len(n.Validators) == 0 { + return 0, errors.New("no validators available") + } + + var latestHeight int64 + val := n.Validators[0] + + for { + select { + case <-timeout.C: + return latestHeight, errors.New("timeout exceeded waiting for block") + case <-ticker.C: + status, err := val.RPCClient.Status(context.Background()) + if err == nil && status != nil { + latestHeight = status.SyncInfo.LatestBlockHeight + if latestHeight >= h { + return latestHeight, nil + } + } + } + } +} + +// WaitForNextBlock waits for the next block to be committed, returning an error +// upon failure. +func (n *Network) WaitForNextBlock() error { + lastBlock, err := n.LatestHeight() + if err != nil { + return err + } + + _, err = n.WaitForHeight(lastBlock + 1) + if err != nil { + return err + } + + return err +} + +// WaitForDuration waits for at least the duration provided in blockchain time. +func (n *Network) WaitForDuration(duration time.Duration) error { + if len(n.Validators) == 0 { + return fmt.Errorf("no validators") + } + val := n.Validators[0] + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + lastBlock, err := val.RPCClient.Block(ctx, nil) + if err != nil { + return err + } + + waitAtLeastUntil := lastBlock.Block.Time.Add(duration) + + for { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + block, err := val.RPCClient.Block(ctx, nil) + if err != nil { + return err + } + if block.Block.Time.After(waitAtLeastUntil) { + return nil + } + } +} + +// Cleanup removes the root testing (temporary) directory and stops both the +// Tendermint and API services. It allows other callers to create and start +// test networks. This method must be called when a test is finished, typically +// in a defer. +func (n *Network) Cleanup() { + defer func() { + lock.Unlock() + n.Logger.Log("released test network lock") + }() + + n.Logger.Log("cleaning up test network...") + + for _, v := range n.Validators { + if v.tmNode != nil && v.tmNode.IsRunning() { + _ = v.tmNode.Stop() + } + + if v.api != nil { + _ = v.api.Close() + } + + if v.grpc != nil { + v.grpc.Stop() + if v.grpcWeb != nil { + _ = v.grpcWeb.Close() + } + } + } + + // Give a brief pause for things to finish closing in other processes. + // Hopefully this helps with the address-in-use errors. 100ms chosen + // randomly. + time.Sleep(100 * time.Millisecond) + + if n.Config.CleanupDir { + _ = os.RemoveAll(n.BaseDir) + } + + n.Logger.Log("finished cleaning up test network") +} + +func (val Validator) SecretMnemonic() string { + return val.secretMnemonic +} + +func (val Validator) SecretMnemonicSlice() []string { + return strings.Fields(val.secretMnemonic) +} + +// LogMnemonic logs a secret to the network's logger for debugging and manual +// testing +func LogMnemonic(l Logger, secret string) { + lines := []string{ + "THIS MNEMONIC IS FOR TESTING PURPOSES ONLY", + "DO NOT USE IN PRODUCTION", + "", + strings.Join(strings.Fields(secret)[0:8], " "), + strings.Join(strings.Fields(secret)[8:16], " "), + strings.Join(strings.Fields(secret)[16:24], " "), + } + + lineLengths := make([]int, len(lines)) + for i, line := range lines { + lineLengths[i] = len(line) + } + + maxLineLength := 0 + for _, lineLen := range lineLengths { + if lineLen > maxLineLength { + maxLineLength = lineLen + } + } + + l.Log("\n") + l.Log(strings.Repeat("+", maxLineLength+8)) + for _, line := range lines { + l.Logf("++ %s ++\n", centerText(line, maxLineLength)) + } + l.Log(strings.Repeat("+", maxLineLength+8)) + l.Log("\n") +} + +// centerText: Centers text across a fixed width, filling either side with +// whitespace buffers +func centerText(text string, width int) string { + textLen := len(text) + leftBuffer := strings.Repeat(" ", (width-textLen)/2) + rightBuffer := strings.Repeat(" ", (width-textLen)/2+(width-textLen)%2) + + return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) +} + +func (n *Network) keyBaseAndInfoForAddr(addr sdk.AccAddress) (keyring.Keyring, *keyring.Record, error) { + for _, v := range n.Validators { + info, err := v.ClientCtx.Keyring.KeyByAddress(addr) + if err == nil { + return v.ClientCtx.Keyring, info, nil + } + } + + return nil, nil, fmt.Errorf("address not found in any of the known validators keyrings: %s", addr.String()) +} + +// trapSignal traps SIGINT and SIGTERM and calls os.Exit once a signal is received. +func trapSignal(cleanupFunc func()) { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + go func() { + sig := <-sigs + + if cleanupFunc != nil { + cleanupFunc() + } + exitCode := 128 + + switch sig { + case syscall.SIGINT: + exitCode += int(syscall.SIGINT) + case syscall.SIGTERM: + exitCode += int(syscall.SIGTERM) + } + + os.Exit(exitCode) + }() +} diff --git a/x/common/testutil/cli/network_config.go b/x/common/testutil/cli/network_config.go new file mode 100644 index 00000000..91fc799c --- /dev/null +++ b/x/common/testutil/cli/network_config.go @@ -0,0 +1,63 @@ +package cli + +import ( + "encoding/json" + "time" + + sdkmath "cosmossdk.io/math" + + tmconfig "github.com/cometbft/cometbft/config" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Config: Defines the parameters needed to start a local test network. +type Config struct { + Codec codec.Codec + LegacyAmino *codec.LegacyAmino // TODO: Remove! + InterfaceRegistry codectypes.InterfaceRegistry + + TxConfig client.TxConfig + AccountRetriever client.AccountRetriever + + AppConstructor AppConstructor // the ABCI application constructor + GenesisState map[string]json.RawMessage // custom genesis state to provide + TimeoutCommit time.Duration // TimeoutCommit: the consensus commitment timeout. + ChainID string // the network chain-id + NumValidators int // the total number of validators to create and bond + Mnemonics []string // custom user-provided validator operator mnemonics + BondDenom string // the staking bond denomination + MinGasPrices string // the minimum gas prices each validator will accept + AccountTokens sdkmath.Int // the amount of unique validator tokens (e.g. 1000node0) + StakingTokens sdkmath.Int // the amount of tokens each validator has available to stake + BondedTokens sdkmath.Int // the amount of tokens each validator stakes + StartingTokens sdk.Coins // Additional tokens to be added to the starting block to validators + PruningStrategy string // the pruning strategy each validator will have + EnableTMLogging bool // enable Tendermint logging to STDOUT + CleanupDir bool // remove base temporary directory during cleanup + SigningAlgo string // signing algorithm for keys + KeyringOptions []keyring.Option // keyring configuration options + RPCAddress string // RPC listen address (including port) + APIAddress string // REST API listen address (including port) + GRPCAddress string // GRPC server listen address (including port) +} + +func (cfg *Config) AbsorbServerConfig(srvCfg *serverconfig.Config) { + cfg.GRPCAddress = srvCfg.GRPC.Address + cfg.APIAddress = srvCfg.API.Address +} + +func (cfg *Config) AbsorbTmConfig(tmCfg *tmconfig.Config) { + cfg.RPCAddress = tmCfg.RPC.ListenAddress +} + +// AbsorbListenAddresses ensures that the listening addresses for an active +// node are set on the network config. +func (cfg *Config) AbsorbListenAddresses(val *Validator) { + cfg.AbsorbServerConfig(val.AppConfig) + cfg.AbsorbTmConfig(val.Ctx.Config) +} diff --git a/x/common/testutil/cli/network_test.go b/x/common/testutil/cli/network_test.go new file mode 100644 index 00000000..4fc2141f --- /dev/null +++ b/x/common/testutil/cli/network_test.go @@ -0,0 +1,139 @@ +// Alteration of [network/network_test.go](https://github.com/cosmos/cosmos-sdk/blob/v0.45.15/testutil/network/network_test.go) +package cli_test + +import ( + "fmt" + "strings" + "testing" + "time" + + sdkcodec "github.com/cosmos/cosmos-sdk/codec" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/app" + + "github.com/stretchr/testify/suite" + + "github.com/archway-network/archway/x/common/testutil/cli" + "github.com/archway-network/archway/x/common/testutil/genesis" +) + +func TestIntegrationTestSuite_RunAll(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +type IntegrationTestSuite struct { + suite.Suite + + network *cli.Network + cfg *cli.Config +} + +func (s *IntegrationTestSuite) SetupSuite() { + /* Make test skip if -short is not used: + All tests: `go test ./...` + Unit tests only: `go test ./... -short` + Integration tests only: `go test ./... -run Integration` + https://stackoverflow.com/a/41407042/13305627 */ + if testing.Short() { + s.T().Skip("skipping integration test suite") + } + s.T().Log("setting up integration test suite") + + encConfig := app.MakeEncodingConfig() + cfg := new(cli.Config) + *cfg = cli.BuildNetworkConfig(genesis.NewTestGenesisState(encConfig)) + network, err := cli.New( + s.T(), + s.T().TempDir(), + *cfg, + ) + s.Require().NoError(err) + s.network = network + + cfg.AbsorbListenAddresses(network.Validators[0]) + s.cfg = cfg + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestNetwork_Liveness() { + height, err := s.network.WaitForHeightWithTimeout(4, time.Minute) + s.Require().NoError(err, "expected to reach 4 blocks; got %d", height) + + err = s.network.WaitForDuration(1 * time.Second) + s.NoError(err) +} + +func (s *IntegrationTestSuite) TestNetwork_LatestHeight() { + height, err := s.network.LatestHeight() + s.NoError(err) + s.Positive(height) + + sadNetwork := new(cli.Network) + _, err = sadNetwork.LatestHeight() + s.Error(err) +} + +func (s *IntegrationTestSuite) TestLogMnemonic() { + kring, algo, nodeDirName := cli.NewKeyring(s.T()) + + var cdc sdkcodec.Codec = app.MakeEncodingConfig().Codec + _, mnemonic, err := sdktestutil.GenerateCoinKey(algo, cdc) + s.NoError(err) + + overwrite := true + _, secret, err := sdktestutil.GenerateSaveCoinKey( + kring, nodeDirName, mnemonic, overwrite, algo, + ) + s.NoError(err) + + cli.LogMnemonic(&mockLogger{ + Logs: []string{}, + }, secret) +} + +func (s *IntegrationTestSuite) TestValidatorGetSecret() { + val := s.network.Validators[0] + secret := val.SecretMnemonic() + secretSlice := val.SecretMnemonicSlice() + s.Equal(secret, strings.Join(secretSlice, " ")) + + kring, algo, nodeDirName := cli.NewKeyring(s.T()) + mnemonic := secret + overwrite := true + addrGenerated, secretGenerated, err := sdktestutil.GenerateSaveCoinKey( + kring, nodeDirName, mnemonic, overwrite, algo, + ) + s.NoError(err) + s.Equal(secret, secretGenerated) + s.Equal(val.Address, addrGenerated) +} + +var _ cli.Logger = (*mockLogger)(nil) + +type mockLogger struct { + Logs []string +} + +func (ml *mockLogger) Log(args ...interface{}) { + ml.Logs = append(ml.Logs, fmt.Sprint(args...)) +} + +func (ml *mockLogger) Logf(format string, args ...interface{}) { + ml.Logs = append(ml.Logs, fmt.Sprintf(format, args...)) +} + +func (s *IntegrationTestSuite) TestNewAccount() { + s.NotPanics(func() { + addr := cli.NewAccount(s.network, "newacc") + s.NoError(sdk.VerifyAddressFormat(addr)) + }) +} diff --git a/x/common/testutil/cli/query.go b/x/common/testutil/cli/query.go new file mode 100644 index 00000000..6f4ceefe --- /dev/null +++ b/x/common/testutil/cli/query.go @@ -0,0 +1,126 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/gogoproto/proto" + + "github.com/cosmos/cosmos-sdk/client" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + + tmcli "github.com/cometbft/cometbft/libs/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" + // oraclecli "github.com/archway-network/archway/x/oracle/client/cli" + // oracletypes "github.com/archway-network/archway/x/oracle/types" + // sudocli "github.com/archway-network/archway/x/sudo/cli" + // sudotypes "github.com/archway-network/archway/x/sudo/types" +) + +// ExecQueryOption defines a type which customizes a CLI query operation. +type ExecQueryOption func(queryOption *queryOptions) + +// queryOptions is an internal type which defines options. +type queryOptions struct { + outputEncoding EncodingType +} + +// EncodingType defines the encoding methodology for requests and responses. +type EncodingType int + +const ( + // EncodingTypeJSON defines the types are JSON encoded or need to be encoded using JSON. + EncodingTypeJSON = iota + // EncodingTypeProto defines the types are proto encoded or need to be encoded using proto. + EncodingTypeProto +) + +// WithQueryEncodingType defines how the response of the CLI query should be decoded. +func WithQueryEncodingType(e EncodingType) ExecQueryOption { + return func(queryOption *queryOptions) { + queryOption.outputEncoding = e + } +} + +func (chain Network) ExecQuery( + cmd *cobra.Command, + args []string, + result proto.Message, + opts ...ExecQueryOption, +) error { + return ExecQuery(chain.Validators[0].ClientCtx, cmd, args, result, opts...) +} + +// ExecQuery executes a CLI query onto the provided Network. +func ExecQuery( + clientCtx client.Context, + cmd *cobra.Command, + args []string, + result proto.Message, + opts ...ExecQueryOption, +) error { + var options queryOptions + for _, o := range opts { + o(&options) + } + switch options.outputEncoding { + case EncodingTypeJSON: + args = append(args, fmt.Sprintf("--%s=json", tmcli.OutputFlag)) + case EncodingTypeProto: + return fmt.Errorf("query proto encoding is not supported") + default: + return fmt.Errorf("unknown query encoding type %d", options.outputEncoding) + } + + resultRaw, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + if err != nil { + return err + } + + switch options.outputEncoding { + case EncodingTypeJSON: + return clientCtx.Codec.UnmarshalJSON(resultRaw.Bytes(), result) + case EncodingTypeProto: + return clientCtx.Codec.Unmarshal(resultRaw.Bytes(), result) + default: + return fmt.Errorf("unrecognized encoding option %v", options.outputEncoding) + } +} + +// func QueryOracleExchangeRate(clientCtx client.Context, pair asset.Pair) (*oracletypes.QueryExchangeRateResponse, error) { +// var queryResp oracletypes.QueryExchangeRateResponse +// if err := ExecQuery(clientCtx, oraclecli.GetCmdQueryExchangeRates(), []string{pair.String()}, &queryResp); err != nil { +// return nil, err +// } +// return &queryResp, nil +// } + +func QueryTx(ctx client.Context, txHash string) (*sdk.TxResponse, error) { + var queryResp sdk.TxResponse + if err := ExecQuery( + ctx, + cli.QueryTxCmd(), + []string{ + txHash, + }, + &queryResp, + ); err != nil { + return nil, err + } + + return &queryResp, nil +} + +// func QuerySudoers(clientCtx client.Context) (*sudotypes.QuerySudoersResponse, error) { +// var queryResp sudotypes.QuerySudoersResponse +// if err := ExecQuery( +// clientCtx, +// sudocli.CmdQuerySudoers(), +// []string{}, +// &queryResp, +// ); err != nil { +// return nil, err +// } +// return &queryResp, nil +// } diff --git a/x/common/testutil/cli/tx.go b/x/common/testutil/cli/tx.go new file mode 100644 index 00000000..61245f16 --- /dev/null +++ b/x/common/testutil/cli/tx.go @@ -0,0 +1,174 @@ +package cli + +import ( + "fmt" + + "cosmossdk.io/math" + "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdktestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" + + "github.com/archway-network/archway/x/common/denoms" +) + +type ExecTxOption func(*execTxOptions) + +func WithTxOptions(newOptions TxOptionChanges) ExecTxOption { + return func(options *execTxOptions) { + if newOptions.BroadcastMode != nil { + options.BroadcastMode = *newOptions.BroadcastMode + } + if newOptions.CanFail != nil { + options.CanFail = *newOptions.CanFail + } + if newOptions.Fees != nil { + options.Fees = *newOptions.Fees + } + if newOptions.Gas != nil { + options.Gas = *newOptions.Gas + } + if newOptions.KeyringBackend != nil { + options.KeyringBackend = *newOptions.KeyringBackend + } + if newOptions.SkipConfirmation != nil { + options.SkipConfirmation = *newOptions.SkipConfirmation + } + } +} + +type TxOptionChanges struct { + BroadcastMode *string + CanFail *bool + Fees *sdk.Coins + Gas *int64 + KeyringBackend *string + SkipConfirmation *bool +} + +type execTxOptions struct { + BroadcastMode string + CanFail bool + Fees sdk.Coins + Gas int64 + KeyringBackend string + SkipConfirmation bool +} + +var DEFAULT_TX_OPTIONS = execTxOptions{ + Fees: sdk.NewCoins(sdk.NewCoin(denoms.NIBI, math.NewInt(1000))), + Gas: 2000000, + SkipConfirmation: true, + BroadcastMode: flags.BroadcastSync, + CanFail: false, + KeyringBackend: keyring.BackendTest, +} + +func (network *Network) ExecTxCmd( + cmd *cobra.Command, from sdk.AccAddress, args []string, opts ...ExecTxOption, +) (*sdk.TxResponse, error) { + if len(network.Validators) == 0 { + return nil, fmt.Errorf("invalid network") + } + + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagFrom, from)) + + options := DEFAULT_TX_OPTIONS + + for _, opt := range opts { + opt(&options) + } + + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, options.BroadcastMode)) + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagFees, options.Fees)) + args = append(args, fmt.Sprintf("--%s=%d", flags.FlagGas, options.Gas)) + args = append(args, fmt.Sprintf("--%s=%s", flags.FlagKeyringBackend, options.KeyringBackend)) + switch options.SkipConfirmation { + case true: + args = append(args, fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation)) + case false: + args = append(args, fmt.Sprintf("--%s=false", flags.FlagSkipConfirmation)) + } + + clientCtx := network.Validators[0].ClientCtx + + rawResp, err := sdktestutil.ExecTestCLICmd(clientCtx, cmd, args) + if err != nil { + return nil, fmt.Errorf("failed to execute tx: %w", err) + } + + err = network.WaitForNextBlock() + if err != nil { + return nil, err + } + + txResp := new(sdk.TxResponse) + clientCtx.Codec.MustUnmarshalJSON(rawResp.Bytes(), txResp) + resp, err := QueryTx(clientCtx, txResp.TxHash) + if err != nil { + return nil, fmt.Errorf("failed to query tx: %w", err) + } + + if options.CanFail { + return resp, nil + } + + if resp.Code != types.CodeTypeOK { + return nil, fmt.Errorf("tx failed with code %d: %s", resp.Code, resp.RawLog) + } + + return resp, nil +} + +// func (chain *Network) BroadcastMsgs( +// from sdk.AccAddress, msgs ...sdk.Msg, +// ) (*sdk.TxResponse, error) { +// cfg := chain.Config +// kb, info, err := chain.keyBaseAndInfoForAddr(from) +// if err != nil { +// return nil, err +// } + +// rpc := chain.Validators[0].RPCClient +// txBuilder := cfg.TxConfig.NewTxBuilder() +// err = txBuilder.SetMsgs(msgs...) +// if err != nil { +// return nil, err +// } + +// txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, math.NewInt(1000)))) +// txBuilder.SetGasLimit(uint64(1 * common.TO_MICRO)) + +// acc, err := cfg.AccountRetriever.GetAccount(chain.Validators[0].ClientCtx, from) +// if err != nil { +// return nil, err +// } + +// txFactory := tx.Factory{} +// txFactory = txFactory. +// WithChainID(cfg.ChainID). +// WithKeybase(kb). +// WithTxConfig(cfg.TxConfig). +// WithAccountRetriever(cfg.AccountRetriever). +// WithAccountNumber(acc.GetAccountNumber()). +// WithSequence(acc.GetSequence()) + +// err = tx.Sign(ctx, txFactory, info.Name, txBuilder, true) +// if err != nil { +// return nil, err +// } + +// txBytes, err := cfg.TxConfig.TxEncoder()(txBuilder.GetTx()) +// if err != nil { +// return nil, err +// } + +// respRaw, err := rpc.BroadcastTxSync(context.Background(), txBytes) +// if err != nil { +// return nil, err +// } + +// return sdk.NewResponseFormatBroadcastTx(respRaw), err +// } diff --git a/x/common/testutil/cli/tx_test.go b/x/common/testutil/cli/tx_test.go new file mode 100644 index 00000000..c615c079 --- /dev/null +++ b/x/common/testutil/cli/tx_test.go @@ -0,0 +1,75 @@ +package cli_test + +// import ( +// "testing" + +// sdk "github.com/cosmos/cosmos-sdk/types" + +// "cosmossdk.io/math" +// bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/cli" +// banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + +// "github.com/archway-network/archway/x/common/denoms" +// "github.com/archway-network/archway/x/common/testutil" +// "github.com/archway-network/archway/x/common/testutil/cli" +// ) + +// func (s *IntegrationTestSuite) TestSendTx() { +// fromAddr := s.network.Validators[0].Address +// toAddr := testutil.AccAddress() +// sendCoin := sdk.NewCoin(denoms.NIBI, math.NewInt(42)) +// txResp, err := s.network.BroadcastMsgs(fromAddr, &banktypes.MsgSend{ +// FromAddress: fromAddr.String(), +// ToAddress: toAddr.String(), +// Amount: sdk.NewCoins(sendCoin), +// }, +// ) +// s.NoError(err) +// s.EqualValues(0, txResp.Code) +// } + +// func (s *IntegrationTestSuite) TestExecTx() { +// fromAddr := s.network.Validators[0].Address +// toAddr := testutil.AccAddress() +// sendCoin := sdk.NewCoin(denoms.NIBI, math.NewInt(69)) +// args := []string{fromAddr.String(), toAddr.String(), sendCoin.String()} +// txResp, err := s.network.ExecTxCmd(bankcli.NewSendTxCmd(), fromAddr, args) +// s.NoError(err) +// s.EqualValues(0, txResp.Code) + +// s.T().Run("test tx option changes", func(t *testing.T) { +// defaultOpts := cli.DEFAULT_TX_OPTIONS +// opts := cli.WithTxOptions(cli.TxOptionChanges{ +// BroadcastMode: &defaultOpts.BroadcastMode, +// CanFail: &defaultOpts.CanFail, +// Fees: &defaultOpts.Fees, +// Gas: &defaultOpts.Gas, +// KeyringBackend: &defaultOpts.KeyringBackend, +// SkipConfirmation: &defaultOpts.SkipConfirmation, +// }) +// txResp, err = s.network.ExecTxCmd(bankcli.NewSendTxCmd(), fromAddr, args, opts) +// s.NoError(err) +// s.EqualValues(0, txResp.Code) +// }) + +// s.T().Run("fail when validators are missing", func(t *testing.T) { +// networkNoVals := new(cli.Network) +// *networkNoVals = *s.network +// networkNoVals.Validators = []*cli.Validator{} +// _, err := networkNoVals.ExecTxCmd(bankcli.NewTxCmd(), fromAddr, args) +// s.Error(err) +// s.Contains(err.Error(), "") +// }) +// } + +// func (s *IntegrationTestSuite) TestFillWalletFromValidator() { +// toAddr := testutil.AccAddress() +// val := s.network.Validators[0] +// funds := sdk.NewCoins( +// sdk.NewInt64Coin(denoms.NIBI, 420), +// ) +// feeDenom := denoms.NIBI +// s.NoError(cli.FillWalletFromValidator( +// toAddr, funds, val, feeDenom, +// )) +// } diff --git a/x/common/testutil/cli/util.go b/x/common/testutil/cli/util.go new file mode 100644 index 00000000..22f8fe0c --- /dev/null +++ b/x/common/testutil/cli/util.go @@ -0,0 +1,311 @@ +package cli + +import ( + "context" + + cmtjson "github.com/cometbft/cometbft/libs/json" + cmtos "github.com/cometbft/cometbft/libs/os" + + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + + "cosmossdk.io/log" + tmtypes "github.com/cometbft/cometbft/abci/types" + sdkcodec "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + "golang.org/x/sync/errgroup" + + "github.com/archway-network/archway/app" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server/api" + servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + gentypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + cometconfig "github.com/cometbft/cometbft/config" + tmos "github.com/cometbft/cometbft/libs/os" + "github.com/cometbft/cometbft/node" + "github.com/cometbft/cometbft/p2p" + pvm "github.com/cometbft/cometbft/privval" + "github.com/cometbft/cometbft/proxy" + "github.com/cometbft/cometbft/rpc/client/local" + "github.com/cometbft/cometbft/types" + tmtime "github.com/cometbft/cometbft/types/time" + servercmtlog "github.com/cosmos/cosmos-sdk/server/log" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +func startInProcess(cfg Config, val *Validator) error { + logger := val.Ctx.Logger + tmCfg := val.Ctx.Config + tmCfg.Instrumentation.Prometheus = false + + if err := val.AppConfig.ValidateBasic(); err != nil { + return err + } + + nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) + if err != nil { + return err + } + + app := cfg.AppConstructor(*val) + cmtApp := server.NewCometABCIWrapper(app) + + genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) + tmNode, err := node.NewNode( + tmCfg, + pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), + nodeKey, + proxy.NewLocalClientCreator(cmtApp), + genDocProvider, + cometconfig.DefaultDBProvider, + node.DefaultMetricsProvider(tmCfg.Instrumentation), + servercmtlog.CometLoggerWrapper{Logger: logger.With("module", val.Moniker)}, + ) + if err != nil { + return err + } + + if err := tmNode.Start(); err != nil { + return err + } + + val.tmNode = tmNode + + if val.RPCAddress != "" { + val.RPCClient = local.New(tmNode) + } + + // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. + if val.APIAddress != "" || val.AppConfig.GRPC.Enable { + val.ClientCtx = val.ClientCtx. + WithClient(val.RPCClient) + + // Add the tx service in the gRPC router. + app.RegisterTxService(val.ClientCtx) + + // Add the tendermint queries service in the gRPC router. + app.RegisterTendermintService(val.ClientCtx) + } + + grpcCfg := val.AppConfig.GRPC + ctx := context.Background() + ctx, val.cancelFn = context.WithCancel(ctx) + val.errGroup, ctx = errgroup.WithContext(ctx) + + if grpcCfg.Enable { + grpcSrv, err := servergrpc.NewGRPCServer(val.ClientCtx, app, grpcCfg) + if err != nil { + return err + } + + // Start the gRPC server in a goroutine. Note, the provided ctx will ensure + // that the server is gracefully shut down. + val.errGroup.Go(func() error { + return servergrpc.StartGRPCServer(ctx, logger.With(log.ModuleKey, "grpc-server"), grpcCfg, grpcSrv) + }) + + val.grpc = grpcSrv + } + + if val.APIAddress != "" { + apiSrv := api.New(val.ClientCtx, logger.With(log.ModuleKey, "api-server"), val.grpc) + app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) + + val.errGroup.Go(func() error { + return apiSrv.Start(ctx, *val.AppConfig) + }) + } + + return nil +} + +func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error { + genTime := tmtime.Now() + + for i := 0; i < cfg.NumValidators; i++ { + tmCfg := vals[i].Ctx.Config + + nodeDir := filepath.Join(outputDir, vals[i].Moniker, "simd") + gentxsDir := filepath.Join(outputDir, "gentxs") + + tmCfg.Moniker = vals[i].Moniker + tmCfg.SetRoot(nodeDir) + + initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].NodeID, vals[i].PubKey) + + genFile := tmCfg.GenesisFile() + appGenesis, err := gentypes.AppGenesisFromFile(genFile) + if err != nil { + return err + } + + appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig, + tmCfg, initCfg, appGenesis, banktypes.GenesisBalancesIterator{}, genutiltypes.DefaultMessageValidator, addresscodec.NewBech32Codec("nibivaloper")) + if err != nil { + return err + } + + // overwrite each validator's genesis file to have a canonical genesis time + appGenesis = gentypes.NewAppGenesisWithVersion(cfg.ChainID, appState) + appGenesis.GenesisTime = genTime + appGenesis.Consensus.Validators = nil + + if err := appGenesis.ValidateAndComplete(); err != nil { + return err + } + + genDocBytes, err := cmtjson.MarshalIndent(appGenesis, "", " ") + if err != nil { + return err + } + return cmtos.WriteFile(genFile, genDocBytes, 0644) + } + + return nil +} + +func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string) error { + // set the accounts in the genesis state + var authGenState authtypes.GenesisState + cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[authtypes.ModuleName], &authGenState) + + accounts, err := authtypes.PackAccounts(genAccounts) + if err != nil { + return err + } + + authGenState.Accounts = append(authGenState.Accounts, accounts...) + cfg.GenesisState[authtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&authGenState) + + // set the balances in the genesis state + var bankGenState banktypes.GenesisState + cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[banktypes.ModuleName], &bankGenState) + + bankGenState.Balances = append(bankGenState.Balances, genBalances...) + cfg.GenesisState[banktypes.ModuleName] = cfg.Codec.MustMarshalJSON(&bankGenState) + + appGenStateJSON, err := json.MarshalIndent(cfg.GenesisState, "", " ") + if err != nil { + return err + } + + genDoc := types.GenesisDoc{ + ChainID: cfg.ChainID, + AppState: appGenStateJSON, + Validators: nil, + } + + // generate empty genesis files for each validator and save + for i := 0; i < cfg.NumValidators; i++ { + if err := genDoc.SaveAs(genFiles[i]); err != nil { + return err + } + } + + return nil +} + +func writeFile(name string, dir string, contents []byte) error { + writePath := filepath.Join(dir) //nolint:gocritic + file := filepath.Join(writePath, name) + + err := tmos.EnsureDir(writePath, 0o755) + if err != nil { + return err + } + + err = os.WriteFile(file, contents, 0o644) // nolint: gosec + if err != nil { + return err + } + + return nil +} + +// FillWalletFromValidator fills the wallet with some coins that come from the +// validator. +func FillWalletFromValidator( + addr sdk.AccAddress, balance sdk.Coins, val *Validator, feesDenom string, +) error { + rawResp, err := clitestutil.MsgSendExec( + val.ClientCtx, + val.Address, + addr, + balance, + addresscodec.NewBech32Codec("nibi"), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewInt64Coin(feesDenom, 10000)), + ) + if err != nil { + return err + } + return txOK(val.ClientCtx.Codec, rawResp.Bytes()) +} + +func txOK(jsonCodec sdkcodec.JSONCodec, txBytes []byte) error { + resp := new(sdk.TxResponse) + jsonCodec.MustUnmarshalJSON(txBytes, resp) + if resp.Code != tmtypes.CodeTypeOK { + return fmt.Errorf("%s", resp.RawLog) + } + + return nil +} + +/* +NewAccount Creates a new account with a random mnemonic, stores the mnemonic in the keyring, and returns the address. + +args: + - network: the network in which to create the account and key + - uid: a unique identifier to ensure duplicate accounts are not created + +ret: + - addr: the address of the new account +*/ +func NewAccount(network *Network, uid string) sdk.AccAddress { + val := network.Validators[0] + + // create a new user address + info, _, err := val.ClientCtx.Keyring.NewMnemonic( + /* uid */ uid, + /* language */ keyring.English, + /* hdPath */ sdk.FullFundraiserPath, + /* big39Passphrase */ "", + /* algo */ hd.Secp256k1, + ) + if err != nil { + panic(err) + } + + addr, err := info.GetAddress() + if err != nil { + panic(err) + } + return addr +} + +func NewKeyring(t *testing.T) ( + kring keyring.Keyring, + algo keyring.SignatureAlgo, + nodeDirName string, +) { + var cdc sdkcodec.Codec = app.MakeEncodingConfig().Codec + kring = keyring.NewInMemory(cdc) + nodeDirName = t.TempDir() + algo = hd.Secp256k1 + return kring, algo, nodeDirName +} diff --git a/x/common/testutil/client_ctx.go b/x/common/testutil/client_ctx.go new file mode 100644 index 00000000..8536b968 --- /dev/null +++ b/x/common/testutil/client_ctx.go @@ -0,0 +1,40 @@ +package testutil + +import ( + "context" + "testing" + + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" +) + +// SetupClientCtx configures the client and server contexts and returns the +// resultant 'context.Context'. This is useful for executing CLI commands. +func SetupClientCtx(t *testing.T) context.Context { + home := t.TempDir() + logger := log.NewNopLogger() + cfg, err := genutiltest.CreateDefaultCometConfig(home) + require.NoError(t, err) + + appCodec := moduletestutil.MakeTestEncodingConfig().Codec + testModuleBasicManager := module.NewBasicManager(genutil.AppModuleBasic{}) + err = genutiltest.ExecInitCmd( + testModuleBasicManager, home, appCodec) + require.NoError(t, err) + + serverCtx := server.NewContext(viper.New(), cfg, logger) + clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) + + ctx := context.Background() + ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) + ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) + return ctx +} diff --git a/x/common/testutil/const.go b/x/common/testutil/const.go new file mode 100644 index 00000000..7366624c --- /dev/null +++ b/x/common/testutil/const.go @@ -0,0 +1,6 @@ +package testutil + +const ( + ADDR_GUARD_CREAM = "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl" + ADDR_SUDO_ROOT = "nibi1qqx5reauy4glpskmppy88pz25qp2py5yxvpxdt" +) diff --git a/x/common/testutil/events.go b/x/common/testutil/events.go new file mode 100644 index 00000000..bff84691 --- /dev/null +++ b/x/common/testutil/events.go @@ -0,0 +1,112 @@ +package testutil + +import ( + "fmt" + "reflect" + "strings" + + "github.com/cosmos/gogoproto/proto" + + "github.com/archway-network/archway/x/common/set" + + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +// FilterNewEvents returns only the new events from afterEvents that were not present in beforeEvents +func FilterNewEvents(beforeEvents, afterEvents sdk.Events) sdk.Events { + newEvents := make(sdk.Events, 0) + + for _, afterEvent := range afterEvents { + found := false + for _, beforeEvent := range beforeEvents { + if reflect.DeepEqual(afterEvent, beforeEvent) { + found = true + break + } + } + if !found { + newEvents = append(newEvents, afterEvent) + } + } + + return newEvents +} + +// AssertEventsPresent: Errors if the given event type is not present in events +func AssertEventPresent(events sdk.Events, eventType string) error { + foundTypes := set.New[string]() + for _, event := range events { + if event.Type == eventType { + return nil + } + foundTypes.Add(event.Type) + } + return fmt.Errorf("event \"%s\" not found within set: %s", eventType, foundTypes.ToSlice()) +} + +// AssertEventsPresent: Errors if the given event types are not present in events +func AssertEventsPresent(events sdk.Events, eventTypes []string) (err error) { + for _, eventType := range eventTypes { + err := AssertEventPresent(events, eventType) + if err != nil { + return err + } + } + return +} + +// RequireNotHasTypedEvent: Error if an event type matches the proto.Message name +func RequireNotHasTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Message) { + name := proto.MessageName(event) + for _, ev := range ctx.EventManager().Events() { + if ev.Type == name { + t.Errorf("unexpected event found: %s", name) + } + } +} + +func RequireContainsTypedEvent(t require.TestingT, ctx sdk.Context, event proto.Message) { + foundEvents := []proto.Message{} + for _, abciEvent := range ctx.EventManager().Events() { + eventType := proto.MessageName(event) + if abciEvent.Type != eventType { + continue + } + typedEvent, err := sdk.ParseTypedEvent(abci.Event{ + Type: abciEvent.Type, + Attributes: abciEvent.Attributes, + }) + require.NoError(t, err) + + if reflect.DeepEqual(typedEvent, event) { + return + } else { + foundEvents = append(foundEvents, typedEvent) + } + } + + t.Errorf("event not found, event: %+v, found events: %+v", event, foundEvents) +} + +// EventHasAttributeValue parses the given ABCI event at a key to see if it +// matches (contains) the wanted value. +// +// Args: +// - abciEvent: The event under test +// - key: The key for which we'll check the value +// - want: The desired value +func EventHasAttributeValue(abciEvent sdk.Event, key string, want string) error { + attr, ok := abciEvent.GetAttribute(key) + if !ok { + return fmt.Errorf("abci event does not contain key: %s", key) + } + got := attr.Value + + if !strings.Contains(got, want) { + return fmt.Errorf("expected %s %s, got %s", key, want, got) + } + + return nil +} diff --git a/x/common/testutil/events_test.go b/x/common/testutil/events_test.go new file mode 100644 index 00000000..1bded59e --- /dev/null +++ b/x/common/testutil/events_test.go @@ -0,0 +1,75 @@ +package testutil_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/testutil" + "github.com/archway-network/archway/x/common/testutil/testapp" +) + +func (s *TestSuite) TestEventsUtils() { + bapp, ctx := testapp.NewNibiruTestAppAndContext() + + // Events on the ctx before we broadcast any txs + var beforeEvents sdk.Events = ctx.EventManager().Events() + + newCoins := func(coinsStr string) sdk.Coins { + out, err := sdk.ParseCoinsNormalized(coinsStr) + if err != nil { + panic(err) + } + return out + } + + funds := sdk.NewCoins(sdk.NewInt64Coin(denoms.NIBI, 5_000_000)) + _, addrs := testutil.PrivKeyAddressPairs(2) + senderAddr, otherAddr := addrs[0], addrs[1] + err := testapp.FundAccount(bapp.BankKeeper, ctx, senderAddr, funds) + s.NoError(err) + + s.NoError( + bapp.BankKeeper.SendCoins(ctx, senderAddr, otherAddr, newCoins("12unibi")), + ) + + // Events on the ctx after broadcasting tx + var sdkEvents sdk.Events = ctx.EventManager().Events() + + s.Run("AssertEventsPresent", func() { + err = testutil.AssertEventsPresent(sdkEvents, + []string{"transfer", "coin_received", "message", "coin_spent"}, + ) + s.NoError(err) + s.Error( + testutil.AssertEventsPresent(sdkEvents, []string{"foobar"}), + ) + }) + + s.Run("EventHasAttributeValue", func() { + var transferEvent sdk.Event + for _, abciEvent := range sdkEvents { + if abciEvent.Type == "transfer" { + transferEvent = abciEvent + } + } + for _, err := range []error{ + testutil.EventHasAttributeValue(transferEvent, "sender", senderAddr.String()), + testutil.EventHasAttributeValue(transferEvent, "recipient", otherAddr.String()), + testutil.EventHasAttributeValue(transferEvent, "amount", "12unibi"), + } { + s.NoError(err) + } + }) + + s.Run("FilterNewEvents", func() { + newEvents := testutil.FilterNewEvents(beforeEvents, sdkEvents) + lenBefore := len(beforeEvents) + lenAfter := len(sdkEvents) + lenNew := len(newEvents) + s.Equal(lenAfter-lenNew, lenBefore) + + expectedNewEvents := sdkEvents[lenBefore:lenAfter] + s.Len(expectedNewEvents, lenNew) + s.ElementsMatch(newEvents, expectedNewEvents) + }) +} diff --git a/x/common/testutil/genesis/genesis.go b/x/common/testutil/genesis/genesis.go new file mode 100644 index 00000000..b44de735 --- /dev/null +++ b/x/common/testutil/genesis/genesis.go @@ -0,0 +1,35 @@ +package genesis + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + gov "github.com/cosmos/cosmos-sdk/x/gov/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/testutil/testapp" +) + +/* + NewTestGenesisState returns 'NewGenesisState' using the default + +genesis as input. The blockchain genesis state is represented as a map from module +identifier strings to raw json messages. +*/ +func NewTestGenesisState(encodingConfig app.EncodingConfig) app.GenesisState { + codec := encodingConfig.Codec + genState := app.NewDefaultGenesisState(codec) + + // Set short voting period to allow fast gov proposals in tests + var govGenState govtypes.GenesisState + codec.MustUnmarshalJSON(genState[gov.ModuleName], &govGenState) + *govGenState.Params.VotingPeriod = time.Second * 20 + govGenState.Params.MinDeposit = sdk.NewCoins(sdk.NewInt64Coin(denoms.NIBI, 1_000_000)) // min deposit of 1 NIBI + genState[gov.ModuleName] = codec.MustMarshalJSON(&govGenState) + + testapp.SetDefaultSudoGenesis(genState) + + return genState +} diff --git a/x/common/testutil/genesis/oracle_genesis.go b/x/common/testutil/genesis/oracle_genesis.go new file mode 100644 index 00000000..204ab614 --- /dev/null +++ b/x/common/testutil/genesis/oracle_genesis.go @@ -0,0 +1,27 @@ +package genesis + +// import ( +// "cosmossdk.io/math" + +// "github.com/archway-network/archway/app" +// "github.com/archway-network/archway/x/common/asset" +// "github.com/archway-network/archway/x/common/denoms" +// oracletypes "github.com/archway-network/archway/x/oracle/types" +// ) + +// func AddOracleGenesis(gen app.GenesisState) app.GenesisState { +// gen[oracletypes.ModuleName] = app.MakeEncodingConfig().Codec. +// MustMarshalJSON(OracleGenesis()) +// return gen +// } + +// func OracleGenesis() *oracletypes.GenesisState { +// oracleGenesis := oracletypes.DefaultGenesisState() +// oracleGenesis.ExchangeRates = []oracletypes.ExchangeRateTuple{ +// {Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD), ExchangeRate: math.LegacyNewDec(1_000)}, +// {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: math.LegacyNewDec(10)}, +// } +// oracleGenesis.Params.VotePeriod = 1_000 + +// return oracleGenesis +// } diff --git a/x/common/testutil/genesis/sudo_genesis.go b/x/common/testutil/genesis/sudo_genesis.go new file mode 100644 index 00000000..afcff7b7 --- /dev/null +++ b/x/common/testutil/genesis/sudo_genesis.go @@ -0,0 +1,41 @@ +package genesis + +// import ( +// sdk "github.com/cosmos/cosmos-sdk/types" + +// "github.com/archway-network/archway/app" + +// "github.com/archway-network/archway/x/sudo" +// sudotypes "github.com/archway-network/archway/x/sudo/types" + +// cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + +// "github.com/archway-network/archway/x/common/testutil" +// ) + +// func AddSudoGenesis(gen app.GenesisState) ( +// genState app.GenesisState, +// rootPrivKey cryptotypes.PrivKey, +// rootAddr sdk.AccAddress, +// ) { +// sudoGenesis, rootPrivKey, rootAddr := SudoGenesis() +// gen[sudotypes.ModuleName] = app.MakeEncodingConfig().Codec. +// MustMarshalJSON(sudoGenesis) +// return gen, rootPrivKey, rootAddr +// } + +// func SudoGenesis() ( +// genState *sudotypes.GenesisState, +// rootPrivKey cryptotypes.PrivKey, +// rootAddr sdk.AccAddress, +// ) { +// sudoGenesis := sudo.DefaultGenesis() + +// // Set the root user +// privKeys, addrs := testutil.PrivKeyAddressPairs(1) +// rootPrivKey = privKeys[0] +// rootAddr = addrs[0] +// sudoGenesis.Sudoers.Root = rootAddr.String() + +// return sudoGenesis, rootPrivKey, rootAddr +// } diff --git a/x/common/testutil/mock/mocks.go b/x/common/testutil/mock/mocks.go new file mode 100644 index 00000000..8ed4d3fd --- /dev/null +++ b/x/common/testutil/mock/mocks.go @@ -0,0 +1,42 @@ +package mock + +import ( + "testing" + + sdktestsmocks "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + gomock "github.com/golang/mock/gomock" +) + +/* + AppendCtxWithMockLogger sets the logger for an input context as a mock logger + +with 'EXPECT' statements. This enables testing on functions logged to the context. +For example, + +```go +// This is a passing test example +import ( + + gomock "github.com/golang/mock/gomock" + sdktestsmocks "github.com/cosmos/cosmos-sdk/tests/mocks" + +) + + // assume t is a *testing.T variable. + ctx, logger := AppendCtxWithMockLogger(t, ctx) + logger.EXPECT().Debug("debug") + logger.EXPECT().Info("info") + logger.EXPECT().Error("error") + + ctx.Logger().Debug("debug") + ctx.Logger().Info("info") + ctx.Logger().Error("error") + +``` +*/ +func AppendCtxWithMockLogger(t *testing.T, ctx sdk.Context) (sdk.Context, *sdktestsmocks.MockLogger) { + ctrl := gomock.NewController(t) + logger := sdktestsmocks.NewMockLogger(ctrl) + return ctx.WithLogger(logger), logger +} diff --git a/x/common/testutil/nullify.go b/x/common/testutil/nullify.go new file mode 100644 index 00000000..202b59e6 --- /dev/null +++ b/x/common/testutil/nullify.go @@ -0,0 +1,57 @@ +// Package nullify provides methods to init nil values structs for test assertion. +package testutil + +import ( + "reflect" + "unsafe" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + coinType = reflect.TypeOf(sdk.Coin{}) + coinsType = reflect.TypeOf(sdk.Coins{}) +) + +// Fill analyze all struct fields and slices with +// reflection and initialize the nil and empty slices, +// structs, and pointers. +func Fill(x interface{}) interface{} { + v := reflect.Indirect(reflect.ValueOf(x)) + switch v.Kind() { + case reflect.Slice: + for i := 0; i < v.Len(); i++ { + obj := v.Index(i) + objPt := reflect.NewAt(obj.Type(), unsafe.Pointer(obj.UnsafeAddr())).Interface() + objPt = Fill(objPt) + obj.Set(reflect.ValueOf(objPt)) + } + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f := reflect.Indirect(v.Field(i)) + if !f.CanSet() { + continue + } + switch f.Kind() { + case reflect.Slice: + f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + case reflect.Struct: + switch f.Type() { + case coinType: + coin := reflect.New(coinType).Interface() + s := reflect.ValueOf(coin).Elem() + f.Set(s) + case coinsType: + coins := reflect.New(coinsType).Interface() + s := reflect.ValueOf(coins).Elem() + f.Set(s) + default: + objPt := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Interface() + s := Fill(objPt) + f.Set(reflect.ValueOf(s)) + } + } + } + } + return reflect.Indirect(v).Interface() +} diff --git a/x/common/testutil/path.go b/x/common/testutil/path.go new file mode 100644 index 00000000..3a607d7f --- /dev/null +++ b/x/common/testutil/path.go @@ -0,0 +1,19 @@ +package testutil + +import ( + "path" + "path/filepath" + "runtime" +) + +// GetPackageDir: Returns the absolute path of the Golang package that +// calls this function. +func GetPackageDir() (string, error) { + // Get the import path of the current package + _, filename, _, _ := runtime.Caller(0) + pkgDir := path.Dir(filename) + pkgPath := path.Join(path.Base(pkgDir), "..") + + // Get the directory path of the package + return filepath.Abs(pkgPath) +} diff --git a/x/common/testutil/sample.go b/x/common/testutil/sample.go new file mode 100644 index 00000000..fe9013bd --- /dev/null +++ b/x/common/testutil/sample.go @@ -0,0 +1,84 @@ +package testutil + +import ( + "math/rand" + + "cosmossdk.io/log" + "cosmossdk.io/store" + "cosmossdk.io/store/metrics" + "cosmossdk.io/store/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// AccAddress returns a sample address (sdk.AccAddress) created using secp256k1. +// Note that AccAddress().String() can be used to get a string representation. +func AccAddress() sdk.AccAddress { + _, accAddr := PrivKey() + return accAddr +} + +// PrivKey returns a private key and corresponding on-chain address. +func PrivKey() (*secp256k1.PrivKey, sdk.AccAddress) { + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + addr := pubKey.Address() + return privKey, sdk.AccAddress(addr) +} + +// PrivKeyAddressPairs generates (deterministically) a total of n private keys +// and addresses. +func PrivKeyAddressPairs(n int) (keys []cryptotypes.PrivKey, addrs []sdk.AccAddress) { + r := rand.New(rand.NewSource(12345)) // make the generation deterministic + keys = make([]cryptotypes.PrivKey, n) + addrs = make([]sdk.AccAddress, n) + for i := 0; i < n; i++ { + secret := make([]byte, 32) + _, err := r.Read(secret) + if err != nil { + panic("Could not read randomness") + } + keys[i] = secp256k1.GenPrivKeyFromSecret(secret) + addrs[i] = sdk.AccAddress(keys[i].PubKey().Address()) + } + return +} + +func BlankContext(storeKeyName string) sdk.Context { + storeKey := types.NewKVStoreKey(storeKeyName) + db := dbm.NewMemDB() + stateStore := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + stateStore.MountStoreWithDB(storeKey, types.StoreTypeIAVL, db) + ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) + return ctx +} + +type TypeLatin struct { + Letters string + CapLetters string + Numbers string +} + +var Latin = TypeLatin{ + Letters: "abcdefghijklmnopqrstuvwxyz", + CapLetters: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + Numbers: "0123456789", +} + +func RandLetters(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = Latin.Letters[rand.Intn(len(Latin.Letters))] + } + return string(b) +} + +func GovModuleAddr() sdk.AccAddress { + return authtypes.NewModuleAddress(govtypes.ModuleName) +} diff --git a/x/common/testutil/testapp/test_util.go b/x/common/testutil/testapp/test_util.go new file mode 100644 index 00000000..b3953f39 --- /dev/null +++ b/x/common/testutil/testapp/test_util.go @@ -0,0 +1,127 @@ +package testapp + +import ( + "time" + + "cosmossdk.io/math" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/testutil/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + nibiruapp "github.com/archway-network/archway/app" +) + +// GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts +// that also act as delegators. +func GenesisStateWithSingleValidator(codec codec.Codec, genesisState nibiruapp.GenesisState) (nibiruapp.GenesisState, error) { + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + if err != nil { + return nil, err + } + + // create validator set with single validator + validator := tmtypes.NewValidator(pubKey, 1) + valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) + + // generate genesis account + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + + var bankGenesis banktypes.GenesisState + codec.MustUnmarshalJSON(genesisState[banktypes.ModuleName], &bankGenesis) + balances := bankGenesis.Balances + + balances = append(balances, banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100000000000000))), + }) + + genesisState, err = genesisStateWithValSet(codec, genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...) + if err != nil { + return nil, err + } + + return genesisState, nil +} + +func genesisStateWithValSet( + cdc codec.Codec, + genesisState nibiruapp.GenesisState, + valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, + balances ...banktypes.Balance, +) (nibiruapp.GenesisState, error) { + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = cdc.MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.DefaultPowerReduction + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) + if err != nil { + return nil, err + } + pkAny, err := codectypes.NewAnyWithValue(pk) + if err != nil { + return nil, err + } + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: math.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), + MinSelfDelegation: math.ZeroInt(), + } + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), math.LegacyOneDec())) + } + // set validators and delegations + stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) + genesisState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingGenesis) + + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens to total supply + totalSupply = totalSupply.Add(b.Coins...) + } + + for range delegations { + // add delegated tokens to total supply + totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)) + } + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)}, + }) + + // update total supply + bankGenesis := banktypes.NewGenesisState( + banktypes.DefaultGenesisState().Params, + balances, + totalSupply, + []banktypes.Metadata{}, + []banktypes.SendEnabled{}, + ) + genesisState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenesis) + + return genesisState, nil +} diff --git a/x/common/testutil/testapp/testapp.go b/x/common/testutil/testapp/testapp.go new file mode 100644 index 00000000..b99bc632 --- /dev/null +++ b/x/common/testutil/testapp/testapp.go @@ -0,0 +1,175 @@ +package testapp + +import ( + "encoding/json" + + "time" + + ibctransfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + + "cosmossdk.io/log" + abci "github.com/cometbft/cometbft/abci/types" + tmdb "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/testutil" + // epochstypes "github.com/archway-network/archway/x/epochs/types" + // inflationtypes "github.com/archway-network/archway/x/inflation/types" + // sudotypes "github.com/archway-network/archway/x/sudo/types" +) + +func init() { + EnsureNibiruPrefix() +} + +// NewNibiruTestAppAndContext creates an 'app.ArchwayApp' instance with an +// in-memory 'tmdb.MemDB' and fresh 'sdk.Context'. +func NewNibiruTestAppAndContext() (*app.ArchwayApp, sdk.Context) { + // Prevent "invalid Bech32 prefix; expected nibi, got ...." error + EnsureNibiruPrefix() + + // Set up base app + encoding := app.MakeEncodingConfig() + var appGenesis app.GenesisState = app.NewDefaultGenesisState(encoding.Codec) + // genModEpochs := epochstypes.DefaultGenesisFromTime(time.Now().UTC()) + + // // Set happy genesis: epochs + // appGenesis[epochstypes.ModuleName] = encoding.Codec.MustMarshalJSON( + // genModEpochs, + // ) + + // Set happy genesis: sudo + // sudoGenesis := new(sudotypes.GenesisState) + // sudoGenesis.Sudoers = DefaultSudoers() + // appGenesis[sudotypes.ModuleName] = encoding.Codec.MustMarshalJSON(sudoGenesis) + + app := NewNibiruTestApp(appGenesis) + ctx := NewContext(app) + + // Set defaults for certain modules. + // app.OracleKeeper.SetPrice(ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), math.LegacyNewDec(20000)) + // app.OracleKeeper.SetPrice(ctx, "xxx:yyy", math.LegacyNewDec(20000)) + // app.SudoKeeper.Sudoers.Set(ctx, DefaultSudoers()) + + return app, ctx +} + +// NewContext: Returns a fresh sdk.Context corresponding to the given NibiruApp. +func NewContext(nibiru *app.ArchwayApp) sdk.Context { + return nibiru.NewContext(false) +} + +// DefaultSudoers: State for the x/sudo module for the default test app. +// func DefaultSudoers() sudotypes.Sudoers { +// addr := DefaultSudoRoot().String() +// return sudotypes.Sudoers{ +// Root: addr, +// Contracts: []string{addr}, +// } +// } + +func DefaultSudoRoot() sdk.AccAddress { + return sdk.MustAccAddressFromBech32(testutil.ADDR_SUDO_ROOT) +} + +// SetDefaultSudoGenesis: Sets the sudo module genesis state to a valid +// default. See "DefaultSudoers". +func SetDefaultSudoGenesis(gen app.GenesisState) { + // sudoGen := new(sudotypes.GenesisState) + // encoding := app.MakeEncodingConfig() + // encoding.Codec.MustUnmarshalJSON(gen[sudotypes.ModuleName], sudoGen) + // if err := sudoGen.Validate(); err != nil { + // sudoGen.Sudoers = DefaultSudoers() + // gen[sudotypes.ModuleName] = encoding.Codec.MustMarshalJSON(sudoGen) + // } +} + +// NewNibiruTestAppAndZeroTimeCtx: Runs NewNibiruTestAppAndZeroTimeCtx with the +// block time set to time zero. +func NewNibiruTestAppAndContextAtTime(startTime time.Time) (*app.ArchwayApp, sdk.Context) { + app, _ := NewNibiruTestAppAndContext() + ctx := NewContext(app).WithBlockTime(startTime) + return app, ctx +} + +// NewNibiruTestApp initializes a chain with the given genesis state to +// creates an application instance ('app.ArchwayApp'). This app uses an +// in-memory database ('tmdb.MemDB') and has logging disabled. +func NewNibiruTestApp(gen app.GenesisState, baseAppOptions ...func(*baseapp.BaseApp)) *app.ArchwayApp { + db := tmdb.NewMemDB() + logger := log.NewNopLogger() + + encoding := app.MakeEncodingConfig() + SetDefaultSudoGenesis(gen) + + application := app.NewArchwayApp( + logger, + db, + /*traceStore=*/ nil, + /*loadLatest=*/ true, + encoding, + /*appOpts=*/ sims.EmptyAppOptions{}, + baseAppOptions..., + ) + + gen, err := GenesisStateWithSingleValidator(encoding.Codec, gen) + if err != nil { + panic(err) + } + + stateBytes, err := json.MarshalIndent(gen, "", " ") + if err != nil { + panic(err) + } + + application.InitChain(&abci.RequestInitChain{ + ConsensusParams: sims.DefaultConsensusParams, + AppStateBytes: stateBytes, + }) + + return application +} + +// FundAccount is a utility function that funds an account by minting and +// sending the coins to the address. This should be used for testing purposes +// only! +func FundAccount( + bankKeeper bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, + amounts sdk.Coins, +) error { + if err := bankKeeper.MintCoins(ctx, ibctransfertypes.ModuleName, amounts); err != nil { + return err + } + + return bankKeeper.SendCoinsFromModuleToAccount(ctx, ibctransfertypes.ModuleName, addr, amounts) +} + +// FundModuleAccount is a utility function that funds a module account by +// minting and sending the coins to the address. This should be used for testing +// purposes only! +func FundModuleAccount( + bankKeeper bankkeeper.Keeper, ctx sdk.Context, + recipientMod string, amounts sdk.Coins, +) error { + // if err := bankKeeper.MintCoins(ctx, inflationtypes.ModuleName, amounts); err != nil { + // return err + // } + + // return bankKeeper.SendCoinsFromModuleToModule(ctx, inflationtypes.ModuleName, recipientMod, amounts) + return nil +} + +// EnsureNibiruPrefix sets the account address prefix to Nibiru's rather than +// the default from the Cosmos-SDK, guaranteeing that tests will work with nibi +// addresses rather than cosmos ones (for Gaia). +func EnsureNibiruPrefix() { + csdkConfig := sdk.GetConfig() + archwayPrefix := app.Bech32PrefixAccAddr + if csdkConfig.GetBech32AccountAddrPrefix() != archwayPrefix { + app.SetPrefixes() + } +} diff --git a/x/common/testutil/testutil_test.go b/x/common/testutil/testutil_test.go new file mode 100644 index 00000000..cb93a301 --- /dev/null +++ b/x/common/testutil/testutil_test.go @@ -0,0 +1,126 @@ +package testutil_test + +import ( + "context" + "os/exec" + "path" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/common/set" + "github.com/archway-network/archway/x/common/testutil" +) + +type TestSuite struct { + suite.Suite +} + +func TestTestSuite(t *testing.T) { + suite.Run(t, new(TestSuite)) +} + +func (s *TestSuite) TestGetPackageDir() { + pkgDir, err := testutil.GetPackageDir() + s.NoError(err) + s.Equal("testutil", path.Base(pkgDir)) + s.Equal("common", path.Base(path.Dir(pkgDir))) +} + +// TestSampleFns: Tests functions that generate test data from sample.go +func (s *TestSuite) TestSampleFns() { + s.T().Log("consecutive calls give different addrs") + addrs := set.New[string]() + for times := 0; times < 16; times++ { + newAddr := testutil.AccAddress().String() + s.False(addrs.Has(newAddr)) + addrs.Add(newAddr) + } +} + +func (s *TestSuite) TestPrivKeyAddressPairs() { + s.T().Log("calls should be deterministic") + keysA, addrsA := testutil.PrivKeyAddressPairs(4) + keysB, addrsB := testutil.PrivKeyAddressPairs(4) + s.Equal(keysA, keysB) + s.Equal(addrsA, addrsB) +} + +func (s *TestSuite) TestBlankContext() { + ctx := testutil.BlankContext("new-kv-store-key") + goCtx := sdk.WrapSDKContext(ctx) + + freshGoCtx := context.Background() + s.Require().Panics(func() { sdk.UnwrapSDKContext(freshGoCtx) }) + + s.Require().NotPanics(func() { sdk.UnwrapSDKContext(goCtx) }) +} + +func (s *TestSuite) TestNullifyFill() { + for _, tc := range []struct { + name string + input any + want any + }{ + { + name: "nullify fill slice", + input: []string{}, + want: make([]string, 0), + }, + { + name: "nullify fill struct with coins", + input: struct { + Coins sdk.Coins + Strings []string + }{}, + want: struct { + Coins sdk.Coins + Strings []string + }{ + Coins: sdk.Coins(nil), + Strings: []string(nil), + }, + }, + { + name: "nullify fill sdk.Coin struct", + input: struct { + Coin sdk.Coin + Ints []int + }{}, + want: struct { + Coin sdk.Coin + Ints []int + }{ + Coin: sdk.Coin{}, + Ints: []int(nil), + }, + }, + { + name: "nullify fill pointer to null concrete", + input: new(sdk.Coin), + want: sdk.Coin{}, + }, + } { + s.Run(tc.name, func() { + got := testutil.Fill(tc.input) + s.EqualValues(tc.want, got) + }) + } +} + +func (s *TestSuite) TestSetupClientCtx() { + goCtx := testutil.SetupClientCtx(s.T()) + trivialCobraCommand := &cobra.Command{ + Use: "run-true", + Short: "Runs the Unix command, 'true'", + RunE: func(cmd *cobra.Command, args []string) error { + return exec.Command("true").Run() + }, + } + + err := trivialCobraCommand.ExecuteContext(goCtx) + s.NoError(err) +} diff --git a/x/oracle/README.md b/x/oracle/README.md new file mode 100644 index 00000000..b92898cd --- /dev/null +++ b/x/oracle/README.md @@ -0,0 +1,330 @@ +# Oracle + +The Oracle module provides the Nibiru blockchain with an up-to-date and accurate price feed of exchange rates of trading pairs. + +As price information is extrinsic to the blockchain, the Nibiru network relies on validators to periodically vote on the current exchange rates, with the protocol tallying up the results once per `VotePeriod` and updating the on-chain exchange rate as the weighted median of the ballot. + +> Since the Oracle service is powered by validators, you may find it interesting to look at the [Staking](https://github.com/cosmos/cosmos-sdk/tree/master/x/staking/spec/README.md) module, which covers the logic for staking and validators. + +## Contents +- [Oracle](#oracle) + - [Concepts](#concepts) + - [Voting Procedure](#voting-procedure) + - [Reward Band](#reward-band) + - [Slashing](#slashing) + - [Abstaining from Voting](#abstaining-from-voting) + - [Messages](#messages) + - [Module Parameters](#module-parameters) + - [State](#state) + - [ExchangeRate](#exchangerate) + - [FeederDelegation](#feederdelegation) + - [MissCounter](#misscounter) + - [AggregateExchangeRatePrevote](#aggregateexchangerateprevote) + - [AggregateExchangeRateVote](#aggregateexchangeratevote) + - [End Block](#end-block) + - [Tally Exchange Rate Votes](#tally-exchange-rate-votes) + - [Messages](#messages-1) + - [MsgAggregateExchangeRatePrevote](#msgaggregateexchangerateprevote) + - [MsgAggregateExchangeRateVote](#msgaggregateexchangeratevote) + - [MsgDelegateFeedConsent](#msgdelegatefeedconsent) + - [Events](#events) + - [EndBlocker](#endblocker) + - [Events for MsgExchangeRatePrevote](#events-for-msgexchangerateprevote) + - [Events for MsgExchangeRateVote](#events-for-msgexchangeratevote) + - [Events for MsgDelegateFeedConsent](#events-for-msgdelegatefeedconsent) + - [Events for MsgAggregateExchangeRatePrevote](#events-for-msgaggregateexchangerateprevote) + - [Events for MsgAggregateExchangeRateVote](#events-for-msgaggregateexchangeratevote) + +--- + +## Concepts + +See [docs.nibiru.fi/ecosystem/oracle](https://docs.nibiru.fi/ecosystem/oracle/). + +### Voting Procedure + +During each `VotePeriod`, the Oracle module obtains consensus on the exchange rate of pairs specified in `Whitelist` by requiring all members of the validator set to submit a vote for exchange rates before the end of the interval. + +Validators must first pre-commit to a exchange rate, then in the subsequent `VotePeriod` submit and reveal their exchange rate alongside a proof that they had pre-commited at that price. This scheme forces the voter to commit to a submission before knowing the votes of others and thereby reduces centralization and free-rider risk in the Oracle. + +* Prevote and Vote + + Let `P_t` be the current time interval of duration defined by `VotePeriod` (currently set to 30 seconds) during which validators must submit two messages: + + * A `MsgAggregateExchangeRatePrevote`, containing the SHA256 hash of the exchange rates of pairs. A prevote must be submitted for all pairs. + * A `MsgAggregateExchangeRateVote`, containing the salt used to create the hash for the aggregate prevote submitted in the previous interval `P_t-1`. + +* Vote Tally + + At the end of `P_t`, the submitted votes are tallied. + + The submitted salt of each vote is used to verify consistency with the prevote submitted by the validator in `P_t-1`. If the validator has not submitted a prevote, or the SHA256 resulting from the salt does not match the hash from the prevote, the vote is dropped. + + For each pair, if the total voting power of submitted votes exceeds 50%, the weighted median of the votes is recorded on-chain as the effective exchange rate for the following `VotePeriod` `P_t+1`. + + Exchange rates receiving fewer than `VoteThreshold` total voting power have their exchange rates deleted from the store, and no exchange rate will exist for the next VotePeriod `P_t+1`. + +* Ballot Rewards + + After the votes are tallied, the winners of the ballots are determined with `tally()`. + + Voters that have managed to vote within a narrow band around the weighted median, are rewarded with a portion of the collected seigniorage. See `k.RewardBallotWinners()` for more details. + +### Reward Band + +Let `M` be the weighted median, `𝜎` be the standard deviation of the votes in the ballot, and be the RewardBand parameter. The band around the median is set to be `𝜀 = max(𝜎, R/2)`. All valid (i.e. bonded and non-jailed) validators that submitted an exchange rate vote in the interval `[M - 𝜀, M + 𝜀]` should be included in the set of winners, weighted by their relative vote power. + +### Slashing + +> Be sure to read this section carefully as it concerns potential loss of funds. + +A `VotePeriod` during which either of the following events occur is considered a "miss": + +* The validator fails to submits a vote for an exchange rate against **each and every** pair specified in `Whitelist`. + +* The validator fails to vote within the `reward band` around the weighted median for one or more pairs. + +During every `SlashWindow`, participating validators must maintain a valid vote rate of at least `MinValidPerWindow` (5%), lest they get their stake slashed (currently set to 0.01%). The slashed validator is automatically temporarily "jailed" by the protocol (to protect the funds of delegators), and the operator is expected to fix the discrepancy promptly to resume validator participation. + +### Abstaining from Voting + +A validator may abstain from voting by submitting a non-positive integer for the `ExchangeRate` field in `MsgAggregateExchangeRateVote`. Doing so will absolve them of any penalties for missing `VotePeriod`s, but also disqualify them from receiving Oracle seigniorage rewards for faithful reporting. + +### Messages + +> The control flow for vote-tallying, exchange rate updates, ballot rewards and slashing happens at the end of every `VotePeriod`, and is found at the [end-block ABCI](#end-block) function rather than inside message handlers. + +--- + +## Module Parameters + +The oracle module contains the following parameters: + +| Module Param (type) | Description | +| ------------------------- | ----------- | +| `VotePeriod` (uint64) | Defines the number of blocks during which voting takes place. Ex. "5". | +| `VoteThreshold` (Dec) | VoteThreshold specifies the minimum proportion of votes that must be received for a ballot to pass. Ex. "0.5" | +| `RewardBand` (Dec) | Defines a maxium divergence that a price vote can have from the weighted median in the ballot. If a vote lies within the valid range defined by: `μ := weightedMedian`, `validRange := μ ± (μ * rewardBand / 2)`, then rewards are added to the validator performance. Note that if the reward band is smaller than 1 standard deviation, the band is taken to be 1 standard deviation.a price. Ex. "0.02" | +| `Whitelist` (set[String]) | The set of whitelisted markets, or asset pairs, for the module. Ex. '["unibi:uusd","ubtc:uusd"]' | +| `SlashFraction` (Dec) | The proportion of an oracle's stake that gets slashed in the event of slashing. `SlashFraction` specifies the exact penalty for failing a voting period. | +| `SlashWindow` (uint64) | The number of voting periods that specify a "slash window". After each slash window, all oracles that have missed more than the penalty threshold are slashed. Missing the penalty threshold is synonymous with submitting fewer valid votes than `MinValidPerWindow`. | +| `MinValidPerWindow` (Dec) | The oracle slashing threshold. Ex. "0.05". | +| `TwapLookbackWindow` (Duration) | Lookback window for time-weighted average price (TWAP) calculations. + +--- + +## State + +### ExchangeRate + +An `sdk.Dec` that stores the current exchange rate against a given pair. + +You can get the active list of pairs (exchange rates with votes past `VoteThreshold`) with `k.GetActivePairs()`. + +- ExchangeRate: `0x03 -> amino(sdk.Dec)` + +### FeederDelegation + +An `sdk.AccAddress` (`nibi-` account) address of `operator`'s delegated price feeder. + +- FeederDelegation: `0x04 -> amino(sdk.AccAddress)` + +### MissCounter + +An `int64` representing the number of `VotePeriods` that validator `operator` missed during the current `SlashWindow`. + +- MissCounter: `0x05 -> amino(int64)` + +### AggregateExchangeRatePrevote + +`AggregateExchangeRatePrevote` containing validator voter's aggregated prevote for all pairs for the current `VotePeriod`. + +- AggregateExchangeRatePrevote: `0x06 -> amino(AggregateExchangeRatePrevote)` + +```go +// AggregateVoteHash is hash value to hide vote exchange rates +// which is formatted as hex string in SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") +type AggregateVoteHash []byte + +type AggregateExchangeRatePrevote struct { + Hash AggregateVoteHash // Vote hex hash to protect centralize data source problem + Voter sdk.ValAddress // Voter val address + SubmitBlock int64 +} +``` + +### AggregateExchangeRateVote + +`AggregateExchangeRateVote` containing validator voter's aggregate vote for all pairs for the current `VotePeriod`. + +- AggregateExchangeRateVote: `0x07 -> amino(AggregateExchangeRateVote)` + +```go +type ExchangeRateTuple struct { + Pair string `json:"pair"` + ExchangeRate sdk.Dec `json:"exchange_rate"` +} + +type ExchangeRateTuples []ExchangeRateTuple + +type AggregateExchangeRateVote struct { + ExchangeRateTuples ExchangeRateTuples // ExchangeRates of pairs + Voter sdk.ValAddress // voter val address of validator +} +``` + +--- + +## End Block + +### Tally Exchange Rate Votes + +At the end of every block, the `Oracle` module checks whether it's the last block of the `VotePeriod`. If it is, it runs the [Voting Procedure](#Voting_Procedure): + +1. All current active exchange rates are purged from the store + +2. Received votes are organized into ballots by pair. Abstained votes, as well as votes by inactive or jailed validators are ignored + +3. Pairs not meeting the following requirements will be dropped: + + - Must appear in the permitted pairs in `Whitelist` + - Ballot for pair must have at least `VoteThreshold` total vote power + +4. For each remaining `pair` with a passing ballot: + + - Tally up votes and find the weighted median exchange rate and winners with `tally()` + - Iterate through winners of the ballot and add their weight to their running total + - Set the exchange rate on the blockchain for that pair with `k.SetExchangeRate()` + - Emit an `exchange_rate_update` event + +5. Count up the validators who [missed](#Slashing) the Oracle vote and increase the appropriate miss counters + +6. If at the end of a `SlashWindow`, penalize validators who have missed more than the penalty threshold (submitted fewer valid votes than `MinValidPerWindow`) + +7. Distribute rewards to ballot winners with `k.RewardBallotWinners()` + +8. Clear all prevotes (except ones for the next `VotePeriod`) and votes from the store + +--- + +## Messages + +### MsgAggregateExchangeRatePrevote + +`Hash` is a hex string generated by the leading 20 bytes of the SHA256 hash (hex string) of a string of the format `{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}`, the metadata of the actual `MsgAggregateExchangeRateVote` to follow in the next `VotePeriod`. You can use the `GetAggregateVoteHash()` function to help encode this hash. Note that since in the subsequent `MsgAggregateExchangeRateVote`, the salt will have to be revealed, the salt used must be regenerated for each prevote submission. + +```go +// MsgAggregateExchangeRatePrevote - struct for aggregate prevoting on the ExchangeRateVote. +// The purpose of aggregate prevote is to hide vote exchange rates with hash +// which is formatted as hex string in SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") +type MsgAggregateExchangeRatePrevote struct { + Hash AggregateVoteHash + Feeder sdk.AccAddress + Validator sdk.ValAddress +} +``` + +`Feeder` (`nibi-` address) is used if the validator wishes to delegate oracle vote signing to a separate key (who "feeds" the price in lieu of the operator) to de-risk exposing their validator signing key. + +`Validator` is the validator address (`nibivaloper-` address) of the original validator. + +### MsgAggregateExchangeRateVote + +The `MsgAggregateExchangeRateVote` contains the actual exchange rates vote. The `Salt` parameter must match the salt used to create the prevote, otherwise the voter cannot be rewarded. + +```go +// MsgAggregateExchangeRateVote - struct for voting on the exchange rates of pairs. +type MsgAggregateExchangeRateVote struct { + Salt string + ExchangeRates string + Feeder sdk.AccAddress + Validator sdk.ValAddress +} +``` + +### MsgDelegateFeedConsent + +Validators may also elect to delegate voting rights to another key to prevent the block signing key from being kept online. To do so, they must submit a `MsgDelegateFeedConsent`, delegating their oracle voting rights to a `Delegate` that sign `MsgAggregateExchangeRatePrevote` and `MsgAggregateExchangeRateVote` on behalf of the validator. + +> Delegate validators will likely require you to deposit some funds (in NIBI) which they can use to pay fees, sent in a separate MsgSend. This agreement is made off-chain and not enforced by the Nibiru protocol. + +The `Operator` field contains the operator address of the validator (prefixed `nibivaloper-`). The `Delegate` field is the account address (prefixed `nibi-`) of the delegate account that will be submitting exchange rate related votes and prevotes on behalf of the `Operator`. + +```go +// MsgDelegateFeedConsent - struct for delegating oracle voting rights to another address. +type MsgDelegateFeedConsent struct { + Operator sdk.ValAddress + Delegate sdk.AccAddress +} +``` + +--- + +## Events + +The oracle module emits the following events: + +### EndBlocker + +| Type | Attribute Key | Attribute Value | +|----------------------|---------------|-----------------| +| exchange_rate_update | pair | {pair} | +| exchange_rate_update | exchange_rate | {exchangeRate} | + + +### Events for MsgExchangeRatePrevote + +| Type | Attribute Key | Attribute Value | +|---------|---------------|---------------------| +| prevote | pair | {pair} | +| prevote | voter | {validatorAddress} | +| prevote | feeder | {feederAddress} | +| message | module | oracle | +| message | action | exchangerateprevote | +| message | sender | {senderAddress} | + +### Events for MsgExchangeRateVote + +| Type | Attribute Key | Attribute Value | +|---------|---------------|--------------------| +| vote | pair | {pair} | +| vote | voter | {validatorAddress} | +| vote | exchange_rate | {exchangeRate} | +| vote | feeder | {feederAddress} | +| message | module | oracle | +| message | action | exchangeratevote | +| message | sender | {senderAddress} | + +### Events for MsgDelegateFeedConsent + + +| Type | Attribute Key | Attribute Value | +|---------------|---------------|--------------------| +| feed_delegate | operator | {validatorAddress} | +| feed_delegate | feeder | {feederAddress} | +| message | module | oracle | +| message | action | delegatefeeder | +| message | sender | {senderAddress} | + +### Events for MsgAggregateExchangeRatePrevote + +| Type | Attribute Key | Attribute Value | +|-------------------|---------------|------------------------------| +| aggregate_prevote | voter | {validatorAddress} | +| aggregate_prevote | feeder | {feederAddress} | +| message | module | oracle | +| message | action | aggregateexchangerateprevote | +| message | sender | {senderAddress} | + +### Events for MsgAggregateExchangeRateVote + +| Type | Attribute Key | Attribute Value | +|----------------|----------------|---------------------------| +| aggregate_vote | voter | {validatorAddress} | +| aggregate_vote | exchange_rates | {exchangeRates} | +| aggregate_vote | feeder | {feederAddress} | +| message | module | oracle | +| message | action | aggregateexchangeratevote | +| message | sender | {senderAddress} | + +--- diff --git a/x/oracle/abci.go b/x/oracle/abci.go new file mode 100644 index 00000000..9977942f --- /dev/null +++ b/x/oracle/abci.go @@ -0,0 +1,30 @@ +package oracle + +import ( + "time" + + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/types" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// EndBlocker is called at the end of every block +func EndBlocker(ctx sdk.Context, k keeper.Keeper) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + params, err := k.Params.Get(ctx) + if err != nil { + return + } + if types.IsPeriodLastBlock(ctx, params.VotePeriod) { + k.UpdateExchangeRates(ctx) + } + + // Do slash who did miss voting over threshold and + // reset miss counters of all validators at the last block of slash window + if types.IsPeriodLastBlock(ctx, params.SlashWindow) { + k.SlashAndResetMissCounters(ctx) + } +} diff --git a/x/oracle/abci_test.go b/x/oracle/abci_test.go new file mode 100644 index 00000000..9c5bb4e4 --- /dev/null +++ b/x/oracle/abci_test.go @@ -0,0 +1,96 @@ +package oracle + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestOracleTallyTiming(t *testing.T) { + input, h := keeper.Setup(t) + + // all the Addrs vote for the block ... not last period block yet, so tally fails + for i := range keeper.Addrs[:4] { + keeper.MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), ExchangeRate: math.LegacyOneDec()}, + }, i) + } + + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + + params.VotePeriod = 10 // set vote period to 10 for now, for convenience + params.ExpirationBlocks = 100 + input.OracleKeeper.Params.Set(input.Ctx, params) + require.Equal(t, 1, int(input.Ctx.BlockHeight())) + + EndBlocker(input.Ctx, input.OracleKeeper) + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, asset.Registry.Pair(denoms.BTC, denoms.USD)) + require.Error(t, err) + + input.Ctx = input.Ctx.WithBlockHeight(int64(params.VotePeriod - 1)) + + EndBlocker(input.Ctx, input.OracleKeeper) + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, asset.Registry.Pair(denoms.BTC, denoms.USD)) + require.NoError(t, err) +} + +// Set prices for 2 pairs, one that is updated and the other which is updated only once. +// Ensure that the updated pair is not deleted and the other pair is deleted after a certain time. +func TestOraclePriceExpiration(t *testing.T) { + input, h := keeper.Setup(t) + pair1 := asset.Registry.Pair(denoms.BTC, denoms.USD) + pair2 := asset.Registry.Pair(denoms.ETH, denoms.USD) + + // Set prices for both pairs + for i := range keeper.Addrs[:4] { + keeper.MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: pair1, ExchangeRate: math.LegacyOneDec()}, + {Pair: pair2, ExchangeRate: math.LegacyOneDec()}, + }, i) + } + + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + + params.VotePeriod = 10 + params.ExpirationBlocks = 10 + input.OracleKeeper.Params.Set(input.Ctx, params) + + // Wait for prices to set + input.Ctx = input.Ctx.WithBlockHeight(int64(params.VotePeriod - 1)) + EndBlocker(input.Ctx, input.OracleKeeper) + + // Check if both prices are set + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, pair1) + require.NoError(t, err) + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, pair2) + require.NoError(t, err) + + // Set prices for pair 1 + for i := range keeper.Addrs[:4] { + keeper.MakeAggregatePrevoteAndVote(t, input, h, 19, types.ExchangeRateTuples{ + {Pair: pair1, ExchangeRate: math.LegacyNewDec(2)}, + }, i) + } + + // Set price + input.Ctx = input.Ctx.WithBlockHeight(19) + EndBlocker(input.Ctx, input.OracleKeeper) + + // Set the block height to 1 block after the expiration + // End blocker should delete the price of pair2 + input.Ctx = input.Ctx.WithBlockHeight(int64(params.ExpirationBlocks+params.VotePeriod) + 1) + EndBlocker(input.Ctx, input.OracleKeeper) + + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, pair1) + require.NoError(t, err) + _, err = input.OracleKeeper.ExchangeRates.Get(input.Ctx, pair2) + require.Error(t, err) +} diff --git a/x/oracle/client/cli/gen_pricefeeder_delegation.go b/x/oracle/client/cli/gen_pricefeeder_delegation.go new file mode 100644 index 00000000..00fcd399 --- /dev/null +++ b/x/oracle/client/cli/gen_pricefeeder_delegation.go @@ -0,0 +1,99 @@ +package cli + +import ( + "encoding/json" + "fmt" + + flag "github.com/spf13/pflag" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/spf13/cobra" + + "github.com/archway-network/archway/x/oracle/types" +) + +const ( + FlagValidator = "validator" + FlagPricefeeder = "pricefeeder" +) + +func AddGenesisPricefeederDelegationCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-pricefeeder-delegation", + Short: "Add a pricefeeder delegation to genesis.json.", + Long: `Add a pricefeeder delegation to genesis.json.`, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + config.SetRoot(clientCtx.HomeDir) + genFile := config.GenesisFile() + + genState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return err + } + + valAddr, err := cmd.Flags().GetString(FlagValidator) + if err != nil { + return err + } + + _, err = sdk.ValAddressFromBech32(valAddr) + if err != nil { + return err + } + + pricefeederAddr, err := cmd.Flags().GetString(FlagPricefeeder) + if err != nil { + return err + } + + _, err = sdk.AccAddressFromBech32(pricefeederAddr) + if err != nil { + return err + } + + oracleGenState := types.GetGenesisStateFromAppState(clientCtx.Codec, genState) + oracleGenState.FeederDelegations = append(oracleGenState.FeederDelegations, types.FeederDelegation{ + FeederAddress: pricefeederAddr, + ValidatorAddress: valAddr, + }) + + oracleGenStateBz, err := clientCtx.Codec.MarshalJSON(oracleGenState) + if err != nil { + return fmt.Errorf("failed to marshal market genesis state: %w", err) + } + + genState[types.ModuleName] = oracleGenStateBz + + appStateJSON, err := json.Marshal(genState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + + flagSet := flag.NewFlagSet("flags-add-genesis-pricefeeder-delegation", flag.ContinueOnError) + + flagSet.String(FlagValidator, "", "validator address") + _ = cmd.MarkFlagRequired(FlagValidator) + + flagSet.String(FlagPricefeeder, "", "pricefeeder address") + _ = cmd.MarkFlagRequired(FlagPricefeeder) + + cmd.Flags().AddFlagSet(flagSet) + + return cmd +} diff --git a/x/oracle/client/cli/gen_pricefeeder_delegation_test.go b/x/oracle/client/cli/gen_pricefeeder_delegation_test.go new file mode 100644 index 00000000..96569123 --- /dev/null +++ b/x/oracle/client/cli/gen_pricefeeder_delegation_test.go @@ -0,0 +1,77 @@ +package cli_test + +import ( + "fmt" + "testing" + + "github.com/archway-network/archway/app/appconst" + "github.com/archway-network/archway/x/common/testutil" + "github.com/archway-network/archway/x/oracle/client/cli" + + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestAddGenesisPricefeederDelegation(t *testing.T) { + cfg := sdk.GetConfig() + cfg.SetBech32PrefixForAccount(appconst.Bech32PrefixAccAddr, appconst.Bech32PrefixAccPub) + + tests := []struct { + name string + validator string + pricefeeder string + + expectErr bool + }{ + { + name: "valid", + validator: "nibivaloper1zaavvzxez0elundtn32qnk9lkm8kmcszuwx9jz", + pricefeeder: "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl", + expectErr: false, + }, + { + name: "invalid pricefeeder", + validator: "nibivaloper1zaavvzxez0elundtn32qnk9lkm8kmcszuwx9jz", + pricefeeder: "nibi1foobar", + expectErr: true, + }, + { + name: "empty pricefeeder", + validator: "nibivaloper1zaavvzxez0elundtn32qnk9lkm8kmcszuwx9jz", + pricefeeder: "", + expectErr: true, + }, + { + name: "invalid validator", + validator: "nibivaloper1foobar", + pricefeeder: "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl", + expectErr: true, + }, + { + name: "empty validator", + validator: "", + pricefeeder: "nibi1zaavvzxez0elundtn32qnk9lkm8kmcsz44g7xl", + expectErr: true, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + ctx := testutil.SetupClientCtx(t) + cmd := cli.AddGenesisPricefeederDelegationCmd(t.TempDir()) + cmd.SetArgs([]string{ + fmt.Sprintf("--%s=%s", cli.FlagValidator, tc.validator), + fmt.Sprintf("--%s=%s", cli.FlagPricefeeder, tc.pricefeeder), + fmt.Sprintf("--%s=home", flags.FlagHome), + }) + + if tc.expectErr { + require.Error(t, cmd.ExecuteContext(ctx)) + } else { + require.NoError(t, cmd.ExecuteContext(ctx)) + } + }) + } +} diff --git a/x/oracle/client/cli/query.go b/x/oracle/client/cli/query.go new file mode 100644 index 00000000..862d097e --- /dev/null +++ b/x/oracle/client/cli/query.go @@ -0,0 +1,370 @@ +package cli + +import ( + "context" + "strings" + + "github.com/spf13/cobra" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/oracle/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + oracleQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the oracle module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + oracleQueryCmd.AddCommand( + GetCmdQueryExchangeRates(), + GetCmdQueryActives(), + GetCmdQueryParams(), + GetCmdQueryFeederDelegation(), + GetCmdQueryMissCounter(), + GetCmdQueryAggregatePrevote(), + GetCmdQueryAggregateVote(), + GetCmdQueryVoteTargets(), + ) + + return oracleQueryCmd +} + +// GetCmdQueryExchangeRates implements the query rate command. +func GetCmdQueryExchangeRates() *cobra.Command { + cmd := &cobra.Command{ + Use: "exchange-rates [pair]", + Args: cobra.RangeArgs(0, 1), + Short: "Query the current exchange rate w.r.t a pair", + Long: strings.TrimSpace(` +Query the current exchange rate of a pair. +You can find the current list of active pairs by running + +$ nibid query oracle exchange-rates + +Or, can filter with pair + +$ nibid query oracle exchange-rates nibi:usd +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + if len(args) == 0 { + res, err := queryClient.ExchangeRates(context.Background(), &types.QueryExchangeRatesRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + assetPair, err := asset.TryNewPair(args[0]) + if err != nil { + return err + } + + res, err := queryClient.ExchangeRate( + context.Background(), + &types.QueryExchangeRateRequest{Pair: assetPair}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryActives implements the query actives command. +func GetCmdQueryActives() *cobra.Command { + cmd := &cobra.Command{ + Use: "actives", + Args: cobra.NoArgs, + Short: "Query the active list of pairs recognized by the oracle", + Long: strings.TrimSpace(` +Query the active list of pairs recognized by the oracles. + +$ nibid query oracle actives +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Actives(context.Background(), &types.QueryActivesRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryParams implements the query params command. +func GetCmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Args: cobra.NoArgs, + Short: "Query the current Oracle params", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryFeederDelegation implements the query feeder delegation command +func GetCmdQueryFeederDelegation() *cobra.Command { + cmd := &cobra.Command{ + Use: "feeder [validator]", + Args: cobra.ExactArgs(1), + Short: "Query the oracle feeder delegate account", + Long: strings.TrimSpace(` +Query the account the validator's oracle voting right is delegated to. + +$ nibid query oracle feeder nibivaloper... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + valString := args[0] + validator, err := sdk.ValAddressFromBech32(valString) + if err != nil { + return err + } + + res, err := queryClient.FeederDelegation( + context.Background(), + &types.QueryFeederDelegationRequest{ValidatorAddr: validator.String()}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryMissCounter implements the query miss counter of the validator command +func GetCmdQueryMissCounter() *cobra.Command { + cmd := &cobra.Command{ + Use: "miss [validator]", + Args: cobra.ExactArgs(1), + Short: "Query the # of the miss count", + Long: strings.TrimSpace(` +Query the # of vote periods missed in this oracle slash window. + +$ nibid query oracle miss nibivaloper... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + valString := args[0] + validator, err := sdk.ValAddressFromBech32(valString) + if err != nil { + return err + } + + res, err := queryClient.MissCounter( + context.Background(), + &types.QueryMissCounterRequest{ValidatorAddr: validator.String()}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryAggregatePrevote implements the query aggregate prevote of the validator command +func GetCmdQueryAggregatePrevote() *cobra.Command { + cmd := &cobra.Command{ + Use: "aggregate-prevotes [validator]", + Args: cobra.RangeArgs(0, 1), + Short: "Query outstanding oracle aggregate prevotes.", + Long: strings.TrimSpace(` +Query outstanding oracle aggregate prevotes. + +$ nibid query oracle aggregate-prevotes + +Or, can filter with voter address + +$ nibid query oracle aggregate-prevotes nibivaloper... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + if len(args) == 0 { + res, err := queryClient.AggregatePrevotes( + context.Background(), + &types.QueryAggregatePrevotesRequest{}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + valString := args[0] + validator, err := sdk.ValAddressFromBech32(valString) + if err != nil { + return err + } + + res, err := queryClient.AggregatePrevote( + context.Background(), + &types.QueryAggregatePrevoteRequest{ValidatorAddr: validator.String()}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryAggregateVote implements the query aggregate prevote of the validator command +func GetCmdQueryAggregateVote() *cobra.Command { + cmd := &cobra.Command{ + Use: "aggregate-votes [validator]", + Args: cobra.RangeArgs(0, 1), + Short: "Query outstanding oracle aggregate votes.", + Long: strings.TrimSpace(` +Query outstanding oracle aggregate vote. + +$ nibid query oracle aggregate-votes + +Or, can filter with voter address + +$ nibid query oracle aggregate-votes nibivaloper... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + if len(args) == 0 { + res, err := queryClient.AggregateVotes( + context.Background(), + &types.QueryAggregateVotesRequest{}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + valString := args[0] + validator, err := sdk.ValAddressFromBech32(valString) + if err != nil { + return err + } + + res, err := queryClient.AggregateVote( + context.Background(), + &types.QueryAggregateVoteRequest{ValidatorAddr: validator.String()}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryVoteTargets implements the query params command. +func GetCmdQueryVoteTargets() *cobra.Command { + cmd := &cobra.Command{ + Use: "vote-targets", + Args: cobra.NoArgs, + Short: "Query the current Oracle vote targets", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.VoteTargets( + context.Background(), + &types.QueryVoteTargetsRequest{}, + ) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/oracle/client/cli/tx.go b/x/oracle/client/cli/tx.go new file mode 100644 index 00000000..8f1c283a --- /dev/null +++ b/x/oracle/client/cli/tx.go @@ -0,0 +1,207 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/archway-network/archway/x/oracle/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/spf13/cobra" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + oracleTxCmd := &cobra.Command{ + Use: "oracle", + Short: "Oracle transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + oracleTxCmd.AddCommand( + GetCmdDelegateFeederPermission(), + GetCmdAggregateExchangeRatePrevote(), + GetCmdAggregateExchangeRateVote(), + ) + + return oracleTxCmd +} + +// GetCmdDelegateFeederPermission will create a feeder permission delegation tx and sign it with the given key. +func GetCmdDelegateFeederPermission() *cobra.Command { + cmd := &cobra.Command{ + Use: "set-feeder [feeder]", + Args: cobra.ExactArgs(1), + Short: "Delegate the permission to vote for the oracle to an address", + Long: strings.TrimSpace(` +Delegate the permission to submit exchange rate votes for the oracle to an address. + +Delegation can keep your validator operator key offline and use a separate replaceable key online. + +$ nibid tx oracle set-feeder nibi1... + +where "nibi1..." is the address you want to delegate your voting rights to. +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // Get from address + voter := clientCtx.GetFromAddress() + + // The address the right is being delegated from + validator := sdk.ValAddress(voter) + + feederStr := args[0] + feeder, err := sdk.AccAddressFromBech32(feederStr) + if err != nil { + return err + } + + msg := types.NewMsgDelegateFeedConsent(validator, feeder) + if err = msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdAggregateExchangeRatePrevote will create a aggregateExchangeRatePrevote tx and sign it with the given key. +func GetCmdAggregateExchangeRatePrevote() *cobra.Command { + cmd := &cobra.Command{ + Use: "aggregate-prevote [salt] [exchange-rates] [validator]", + Args: cobra.RangeArgs(2, 3), + Short: "Submit an oracle aggregate prevote for the exchange rates of Nibiru", + Long: strings.TrimSpace(` +Submit an oracle aggregate prevote for the exchange rates of a pair. +The purpose of aggregate prevote is to hide aggregate exchange rate vote with hash which is formatted +as hex string in SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") + +# Aggregate Prevote +$ nibid tx oracle aggregate-prevote 1234 (40000.0,BTC:USD)|(1.243,NIBI:USD) + +where "BTC:USD,NIBI:USD" is the pair, and "40000.0, 1.243" is the exchange rates expressed in decimal value. + +If voting from a voting delegate, set "validator" to the address of the validator to vote on behalf of: +$ nibid tx oracle aggregate-prevote 1234 1234 (40000.0,BTC:USD)|(1.243,NIBI:USD) nibivaloper1... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + salt := args[0] + exchangeRatesStr := args[1] + _, err = types.ParseExchangeRateTuples(exchangeRatesStr) + if err != nil { + return fmt.Errorf("given exchange_rates {%s} is not a valid format; exchange_rate should be formatted as DecCoins; %s", exchangeRatesStr, err.Error()) + } + + // Get from address + feeder := clientCtx.GetFromAddress() + + // By default, the feeder is voting on behalf of itself + validator := sdk.ValAddress(feeder) + + // Override validator if validator is given + if len(args) == 3 { + parsedVal, err := sdk.ValAddressFromBech32(args[2]) + if err != nil { + return errors.Wrap(err, "validator address is invalid") + } + + validator = parsedVal + } + + hash := types.GetAggregateVoteHash(salt, exchangeRatesStr, validator) + + msg := types.NewMsgAggregateExchangeRatePrevote(hash, feeder, validator) + if err = msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// GetCmdAggregateExchangeRateVote will create a aggregateExchangeRateVote tx and sign it with the given key. +func GetCmdAggregateExchangeRateVote() *cobra.Command { + cmd := &cobra.Command{ + Use: "aggregate-vote [salt] [exchange-rates] [validator]", + Args: cobra.RangeArgs(2, 3), + Short: "Submit an oracle aggregate vote for the exchange_rates of Nibiru", + Long: strings.TrimSpace(` +Submit an aggregate vote for the exchange_rates of the proposed pairs. Companion to a prevote submitted in the previous vote period. + +$ nibid tx oracle aggregate-vote 1234 (40000.0,BTC:USD)|(1.243,NIBI:USD) + +where "BTC:USD, NIBI:USD" is the pairs, and "40000.0,1.243" is the exchange rates as decimal string. + +"salt" should match the salt used to generate the SHA256 hex in the aggregated pre-vote. + +If voting from a voting delegate, set "validator" to the address of the validator to vote on behalf of: +$ nibid tx oracle aggregate-vote 1234 (40000.0,BTC:USD)|(1.243,NIBI:USD) nibivaloper1.... +`), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + salt := args[0] + exchangeRatesStr := args[1] + _, err = types.ParseExchangeRateTuples(exchangeRatesStr) + if err != nil { + return fmt.Errorf("given exchange_rate {%s} is not a valid format; exchange rate should be formatted as DecCoin; %s", exchangeRatesStr, err.Error()) + } + + // Get from address + feeder := clientCtx.GetFromAddress() + + // By default, the feeder is voting on behalf of itself + validator := sdk.ValAddress(feeder) + + // Override validator if validator is given + if len(args) == 3 { + parsedVal, err := sdk.ValAddressFromBech32(args[2]) + if err != nil { + return errors.Wrap(err, "validator address is invalid") + } + validator = parsedVal + } + + msg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, feeder, validator) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/oracle/genesis.go b/x/oracle/genesis.go new file mode 100644 index 00000000..3d9f9255 --- /dev/null +++ b/x/oracle/genesis.go @@ -0,0 +1,133 @@ +package oracle + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/types" +) + +// InitGenesis initialize default parameters +// and the keeper's address to pubkey map +func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, data *types.GenesisState) { + for _, d := range data.FeederDelegations { + voter, err := sdk.ValAddressFromBech32(d.ValidatorAddress) + if err != nil { + panic(err) + } + + feeder, err := sdk.AccAddressFromBech32(d.FeederAddress) + if err != nil { + panic(err) + } + + keeper.FeederDelegations.Insert(ctx, voter, feeder) + } + + for _, ex := range data.ExchangeRates { + keeper.SetPrice(ctx, ex.Pair, ex.ExchangeRate) + } + + for _, missCounter := range data.MissCounters { + operator, err := sdk.ValAddressFromBech32(missCounter.ValidatorAddress) + if err != nil { + panic(err) + } + + keeper.MissCounters.Insert(ctx, operator, missCounter.MissCounter) + } + + for _, aggregatePrevote := range data.AggregateExchangeRatePrevotes { + valAddr, err := sdk.ValAddressFromBech32(aggregatePrevote.Voter) + if err != nil { + panic(err) + } + + keeper.Prevotes.Insert(ctx, valAddr, aggregatePrevote) + } + + for _, aggregateVote := range data.AggregateExchangeRateVotes { + valAddr, err := sdk.ValAddressFromBech32(aggregateVote.Voter) + if err != nil { + panic(err) + } + + keeper.Votes.Insert(ctx, valAddr, aggregateVote) + } + + if len(data.Pairs) > 0 { + for _, tt := range data.Pairs { + keeper.WhitelistedPairs.Insert(ctx, tt) + } + } else { + for _, item := range data.Params.Whitelist { + keeper.WhitelistedPairs.Insert(ctx, item) + } + } + + for _, pr := range data.Rewards { + keeper.Rewards.Insert(ctx, pr.Id, pr) + } + + // set last ID based on the last pair reward + if len(data.Rewards) != 0 { + keeper.RewardsID.Set(ctx, data.Rewards[len(data.Rewards)-1].Id) + } + keeper.Params.Set(ctx, data.Params) + + // check if the module account exists + moduleAcc := keeper.AccountKeeper.GetModuleAccount(ctx, types.ModuleName) + if moduleAcc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) + } +} + +// ExportGenesis writes the current store values +// to a genesis file, which can be imported again +// with InitGenesis +func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { + params, err := keeper.Params.Get(ctx) + if err != nil { + panic(err) + } + + feederDelegations := []types.FeederDelegation{} + for _, kv := range keeper.FeederDelegations.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() { + feederDelegations = append(feederDelegations, types.FeederDelegation{ + FeederAddress: kv.Value.String(), + ValidatorAddress: kv.Key.String(), + }) + } + + exchangeRates := []types.ExchangeRateTuple{} + for _, er := range keeper.ExchangeRates.Iterate(ctx, collections.Range[asset.Pair]{}).KeyValues() { + exchangeRates = append(exchangeRates, types.ExchangeRateTuple{Pair: er.Key, ExchangeRate: er.Value.ExchangeRate}) + } + + missCounters := []types.MissCounter{} + for _, mc := range keeper.MissCounters.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() { + missCounters = append(missCounters, types.MissCounter{ + ValidatorAddress: mc.Key.String(), + MissCounter: mc.Value, + }) + } + + var pairs []asset.Pair + pairs = append(pairs, keeper.WhitelistedPairs.Iterate(ctx, collections.Range[asset.Pair]{}).Keys()...) + + return types.NewGenesisState( + params, + exchangeRates, + feederDelegations, + missCounters, + keeper.Prevotes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).Values(), + keeper.Votes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).Values(), + pairs, + keeper.Rewards.Iterate(ctx, collections.Range[uint64]{}).Values(), + ) +} diff --git a/x/oracle/genesis_test.go b/x/oracle/genesis_test.go new file mode 100644 index 00000000..56ce5c24 --- /dev/null +++ b/x/oracle/genesis_test.go @@ -0,0 +1,139 @@ +package oracle_test + +import ( + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/oracle" + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestExportInitGenesis(t *testing.T) { + input := keeper.CreateTestFixture(t) + + input.OracleKeeper.Params.Set(input.Ctx, types.DefaultParams()) + input.OracleKeeper.FeederDelegations.Insert(input.Ctx, keeper.ValAddrs[0], keeper.Addrs[1]) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, "pair1:pair2", types.DatedPrice{ExchangeRate: math.LegacyNewDec(123), CreatedBlock: 0}) + input.OracleKeeper.Prevotes.Insert(input.Ctx, keeper.ValAddrs[0], types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{123}, keeper.ValAddrs[0], uint64(2))) + input.OracleKeeper.Votes.Insert(input.Ctx, keeper.ValAddrs[0], types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "foo", ExchangeRate: math.LegacyNewDec(123)}}, keeper.ValAddrs[0])) + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, "pair1:pair1") + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, "pair2:pair2") + input.OracleKeeper.MissCounters.Insert(input.Ctx, keeper.ValAddrs[0], 10) + input.OracleKeeper.Rewards.Insert(input.Ctx, 0, types.Rewards{ + Id: 0, + VotePeriods: 100, + Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1000)), + }) + genesis := oracle.ExportGenesis(input.Ctx, input.OracleKeeper) + + newInput := keeper.CreateTestFixture(t) + oracle.InitGenesis(newInput.Ctx, newInput.OracleKeeper, genesis) + newGenesis := oracle.ExportGenesis(newInput.Ctx, newInput.OracleKeeper) + + require.Equal(t, genesis, newGenesis) +} + +func TestInitGenesis(t *testing.T) { + input := keeper.CreateTestFixture(t) + genesis := types.DefaultGenesisState() + require.NotPanics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.FeederDelegations = []types.FeederDelegation{{ + FeederAddress: keeper.Addrs[0].String(), + ValidatorAddress: "invalid", + }} + + require.Panics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.FeederDelegations = []types.FeederDelegation{{ + FeederAddress: "invalid", + ValidatorAddress: keeper.ValAddrs[0].String(), + }} + + require.Panics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.FeederDelegations = []types.FeederDelegation{{ + FeederAddress: keeper.Addrs[0].String(), + ValidatorAddress: keeper.ValAddrs[0].String(), + }} + + genesis.MissCounters = []types.MissCounter{ + { + ValidatorAddress: "invalid", + MissCounter: 10, + }, + } + + require.Panics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.MissCounters = []types.MissCounter{ + { + ValidatorAddress: keeper.ValAddrs[0].String(), + MissCounter: 10, + }, + } + + genesis.AggregateExchangeRatePrevotes = []types.AggregateExchangeRatePrevote{ + { + Hash: "hash", + Voter: "invalid", + SubmitBlock: 100, + }, + } + + require.Panics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.AggregateExchangeRatePrevotes = []types.AggregateExchangeRatePrevote{ + { + Hash: "hash", + Voter: keeper.ValAddrs[0].String(), + SubmitBlock: 100, + }, + } + + genesis.AggregateExchangeRateVotes = []types.AggregateExchangeRateVote{ + { + ExchangeRateTuples: []types.ExchangeRateTuple{ + { + Pair: "nibi:usd", + ExchangeRate: math.LegacyNewDec(10), + }, + }, + Voter: "invalid", + }, + } + + require.Panics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) + + genesis.AggregateExchangeRateVotes = []types.AggregateExchangeRateVote{ + { + ExchangeRateTuples: []types.ExchangeRateTuple{ + { + Pair: "nibi:usd", + ExchangeRate: math.LegacyNewDec(10), + }, + }, + Voter: keeper.ValAddrs[0].String(), + }, + } + + require.NotPanics(t, func() { + oracle.InitGenesis(input.Ctx, input.OracleKeeper, genesis) + }) +} diff --git a/x/oracle/integration/action/price.go b/x/oracle/integration/action/price.go new file mode 100644 index 00000000..c77dfc7c --- /dev/null +++ b/x/oracle/integration/action/price.go @@ -0,0 +1,57 @@ +package action + +import ( + "time" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/testutil/action" + "github.com/archway-network/archway/x/oracle/types" +) + +func SetOraclePrice(pair asset.Pair, price math.LegacyDec) action.Action { + return &setPairPrice{ + Pair: pair, + Price: price, + } +} + +type setPairPrice struct { + Pair asset.Pair + Price math.LegacyDec +} + +func (s setPairPrice) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + app.Keepers.OracleKeeper.SetPrice(ctx, s.Pair, s.Price) + + return ctx, nil +} + +func InsertOraclePriceSnapshot(pair asset.Pair, time time.Time, price math.LegacyDec) action.Action { + return &insertOraclePriceSnapshot{ + Pair: pair, + Time: time, + Price: price, + } +} + +type insertOraclePriceSnapshot struct { + Pair asset.Pair + Time time.Time + Price math.LegacyDec +} + +func (s insertOraclePriceSnapshot) Do(app *app.ArchwayApp, ctx sdk.Context) (sdk.Context, error) { + app.Keepers.OracleKeeper.PriceSnapshots.Insert(ctx, collections.Join(s.Pair, s.Time), types.PriceSnapshot{ + Pair: s.Pair, + Price: s.Price, + TimestampMs: s.Time.UnixMilli(), + }) + + return ctx, nil +} diff --git a/x/oracle/integration/app_test.go b/x/oracle/integration/app_test.go new file mode 100644 index 00000000..c1f7eb83 --- /dev/null +++ b/x/oracle/integration/app_test.go @@ -0,0 +1,178 @@ +package integration_test + +import ( + "context" + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/testutil" + testutilcli "github.com/archway-network/archway/x/common/testutil/cli" + "github.com/archway-network/archway/x/common/testutil/genesis" + "github.com/archway-network/archway/x/common/testutil/testapp" + "github.com/archway-network/archway/x/oracle/types" +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg testutilcli.Config + network *testutilcli.Network +} + +func (s *IntegrationTestSuite) SetupSuite() { + testutil.BeforeIntegrationSuite(s.T()) +} + +func (s *IntegrationTestSuite) SetupTest() { + testapp.EnsureNibiruPrefix() + homeDir := s.T().TempDir() + + genesisState := genesis.NewTestGenesisState(app.MakeEncodingConfig()) + s.cfg = testutilcli.BuildNetworkConfig(genesisState) + s.cfg.NumValidators = 4 + s.cfg.GenesisState[types.ModuleName] = s.cfg.Codec.MustMarshalJSON(func() codec.ProtoMarshaler { + gs := types.DefaultGenesisState() + gs.Params.Whitelist = []asset.Pair{ + "nibi:usdc", + "btc:usdc", + } + + return gs + }()) + + network, err := testutilcli.New( + s.T(), + homeDir, + s.cfg, + ) + s.Require().NoError(err) + s.network = network + + _, err = s.network.WaitForHeight(2) + require.NoError(s.T(), err) +} + +func (s *IntegrationTestSuite) TestSuccessfulVoting() { + // assuming validators have equal power + // we use the weighted median. + // what happens is that prices are ordered + // based on exchange rate, from lowest to highest. + // then the median is picked, based on consensus power + // so obviously, in this case, since validators have the same power + // once weight (based on power) >= total power (sum of weights) + // then the number picked is the one in the middle always. + prices := []map[asset.Pair]math.LegacyDec{ + { + "nibi:usdc": math.LegacyOneDec(), + "btc:usdc": math.LegacyMustNewDecFromStr("100203.0"), + }, + { + "nibi:usdc": math.LegacyOneDec(), + "btc:usdc": math.LegacyMustNewDecFromStr("100150.5"), + }, + { + "nibi:usdc": math.LegacyOneDec(), + "btc:usdc": math.LegacyMustNewDecFromStr("100200.9"), + }, + { + "nibi:usdc": math.LegacyOneDec(), + "btc:usdc": math.LegacyMustNewDecFromStr("100300.9"), + }, + } + votes := s.sendPrevotes(prices) + + s.waitVoteRevealBlock() + + s.sendVotes(votes) + + s.waitPriceUpdateBlock() + + gotPrices := s.currentPrices() + require.Equal(s.T(), + map[asset.Pair]math.LegacyDec{ + "nibi:usdc": math.LegacyOneDec(), + "btc:usdc": math.LegacyMustNewDecFromStr("100200.9"), + }, + gotPrices, + ) +} + +func (s *IntegrationTestSuite) sendPrevotes(prices []map[asset.Pair]math.LegacyDec) []string { + strVotes := make([]string, len(prices)) + for i, val := range s.network.Validators { + raw := prices[i] + votes := make(types.ExchangeRateTuples, 0, len(raw)) + for pair, price := range raw { + votes = append(votes, types.NewExchangeRateTuple(pair, price)) + } + + pricesStr, err := votes.ToString() + require.NoError(s.T(), err) + _, err = s.network.BroadcastMsgs(val.Address, &types.MsgAggregateExchangeRatePrevote{ + Hash: types.GetAggregateVoteHash("1", pricesStr, val.ValAddress).String(), + Feeder: val.Address.String(), + Validator: val.ValAddress.String(), + }) + require.NoError(s.T(), err) + + strVotes[i] = pricesStr + } + + return strVotes +} + +func (s *IntegrationTestSuite) sendVotes(rates []string) { + for i, val := range s.network.Validators { + _, err := s.network.BroadcastMsgs(val.Address, &types.MsgAggregateExchangeRateVote{ + Salt: "1", + ExchangeRates: rates[i], + Feeder: val.Address.String(), + Validator: val.ValAddress.String(), + }) + require.NoError(s.T(), err) + } +} + +func (s *IntegrationTestSuite) waitVoteRevealBlock() { + params, err := types.NewQueryClient(s.network.Validators[0].ClientCtx).Params(context.Background(), &types.QueryParamsRequest{}) + require.NoError(s.T(), err) + + votePeriod := params.Params.VotePeriod + + height, err := s.network.LatestHeight() + require.NoError(s.T(), err) + + waitBlock := (uint64(height)/votePeriod)*votePeriod + votePeriod + + _, err = s.network.WaitForHeight(int64(waitBlock + 1)) + require.NoError(s.T(), err) +} + +// it's an alias, but it exists to give better understanding of what we're doing in test cases scenarios +func (s *IntegrationTestSuite) waitPriceUpdateBlock() { + s.waitVoteRevealBlock() +} + +func (s *IntegrationTestSuite) currentPrices() map[asset.Pair]math.LegacyDec { + rawRates, err := types.NewQueryClient(s.network.Validators[0].ClientCtx).ExchangeRates(context.Background(), &types.QueryExchangeRatesRequest{}) + require.NoError(s.T(), err) + + prices := make(map[asset.Pair]math.LegacyDec) + + for _, p := range rawRates.ExchangeRates { + prices[p.Pair] = p.ExchangeRate + } + + return prices +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} diff --git a/x/oracle/keeper/ballot.go b/x/oracle/keeper/ballot.go new file mode 100644 index 00000000..3ba4fb90 --- /dev/null +++ b/x/oracle/keeper/ballot.go @@ -0,0 +1,190 @@ +package keeper + +import ( + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/omap" + "github.com/archway-network/archway/x/common/set" + "github.com/archway-network/archway/x/oracle/types" +) + +// groupVotesByPair takes a collection of votes and groups them by their +// associated asset pair. This method only considers votes from active validators +// and disregards votes from validators that are not in the provided validator set. +// +// Note that any abstain votes (votes with a non-positive exchange rate) are +// assigned zero vote power. This function then returns a map where each +// asset pair is associated with its collection of ExchangeRateVotes. +func (k Keeper) groupVotesByPair( + ctx sdk.Context, + validatorPerformances types.ValidatorPerformances, +) (pairVotes map[asset.Pair]types.ExchangeRateVotes) { + pairVotes = map[asset.Pair]types.ExchangeRateVotes{} + + for _, value := range k.Votes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() { + voterAddr, aggregateVote := value.Key, value.Value + + // skip votes from inactive validators + validatorPerformance, exists := validatorPerformances[aggregateVote.Voter] + if !exists { + continue + } + + for _, tuple := range aggregateVote.ExchangeRateTuples { + power := validatorPerformance.Power + if !tuple.ExchangeRate.IsPositive() { + // Make the power of abstain vote zero + power = 0 + } + + pairVotes[tuple.Pair] = append( + pairVotes[tuple.Pair], + types.NewExchangeRateVote( + tuple.ExchangeRate, + tuple.Pair, + voterAddr, + power, + ), + ) + } + } + + return +} + +// clearVotesAndPrevotes clears all tallied prevotes and votes from the store +func (k Keeper) clearVotesAndPrevotes(ctx sdk.Context, votePeriod uint64) { + // Clear all aggregate prevotes + for _, prevote := range k.Prevotes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() { + valAddr, aggregatePrevote := prevote.Key, prevote.Value + if ctx.BlockHeight() >= int64(aggregatePrevote.SubmitBlock+votePeriod) { + err := k.Prevotes.Delete(ctx, valAddr) + if err != nil { + k.Logger(ctx).Error("failed to delete prevote", "error", err) + } + } + } + + // Clear all aggregate votes + for _, valAddr := range k.Votes.Iterate(ctx, collections.Range[sdk.ValAddress]{}).Keys() { + err := k.Votes.Delete(ctx, valAddr) + if err != nil { + k.Logger(ctx).Error("failed to delete vote", "error", err) + } + } +} + +// isPassingVoteThreshold votes is passing the threshold amount of voting power +func isPassingVoteThreshold( + votes types.ExchangeRateVotes, thresholdVotingPower sdkmath.Int, minVoters uint64, +) bool { + totalPower := math.NewInt(votes.Power()) + if totalPower.IsZero() { + return false + } + + if totalPower.LT(thresholdVotingPower) { + return false + } + + if votes.NumValidVoters() < minVoters { + return false + } + + return true +} + +// removeInvalidVotes removes the votes which have not reached the vote +// threshold or which are not part of the whitelisted pairs anymore: example +// when params change during a vote period but some votes were already made. +// +// ALERT: This function mutates the pairVotes map, it removes the votes for +// the pair which is not passing the threshold or which is not whitelisted +// anymore. +func (k Keeper) removeInvalidVotes( + ctx sdk.Context, + pairVotes map[asset.Pair]types.ExchangeRateVotes, + whitelistedPairs set.Set[asset.Pair], +) { + boundTokens, err := k.StakingKeeper.TotalBondedTokens(ctx) + if err != nil { + panic(err) + } + totalBondedPower := sdk.TokensToConsensusPower( + boundTokens, + k.StakingKeeper.PowerReduction(ctx), + ) + + // Iterate through sorted keys for deterministic ordering. + orderedPairVotes := omap.OrderedMap_Pair[types.ExchangeRateVotes](pairVotes) + for pair := range orderedPairVotes.Range() { + // If pair is not whitelisted, or the votes for it has failed, then skip + // and remove it from pairBallotsMap for iteration efficiency + if !whitelistedPairs.Has(pair) { + delete(pairVotes, pair) + } + + // If the votes is not passed, remove it from the whitelistedPairs set + // to prevent slashing validators who did valid vote. + if !isPassingVoteThreshold( + pairVotes[pair], + k.VoteThreshold(ctx).MulInt64(totalBondedPower).RoundInt(), + k.MinVoters(ctx), + ) { + delete(whitelistedPairs, pair) + delete(pairVotes, pair) + continue + } + } +} + +// Tally calculates the median and returns it. Sets the set of voters to be +// rewarded, i.e. voted within a reasonable spread from the weighted median to +// the store. +// +// ALERT: This function mutates validatorPerformances slice based on the votes +// made by the validators. +func Tally( + votes types.ExchangeRateVotes, + rewardBand math.LegacyDec, + validatorPerformances types.ValidatorPerformances, +) math.LegacyDec { + weightedMedian := votes.WeightedMedianWithAssertion() + standardDeviation := votes.StandardDeviation(weightedMedian) + rewardSpread := weightedMedian.Mul(rewardBand.QuoInt64(2)) + + if standardDeviation.GT(rewardSpread) { + rewardSpread = standardDeviation + } + + for _, v := range votes { + // Filter votes winners & abstain voters + isInsideSpread := v.ExchangeRate.GTE(weightedMedian.Sub(rewardSpread)) && + v.ExchangeRate.LTE(weightedMedian.Add(rewardSpread)) + isAbstainVote := !v.ExchangeRate.IsPositive() // strictly less than zero, don't want to include zero + isMiss := !isInsideSpread && !isAbstainVote + + validatorPerformance := validatorPerformances[v.Voter.String()] + + switch { + case isInsideSpread: + validatorPerformance.RewardWeight += v.Power + validatorPerformance.WinCount++ + case isMiss: + validatorPerformance.MissCount++ + case isAbstainVote: + validatorPerformance.AbstainCount++ + } + + validatorPerformances[v.Voter.String()] = validatorPerformance + } + + return weightedMedian +} diff --git a/x/oracle/keeper/ballot_test.go b/x/oracle/keeper/ballot_test.go new file mode 100644 index 00000000..44cd2a02 --- /dev/null +++ b/x/oracle/keeper/ballot_test.go @@ -0,0 +1,360 @@ +package keeper + +import ( + "sort" + "testing" + + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + "cosmossdk.io/math" + "github.com/NibiruChain/collections" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/set" + "github.com/archway-network/archway/x/common/testutil" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestGroupVotesByPair(t *testing.T) { + fixture := CreateTestFixture(t) + + power := int64(100) + amt := sdk.TokensFromConsensusPower(power, sdk.DefaultPowerReduction) + sh := stakingkeeper.NewMsgServerImpl(&fixture.StakingKeeper) + + // Validator created + _, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], amt)) + require.NoError(t, err) + staking.EndBlocker(fixture.Ctx, &fixture.StakingKeeper) + + pairBtc := asset.Registry.Pair(denoms.BTC, denoms.NUSD) + pairEth := asset.Registry.Pair(denoms.ETH, denoms.NUSD) + btcVotes := types.ExchangeRateVotes{ + {Pair: pairBtc, ExchangeRate: math.LegacyNewDec(17), Voter: ValAddrs[0], Power: power}, + {Pair: pairBtc, ExchangeRate: math.LegacyNewDec(10), Voter: ValAddrs[1], Power: power}, + {Pair: pairBtc, ExchangeRate: math.LegacyNewDec(6), Voter: ValAddrs[2], Power: power}, + } + ethVotes := types.ExchangeRateVotes{ + {Pair: pairEth, ExchangeRate: math.LegacyNewDec(1_000), Voter: ValAddrs[0], Power: power}, + {Pair: pairEth, ExchangeRate: math.LegacyNewDec(1_300), Voter: ValAddrs[1], Power: power}, + {Pair: pairEth, ExchangeRate: math.LegacyNewDec(2_000), Voter: ValAddrs[2], Power: power}, + } + + for i, v := range btcVotes { + fixture.OracleKeeper.Votes.Insert( + fixture.Ctx, + ValAddrs[i], + types.NewAggregateExchangeRateVote( + types.ExchangeRateTuples{ + {Pair: v.Pair, ExchangeRate: v.ExchangeRate}, + {Pair: ethVotes[i].Pair, ExchangeRate: ethVotes[i].ExchangeRate}, + }, + ValAddrs[i], + ), + ) + } + + // organize votes by pair + pairVotes := fixture.OracleKeeper.groupVotesByPair(fixture.Ctx, types.ValidatorPerformances{ + ValAddrs[0].String(): { + Power: power, + WinCount: 0, + ValAddress: ValAddrs[0], + }, + ValAddrs[1].String(): { + Power: power, + WinCount: 0, + ValAddress: ValAddrs[1], + }, + ValAddrs[2].String(): { + Power: power, + WinCount: 0, + ValAddress: ValAddrs[2], + }, + }) + + // sort each votes for comparison + sort.Sort(btcVotes) + sort.Sort(ethVotes) + sort.Sort(pairVotes[asset.Registry.Pair(denoms.BTC, denoms.NUSD)]) + sort.Sort(pairVotes[asset.Registry.Pair(denoms.ETH, denoms.NUSD)]) + + require.Equal(t, btcVotes, pairVotes[asset.Registry.Pair(denoms.BTC, denoms.NUSD)]) + require.Equal(t, ethVotes, pairVotes[asset.Registry.Pair(denoms.ETH, denoms.NUSD)]) +} + +func TestClearVotesAndPrevotes(t *testing.T) { + fixture := CreateTestFixture(t) + + power := int64(100) + amt := sdk.TokensFromConsensusPower(power, sdk.DefaultPowerReduction) + sh := stakingkeeper.NewMsgServerImpl(&fixture.StakingKeeper) + + // Validator created + _, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], amt)) + require.NoError(t, err) + staking.EndBlocker(fixture.Ctx, &fixture.StakingKeeper) + + btcVotes := types.ExchangeRateVotes{ + types.NewExchangeRateVote(math.LegacyNewDec(17), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[0], power), + types.NewExchangeRateVote(math.LegacyNewDec(10), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[1], power), + types.NewExchangeRateVote(math.LegacyNewDec(6), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[2], power), + } + ethVotes := types.ExchangeRateVotes{ + types.NewExchangeRateVote(math.LegacyNewDec(1000), asset.Registry.Pair(denoms.ETH, denoms.NUSD), ValAddrs[0], power), + types.NewExchangeRateVote(math.LegacyNewDec(1300), asset.Registry.Pair(denoms.ETH, denoms.NUSD), ValAddrs[1], power), + types.NewExchangeRateVote(math.LegacyNewDec(2000), asset.Registry.Pair(denoms.ETH, denoms.NUSD), ValAddrs[2], power), + } + + for i := range btcVotes { + fixture.OracleKeeper.Prevotes.Insert(fixture.Ctx, ValAddrs[i], types.AggregateExchangeRatePrevote{ + Hash: "", + Voter: ValAddrs[i].String(), + SubmitBlock: uint64(fixture.Ctx.BlockHeight()), + }) + + fixture.OracleKeeper.Votes.Insert(fixture.Ctx, ValAddrs[i], + types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{ + {Pair: btcVotes[i].Pair, ExchangeRate: btcVotes[i].ExchangeRate}, + {Pair: ethVotes[i].Pair, ExchangeRate: ethVotes[i].ExchangeRate}, + }, ValAddrs[i])) + } + + fixture.OracleKeeper.clearVotesAndPrevotes(fixture.Ctx, 10) + + prevoteCounter := len(fixture.OracleKeeper.Prevotes.Iterate(fixture.Ctx, collections.Range[sdk.ValAddress]{}).Keys()) + voteCounter := len(fixture.OracleKeeper.Votes.Iterate(fixture.Ctx, collections.Range[sdk.ValAddress]{}).Keys()) + + require.Equal(t, prevoteCounter, 3) + require.Equal(t, voteCounter, 0) + + // vote period starts at b=10, clear the votes at b=0 and below. + fixture.OracleKeeper.clearVotesAndPrevotes(fixture.Ctx.WithBlockHeight(fixture.Ctx.BlockHeight()+10), 10) + prevoteCounter = len(fixture.OracleKeeper.Prevotes.Iterate(fixture.Ctx, collections.Range[sdk.ValAddress]{}).Keys()) + require.Equal(t, prevoteCounter, 0) +} + +func TestFuzzTally(t *testing.T) { + validators := map[string]int64{} + + f := fuzz.New().NilChance(0).Funcs( + func(e *math.LegacyDec, c fuzz.Continue) { + *e = math.LegacyNewDec(c.Int63()) + }, + func(e *map[string]int64, c fuzz.Continue) { + numValidators := c.Intn(100) + 5 + + for i := 0; i < numValidators; i++ { + (*e)[sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()).String()] = c.Int63n(100) + } + }, + func(e *types.ValidatorPerformances, c fuzz.Continue) { + for validator, power := range validators { + addr, err := sdk.ValAddressFromBech32(validator) + require.NoError(t, err) + (*e)[validator] = types.NewValidatorPerformance(power, addr) + } + }, + func(e *types.ExchangeRateVotes, c fuzz.Continue) { + votes := types.ExchangeRateVotes{} + for addr, power := range validators { + addr, _ := sdk.ValAddressFromBech32(addr) + + var rate math.LegacyDec + c.Fuzz(&rate) + + votes = append(votes, types.NewExchangeRateVote(rate, asset.NewPair(c.RandString(), c.RandString()), addr, power)) + } + + *e = votes + }, + ) + + // set random pairs and validators + f.Fuzz(&validators) + + claimMap := types.ValidatorPerformances{} + f.Fuzz(&claimMap) + + votes := types.ExchangeRateVotes{} + f.Fuzz(&votes) + + var rewardBand math.LegacyDec + f.Fuzz(&rewardBand) + + require.NotPanics(t, func() { + Tally(votes, rewardBand, claimMap) + }) +} + +type VoteMap = map[asset.Pair]types.ExchangeRateVotes + +func TestRemoveInvalidBallots(t *testing.T) { + testCases := []struct { + name string + voteMap VoteMap + }{ + { + name: "empty key, empty votes", + voteMap: VoteMap{ + "": types.ExchangeRateVotes{}, + }, + }, + { + name: "nonempty key, empty votes", + voteMap: VoteMap{ + "xxx": types.ExchangeRateVotes{}, + }, + }, + { + name: "nonempty keys, empty votes", + voteMap: VoteMap{ + "xxx": types.ExchangeRateVotes{}, + "abc123": types.ExchangeRateVotes{}, + }, + }, + { + name: "mixed empty keys, empty votes", + voteMap: VoteMap{ + "xxx": types.ExchangeRateVotes{}, + "": types.ExchangeRateVotes{}, + "abc123": types.ExchangeRateVotes{}, + "0x": types.ExchangeRateVotes{}, + }, + }, + { + name: "empty key, nonempty votes, not whitelisted", + voteMap: VoteMap{ + "": types.ExchangeRateVotes{ + {Pair: "", ExchangeRate: math.LegacyZeroDec(), Voter: sdk.ValAddress{}, Power: 0}, + }, + }, + }, + { + name: "nonempty key, nonempty votes, whitelisted", + voteMap: VoteMap{ + "x": types.ExchangeRateVotes{ + {Pair: "x", ExchangeRate: math.LegacyDec{}, Voter: sdk.ValAddress{123}, Power: 5}, + }, + asset.Registry.Pair(denoms.BTC, denoms.NUSD): types.ExchangeRateVotes{ + {Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), ExchangeRate: math.LegacyDec{}, Voter: sdk.ValAddress{123}, Power: 5}, + }, + asset.Registry.Pair(denoms.ETH, denoms.NUSD): types.ExchangeRateVotes{ + {Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), ExchangeRate: math.LegacyDec{}, Voter: sdk.ValAddress{123}, Power: 5}, + }, + }, + }, + } + + for _, testCase := range testCases { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + fixture, _ := Setup(t) + assert.NotPanics(t, func() { + fixture.OracleKeeper.removeInvalidVotes(fixture.Ctx, tc.voteMap, set.New[asset.Pair]( + asset.NewPair(denoms.BTC, denoms.NUSD), + asset.NewPair(denoms.ETH, denoms.NUSD), + )) + }, "voteMap: %v", tc.voteMap) + }) + } +} + +func TestFuzzPickReferencePair(t *testing.T) { + var pairs []asset.Pair + + f := fuzz.New().NilChance(0).Funcs( + func(e *asset.Pair, c fuzz.Continue) { + *e = asset.NewPair(testutil.RandLetters(5), testutil.RandLetters(5)) + }, + func(e *[]asset.Pair, c fuzz.Continue) { + numPairs := c.Intn(100) + 5 + + for i := 0; i < numPairs; i++ { + *e = append(*e, asset.NewPair(testutil.RandLetters(5), testutil.RandLetters(5))) + } + }, + func(e *math.LegacyDec, c fuzz.Continue) { + *e = math.LegacyNewDec(c.Int63()) + }, + func(e *map[asset.Pair]math.LegacyDec, c fuzz.Continue) { + for _, pair := range pairs { + var rate math.LegacyDec + c.Fuzz(&rate) + + (*e)[pair] = rate + } + }, + func(e *map[string]int64, c fuzz.Continue) { + for i := 0; i < 5+c.Intn(100); i++ { + (*e)[sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()).String()] = int64(c.Intn(100) + 1) + } + }, + func(e *map[asset.Pair]types.ExchangeRateVotes, c fuzz.Continue) { + validators := map[string]int64{} + c.Fuzz(&validators) + + for _, pair := range pairs { + votes := types.ExchangeRateVotes{} + + for addr, power := range validators { + addr, _ := sdk.ValAddressFromBech32(addr) + + var rate math.LegacyDec + c.Fuzz(&rate) + + votes = append(votes, types.NewExchangeRateVote(rate, pair, addr, power)) + } + + (*e)[pair] = votes + } + }, + ) + + // set random pairs + f.Fuzz(&pairs) + + input, _ := Setup(t) + + // test OracleKeeper.Pairs.Insert + voteTargets := set.Set[asset.Pair]{} + f.Fuzz(&voteTargets) + whitelistedPairs := make(set.Set[asset.Pair]) + + for key := range voteTargets { + whitelistedPairs.Add(key) + } + + // test OracleKeeper.RemoveInvalidBallots + voteMap := map[asset.Pair]types.ExchangeRateVotes{} + f.Fuzz(&voteMap) + + assert.NotPanics(t, func() { + input.OracleKeeper.removeInvalidVotes(input.Ctx, voteMap, whitelistedPairs) + }, "voteMap: %v", voteMap) +} + +func TestZeroBallotPower(t *testing.T) { + btcVotess := types.ExchangeRateVotes{ + types.NewExchangeRateVote(math.LegacyNewDec(17), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[0], 0), + types.NewExchangeRateVote(math.LegacyNewDec(10), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[1], 0), + types.NewExchangeRateVote(math.LegacyNewDec(6), asset.Registry.Pair(denoms.BTC, denoms.NUSD), ValAddrs[2], 0), + } + + assert.False(t, isPassingVoteThreshold(btcVotess, math.ZeroInt(), 0)) +} diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go new file mode 100644 index 00000000..ec7557b4 --- /dev/null +++ b/x/oracle/keeper/keeper.go @@ -0,0 +1,206 @@ +package keeper + +import ( + "fmt" + "time" + + storetypes "cosmossdk.io/store/types" + + sdkerrors "cosmossdk.io/errors" + "cosmossdk.io/log" + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/oracle/types" +) + +// Keeper of the oracle store +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + + AccountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + distrKeeper types.DistributionKeeper + StakingKeeper types.StakingKeeper + slashingKeeper types.SlashingKeeper + + distrModuleName string + + // Module parameters + Params collections.Item[types.Params] + ExchangeRates collections.Map[asset.Pair, types.DatedPrice] + FeederDelegations collections.Map[sdk.ValAddress, sdk.AccAddress] + MissCounters collections.Map[sdk.ValAddress, uint64] + Prevotes collections.Map[sdk.ValAddress, types.AggregateExchangeRatePrevote] + Votes collections.Map[sdk.ValAddress, types.AggregateExchangeRateVote] + + // PriceSnapshots maps types.PriceSnapshot to the asset.Pair of the snapshot and the creation timestamp as keys.Uint64Key. + PriceSnapshots collections.Map[ + collections.Pair[asset.Pair, time.Time], + types.PriceSnapshot] + WhitelistedPairs collections.KeySet[asset.Pair] + Rewards collections.Map[uint64, types.Rewards] + RewardsID collections.Sequence +} + +// NewKeeper constructs a new keeper for oracle +func NewKeeper( + cdc codec.BinaryCodec, + storeKey storetypes.StoreKey, + + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + distrKeeper types.DistributionKeeper, + stakingKeeper types.StakingKeeper, + slashingKeeper types.SlashingKeeper, + + distrName string, +) Keeper { + // ensure oracle module account is set + if addr := accountKeeper.GetModuleAddress(types.ModuleName); addr == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) + } + + k := Keeper{ + cdc: cdc, + storeKey: storeKey, + AccountKeeper: accountKeeper, + bankKeeper: bankKeeper, + distrKeeper: distrKeeper, + StakingKeeper: stakingKeeper, + slashingKeeper: slashingKeeper, + distrModuleName: distrName, + Params: collections.NewItem(storeKey, 11, collections.ProtoValueEncoder[types.Params](cdc)), + ExchangeRates: collections.NewMap(storeKey, 1, asset.PairKeyEncoder, collections.ProtoValueEncoder[types.DatedPrice](cdc)), + PriceSnapshots: collections.NewMap(storeKey, 10, collections.PairKeyEncoder(asset.PairKeyEncoder, collections.TimeKeyEncoder), collections.ProtoValueEncoder[types.PriceSnapshot](cdc)), + FeederDelegations: collections.NewMap(storeKey, 2, collections.ValAddressKeyEncoder, collections.AccAddressValueEncoder), + MissCounters: collections.NewMap(storeKey, 3, collections.ValAddressKeyEncoder, collections.Uint64ValueEncoder), + Prevotes: collections.NewMap(storeKey, 4, collections.ValAddressKeyEncoder, collections.ProtoValueEncoder[types.AggregateExchangeRatePrevote](cdc)), + Votes: collections.NewMap(storeKey, 5, collections.ValAddressKeyEncoder, collections.ProtoValueEncoder[types.AggregateExchangeRateVote](cdc)), + WhitelistedPairs: collections.NewKeySet(storeKey, 6, asset.PairKeyEncoder), + Rewards: collections.NewMap( + storeKey, 7, + collections.Uint64KeyEncoder, collections.ProtoValueEncoder[types.Rewards](cdc)), + RewardsID: collections.NewSequence(storeKey, 9), + } + return k +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// ValidateFeeder return the given feeder is allowed to feed the message or not +func (k Keeper) ValidateFeeder( + ctx sdk.Context, feederAddr sdk.AccAddress, validatorAddr sdk.ValAddress, +) error { + // A validator delegates price feeder consent to itself by default. + // Thus, we only need to verify consent for price feeder addresses that don't + // match the validator address. + if !feederAddr.Equals(validatorAddr) { + delegate := k.FeederDelegations.GetOr( + ctx, validatorAddr, sdk.AccAddress(validatorAddr)) + if !delegate.Equals(feederAddr) { + return sdkerrors.Wrapf( + types.ErrNoVotingPermission, + "wanted: %s, got: %s", delegate.String(), feederAddr.String()) + } + } + + // Check that the given validator is in the active set for consensus. + if val, err := k.StakingKeeper.Validator(ctx, validatorAddr); err != nil || !val.IsBonded() { + return sdkerrors.Wrapf( + err, + "validator %s is not active set", + validatorAddr.String(), + ) + } + + return nil +} + +func (k Keeper) GetExchangeRateTwap(ctx sdk.Context, pair asset.Pair) (price math.LegacyDec, err error) { + params, err := k.Params.Get(ctx) + if err != nil { + return math.LegacyOneDec().Neg(), err + } + + snapshots := k.PriceSnapshots.Iterate( + ctx, + collections.PairRange[asset.Pair, time.Time]{}. + Prefix(pair). + StartInclusive( + ctx.BlockTime().Add(-1*params.TwapLookbackWindow)). + EndInclusive( + ctx.BlockTime()), + ).Values() + + if len(snapshots) == 0 { + // if there are no snapshots, return -1 for the price + return math.LegacyOneDec().Neg(), types.ErrNoValidTWAP.Wrapf("no snapshots for pair %s", pair.String()) + } + + if len(snapshots) == 1 { + return snapshots[0].Price, nil + } + + firstTimestampMs := snapshots[0].TimestampMs + if firstTimestampMs > ctx.BlockTime().UnixMilli() { + // should never happen, or else we have corrupted state + return math.LegacyOneDec().Neg(), types.ErrNoValidTWAP.Wrapf( + "Possible corrupted state. First timestamp %d is after current blocktime %d", firstTimestampMs, ctx.BlockTime().UnixMilli()) + } + + if firstTimestampMs == ctx.BlockTime().UnixMilli() { + // shouldn't happen because we check for len(snapshots) == 1, but if it does, return the first snapshot price + return snapshots[0].Price, nil + } + + cumulativePrice := math.LegacyZeroDec() + for i, s := range snapshots { + var nextTimestampMs int64 + if i == len(snapshots)-1 { + // if we're at the last snapshot, then consider that price as ongoing until the current blocktime + nextTimestampMs = ctx.BlockTime().UnixMilli() + } else { + nextTimestampMs = snapshots[i+1].TimestampMs + } + + price := s.Price.MulInt64(nextTimestampMs - s.TimestampMs) + cumulativePrice = cumulativePrice.Add(price) + } + + return cumulativePrice.QuoInt64(ctx.BlockTime().UnixMilli() - firstTimestampMs), nil +} + +func (k Keeper) GetExchangeRate(ctx sdk.Context, pair asset.Pair) (price math.LegacyDec, err error) { + exchangeRate, err := k.ExchangeRates.Get(ctx, pair) + price = exchangeRate.ExchangeRate + return +} + +// SetPrice sets the price for a pair as well as the price snapshot. +func (k Keeper) SetPrice(ctx sdk.Context, pair asset.Pair, price math.LegacyDec) { + k.ExchangeRates.Insert(ctx, pair, types.DatedPrice{ExchangeRate: price, CreatedBlock: uint64(ctx.BlockHeight())}) + + key := collections.Join(pair, ctx.BlockTime()) + timestampMs := ctx.BlockTime().UnixMilli() + k.PriceSnapshots.Insert(ctx, key, types.PriceSnapshot{ + Pair: pair, + Price: price, + TimestampMs: timestampMs, + }) + if err := ctx.EventManager().EmitTypedEvent(&types.EventPriceUpdate{ + Pair: pair.String(), + Price: price, + TimestampMs: timestampMs, + }); err != nil { + ctx.Logger().Error("failed to emit OraclePriceUpdate", "pair", pair, "error", err) + } +} diff --git a/x/oracle/keeper/keeper_test.go b/x/oracle/keeper/keeper_test.go new file mode 100644 index 00000000..2aeed42b --- /dev/null +++ b/x/oracle/keeper/keeper_test.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "testing" + + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" +) + +func TestValidateFeeder(t *testing.T) { + // initial setup + input := CreateTestFixture(t) + addr, val := ValAddrs[0], ValPubKeys[0] + addr1, val1 := ValAddrs[1], ValPubKeys[1] + amt := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction) + sh := stakingkeeper.NewMsgServerImpl(&input.StakingKeeper) + ctx := input.Ctx + + // Create 2 validators. + _, err := sh.CreateValidator(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(ctx, NewTestMsgCreateValidator(addr1, val1, amt)) + require.NoError(t, err) + staking.EndBlocker(ctx, &input.StakingKeeper) + + require.Equal( + t, input.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(input.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), + ) + require.Equal(t, amt, input.StakingKeeper.Validator(ctx, addr).GetBondedTokens()) + require.Equal( + t, input.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr1)), + sdk.NewCoins(sdk.NewCoin(input.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), + ) + require.Equal(t, amt, input.StakingKeeper.Validator(ctx, addr1).GetBondedTokens()) + + require.NoError(t, input.OracleKeeper.ValidateFeeder(input.Ctx, sdk.AccAddress(addr), sdk.ValAddress(addr))) + require.NoError(t, input.OracleKeeper.ValidateFeeder(input.Ctx, sdk.AccAddress(addr1), sdk.ValAddress(addr1))) + + // delegate works + input.OracleKeeper.FeederDelegations.Insert(input.Ctx, addr, sdk.AccAddress(addr1)) + require.NoError(t, input.OracleKeeper.ValidateFeeder(input.Ctx, sdk.AccAddress(addr1), addr)) + require.Error(t, input.OracleKeeper.ValidateFeeder(input.Ctx, Addrs[2], addr)) + + // only active validators can do oracle votes + validator, found := input.StakingKeeper.GetValidator(input.Ctx, addr) + require.True(t, found) + validator.Status = stakingtypes.Unbonded + input.StakingKeeper.SetValidator(input.Ctx, validator) + require.Error(t, input.OracleKeeper.ValidateFeeder(input.Ctx, sdk.AccAddress(addr1), addr)) +} diff --git a/x/oracle/keeper/msg_server.go b/x/oracle/keeper/msg_server.go new file mode 100644 index 00000000..d3cb863d --- /dev/null +++ b/x/oracle/keeper/msg_server.go @@ -0,0 +1,168 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/types/errors" + + sdkerrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/oracle/types" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the oracle MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +func (ms msgServer) AggregateExchangeRatePrevote( + goCtx context.Context, + msg *types.MsgAggregateExchangeRatePrevote, +) (*types.MsgAggregateExchangeRatePrevoteResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + valAddr, err := sdk.ValAddressFromBech32(msg.Validator) + if err != nil { + return nil, err + } + + feederAddr, err := sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + return nil, err + } + + if err := ms.ValidateFeeder(ctx, feederAddr, valAddr); err != nil { + return nil, err + } + + // Convert hex string to votehash + voteHash, err := types.AggregateVoteHashFromHexString(msg.Hash) + if err != nil { + return nil, sdkerrors.Wrap(types.ErrInvalidHash, err.Error()) + } + + ms.Keeper.Prevotes.Insert(ctx, valAddr, types.NewAggregateExchangeRatePrevote(voteHash, valAddr, uint64(ctx.BlockHeight()))) + + err = ctx.EventManager().EmitTypedEvent(&types.EventAggregatePrevote{ + Validator: msg.Validator, + Feeder: msg.Feeder, + }) + return &types.MsgAggregateExchangeRatePrevoteResponse{}, err +} + +func (ms msgServer) AggregateExchangeRateVote( + goCtx context.Context, msg *types.MsgAggregateExchangeRateVote, +) (msgResp *types.MsgAggregateExchangeRateVoteResponse, err error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + valAddr, err := sdk.ValAddressFromBech32(msg.Validator) + if err != nil { + return nil, err + } + + feederAddr, err := sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + return nil, err + } + + if err := ms.ValidateFeeder(ctx, feederAddr, valAddr); err != nil { + return nil, err + } + + params, err := ms.Keeper.Params.Get(ctx) + if err != nil { + return nil, err + } + + // An aggergate prevote is required to get an aggregate vote. + aggregatePrevote, err := ms.Keeper.Prevotes.Get(ctx, valAddr) + if err != nil { + return nil, sdkerrors.Wrap(types.ErrNoAggregatePrevote, msg.Validator) + } + + // Check a msg is submitted proper period + // This condition necessary for the commit-reveal scheme. + if (uint64(ctx.BlockHeight())/params.VotePeriod)-(aggregatePrevote.SubmitBlock/params.VotePeriod) != 1 { + return nil, types.ErrRevealPeriodMissMatch.Wrapf( + "aggregate prevote block: %d, current block: %d, vote period: %d", + aggregatePrevote.SubmitBlock, ctx.BlockHeight(), params.VotePeriod, + ) + } + + // Slice of (Pair, ExchangeRate) tuples. + exchangeRateTuples, err := types.ParseExchangeRateTuples(msg.ExchangeRates) + if err != nil { + return nil, sdkerrors.Wrap(errors.ErrInvalidCoins, err.Error()) + } + + // Check all pairs are in the vote target + for _, tuple := range exchangeRateTuples { + if !ms.IsWhitelistedPair(ctx, tuple.Pair) { + return nil, sdkerrors.Wrap(types.ErrUnknownPair, tuple.Pair.String()) + } + } + + // Verify an exchange rate with aggregate prevote hash + hash := types.GetAggregateVoteHash(msg.Salt, msg.ExchangeRates, valAddr) + if aggregatePrevote.Hash != hash.String() { + return nil, sdkerrors.Wrapf( + types.ErrHashVerificationFailed, "must be given %s not %s", aggregatePrevote.Hash, hash, + ) + } + + // Move aggregate prevote to aggregate vote with given exchange rates + ms.Keeper.Votes.Insert( + ctx, valAddr, types.NewAggregateExchangeRateVote(exchangeRateTuples, valAddr), + ) + _ = ms.Keeper.Prevotes.Delete(ctx, valAddr) + + priceTuples, err := types.NewExchangeRateTuplesFromString(msg.ExchangeRates) + if err != nil { + return + } + err = ctx.EventManager().EmitTypedEvent(&types.EventAggregateVote{ + Validator: msg.Validator, + Feeder: msg.Feeder, + Prices: priceTuples, + }) + + return &types.MsgAggregateExchangeRateVoteResponse{}, err +} + +func (ms msgServer) DelegateFeedConsent( + goCtx context.Context, msg *types.MsgDelegateFeedConsent, +) (*types.MsgDelegateFeedConsentResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + operatorAddr, err := sdk.ValAddressFromBech32(msg.Operator) + if err != nil { + return nil, err + } + + delegateAddr, err := sdk.AccAddressFromBech32(msg.Delegate) + if err != nil { + return nil, err + } + + // Check the delegator is a validator + _, err = ms.StakingKeeper.Validator(ctx, operatorAddr) + if err != nil { + return nil, sdkerrors.Wrap(err, msg.Operator) + } + + // Set the delegation + ms.Keeper.FeederDelegations.Insert(ctx, operatorAddr, delegateAddr) + + err = ctx.EventManager().EmitTypedEvent(&types.EventDelegateFeederConsent{ + Feeder: msg.Delegate, + Validator: msg.Operator, + }) + + return &types.MsgDelegateFeedConsentResponse{}, err +} diff --git a/x/oracle/keeper/msg_server_test.go b/x/oracle/keeper/msg_server_test.go new file mode 100644 index 00000000..066587f2 --- /dev/null +++ b/x/oracle/keeper/msg_server_test.go @@ -0,0 +1,194 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestFeederDelegation(t *testing.T) { + input, msgServer := Setup(t) + + exchangeRates := types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), + ExchangeRate: testExchangeRate, + }, + } + + exchangeRateStr, err := exchangeRates.ToString() + require.NoError(t, err) + salt := "1" + hash := types.GetAggregateVoteHash(salt, exchangeRateStr, ValAddrs[0]) + + // Case 1: empty message + delegateFeedConsentMsg := types.MsgDelegateFeedConsent{} + _, err = msgServer.DelegateFeedConsent(sdk.WrapSDKContext(input.Ctx), &delegateFeedConsentMsg) + require.Error(t, err) + + // Case 2: Normal Prevote - without delegation + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), prevoteMsg) + require.NoError(t, err) + + // Case 2.1: Normal Prevote - with delegation fails + prevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), prevoteMsg) + require.Error(t, err) + + // Case 2.2: Normal Vote - without delegation + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(2)), voteMsg) + require.NoError(t, err) + + // Case 2.3: Normal Vote - with delegation fails + voteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(2)), voteMsg) + require.Error(t, err) + + // Case 3: Normal MsgDelegateFeedConsent succeeds + msg := types.NewMsgDelegateFeedConsent(ValAddrs[0], Addrs[1]) + _, err = msgServer.DelegateFeedConsent(sdk.WrapSDKContext(input.Ctx), msg) + require.NoError(t, err) + + // Case 4.1: Normal Prevote - without delegation fails + prevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[2], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), prevoteMsg) + require.Error(t, err) + + // Case 4.2: Normal Prevote - with delegation succeeds + prevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), prevoteMsg) + require.NoError(t, err) + + // Case 4.3: Normal Vote - without delegation fails + voteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[2], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(2)), voteMsg) + require.Error(t, err) + + // Case 4.4: Normal Vote - with delegation succeeds + voteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(2)), voteMsg) + require.NoError(t, err) +} + +func TestAggregatePrevoteVote(t *testing.T) { + input, msgServer := Setup(t) + + salt := "1" + exchangeRates := types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("1000.23"), + }, + { + Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("0.29"), + }, + + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("0.27"), + }, + } + + otherExchangeRate := types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("1000.23"), + }, + { + Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("0.29"), + }, + + { + Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("0.27"), + }, + } + + unintendedExchangeRateStr := types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("1000.23"), + }, + { + Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), + ExchangeRate: math.LegacyMustNewDecFromStr("0.29"), + }, + { + Pair: "BTC:CNY", + ExchangeRate: math.LegacyMustNewDecFromStr("0.27"), + }, + } + exchangeRatesStr, err := exchangeRates.ToString() + require.NoError(t, err) + + otherExchangeRateStr, err := otherExchangeRate.ToString() + require.NoError(t, err) + + unintendedExchageRateStr, err := unintendedExchangeRateStr.ToString() + require.NoError(t, err) + + hash := types.GetAggregateVoteHash(salt, exchangeRatesStr, ValAddrs[0]) + + aggregateExchangeRatePrevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRatePrevoteMsg) + require.NoError(t, err) + + // Unauthorized feeder + aggregateExchangeRatePrevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRatePrevoteMsg) + require.Error(t, err) + + // Invalid addr + aggregateExchangeRatePrevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, sdk.AccAddress{}, ValAddrs[0]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRatePrevoteMsg) + require.Error(t, err) + + // Invalid validator addr + aggregateExchangeRatePrevoteMsg = types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[0], sdk.ValAddress{}) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRatePrevoteMsg) + require.Error(t, err) + + // Invalid reveal period + aggregateExchangeRateVoteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.Error(t, err) + + // Invalid reveal period + input.Ctx = input.Ctx.WithBlockHeight(3) + aggregateExchangeRateVoteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.Error(t, err) + + // Other exchange rate with valid real period + input.Ctx = input.Ctx.WithBlockHeight(2) + aggregateExchangeRateVoteMsg = types.NewMsgAggregateExchangeRateVote(salt, otherExchangeRateStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.Error(t, err) + + // Unauthorized feeder + aggregateExchangeRateVoteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, Addrs[1], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.Error(t, err) + + // Unintended denom vote + aggregateExchangeRateVoteMsg = types.NewMsgAggregateExchangeRateVote(salt, unintendedExchageRateStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.Error(t, err) + + // Valid exchange rate reveal submission + input.Ctx = input.Ctx.WithBlockHeight(2) + aggregateExchangeRateVoteMsg = types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, Addrs[0], ValAddrs[0]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx), aggregateExchangeRateVoteMsg) + require.NoError(t, err) +} diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go new file mode 100644 index 00000000..ec8ae50c --- /dev/null +++ b/x/oracle/keeper/params.go @@ -0,0 +1,75 @@ +package keeper + +import ( + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/oracle/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// UpdateParams updates the oracle parameters +func (k Keeper) UpdateParams(ctx sdk.Context, params types.Params) { + k.Params.Set(ctx, params) +} + +// VotePeriod returns the number of blocks during which voting takes place. +func (k Keeper) VotePeriod(ctx sdk.Context) (res uint64) { + params, _ := k.Params.Get(ctx) + return params.VotePeriod +} + +// VoteThreshold returns the minimum percentage of votes that must be received for a votes to pass. +func (k Keeper) VoteThreshold(ctx sdk.Context) (res math.LegacyDec) { + params, _ := k.Params.Get(ctx) + return params.VoteThreshold +} + +// MinVoters returns the minimum percentage of votes that must be received for a votes to pass. +func (k Keeper) MinVoters(ctx sdk.Context) (res uint64) { + params, _ := k.Params.Get(ctx) + return params.MinVoters +} + +// RewardBand returns a maxium divergence that a price vote can have from the +// weighted median in the votes. If a vote lies within the valid range +// defined by: +// +// μ := weightedMedian, +// validRange := μ ± (μ * rewardBand / 2), +// +// then rewards are added to the validator performance. +// Note that if the reward band is smaller than 1 standard +// deviation, the band is taken to be 1 standard deviation. +func (k Keeper) RewardBand(ctx sdk.Context) (res math.LegacyDec) { + params, _ := k.Params.Get(ctx) + return params.RewardBand +} + +// Whitelist returns the pair list that can be activated +func (k Keeper) Whitelist(ctx sdk.Context) (res []asset.Pair) { + params, _ := k.Params.Get(ctx) + return params.Whitelist +} + +// SlashFraction returns oracle voting penalty rate +func (k Keeper) SlashFraction(ctx sdk.Context) (res math.LegacyDec) { + params, _ := k.Params.Get(ctx) + return params.SlashFraction +} + +// SlashWindow returns the number of voting periods that specify a "slash window". +// After each slash window, all oracles that have missed more than the penalty +// threshold are slashed. Missing the penalty threshold is synonymous with +// submitting fewer valid votes than `MinValidPerWindow`. +func (k Keeper) SlashWindow(ctx sdk.Context) (res uint64) { + params, _ := k.Params.Get(ctx) + return params.SlashWindow +} + +// MinValidPerWindow returns oracle slashing threshold +func (k Keeper) MinValidPerWindow(ctx sdk.Context) (res math.LegacyDec) { + params, _ := k.Params.Get(ctx) + return params.MinValidPerWindow +} diff --git a/x/oracle/keeper/params_test.go b/x/oracle/keeper/params_test.go new file mode 100644 index 00000000..cc0c2707 --- /dev/null +++ b/x/oracle/keeper/params_test.go @@ -0,0 +1,55 @@ +package keeper + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestParams(t *testing.T) { + input := CreateTestFixture(t) + + // Test default params setting + input.OracleKeeper.Params.Set(input.Ctx, types.DefaultParams()) + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + require.NotNil(t, params) + + // Test custom params setting + votePeriod := uint64(10) + voteThreshold := math.LegacyNewDecWithPrec(33, 2) + minVoters := uint64(4) + oracleRewardBand := math.LegacyNewDecWithPrec(1, 2) + slashFraction := math.LegacyNewDecWithPrec(1, 2) + slashWindow := uint64(1000) + minValidPerWindow := math.LegacyNewDecWithPrec(1, 4) + minFeeRatio := math.LegacyNewDecWithPrec(1, 2) + whitelist := []asset.Pair{ + asset.Registry.Pair(denoms.BTC, denoms.NUSD), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + } + + // Should really test validateParams, but skipping because obvious + newParams := types.Params{ + VotePeriod: votePeriod, + VoteThreshold: voteThreshold, + MinVoters: minVoters, + RewardBand: oracleRewardBand, + Whitelist: whitelist, + SlashFraction: slashFraction, + SlashWindow: slashWindow, + MinValidPerWindow: minValidPerWindow, + ValidatorFeeRatio: minFeeRatio, + } + input.OracleKeeper.Params.Set(input.Ctx, newParams) + + storedParams, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + require.NotNil(t, storedParams) + require.Equal(t, storedParams, newParams) +} diff --git a/x/oracle/keeper/querier.go b/x/oracle/keeper/querier.go new file mode 100644 index 00000000..a2e46cf6 --- /dev/null +++ b/x/oracle/keeper/querier.go @@ -0,0 +1,194 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/oracle/types" +) + +// querier is used as Keeper will have duplicate methods if used directly, and gRPC names take precedence over q +type querier struct { + Keeper +} + +// NewQuerier returns an implementation of the oracle QueryServer interface +// for the provided Keeper. +func NewQuerier(keeper Keeper) types.QueryServer { + return &querier{Keeper: keeper} +} + +var _ types.QueryServer = querier{} + +// Params queries params of distribution module +func (q querier) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + var params types.Params + + params, err := q.Keeper.Params.Get(ctx) + if err != nil { + return nil, err + } + + return &types.QueryParamsResponse{Params: params}, nil +} + +// ExchangeRate queries exchange rate of a pair +func (q querier) ExchangeRate(c context.Context, req *types.QueryExchangeRateRequest) (*types.QueryExchangeRateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + if len(req.Pair) == 0 { + return nil, status.Error(codes.InvalidArgument, "empty pair") + } + + ctx := sdk.UnwrapSDKContext(c) + exchangeRate, err := q.Keeper.GetExchangeRate(ctx, req.Pair) + if err != nil { + return nil, err + } + + return &types.QueryExchangeRateResponse{ExchangeRate: exchangeRate}, nil +} + +/* +Gets the time-weighted average price from ( ctx.BlockTime() - interval, ctx.BlockTime() ] +Note the open-ended right bracket. + +If there's only one snapshot, then this function returns the price from that single snapshot. + +Returns -1 if there's no price. +*/ +func (q querier) ExchangeRateTwap(c context.Context, req *types.QueryExchangeRateRequest) (response *types.QueryExchangeRateResponse, err error) { + if _, err = q.ExchangeRate(c, req); err != nil { + return + } + + ctx := sdk.UnwrapSDKContext(c) + twap, err := q.Keeper.GetExchangeRateTwap(ctx, req.Pair) + if err != nil { + return &types.QueryExchangeRateResponse{}, err + } + return &types.QueryExchangeRateResponse{ExchangeRate: twap}, nil +} + +// ExchangeRates queries exchange rates of all pairs +func (q querier) ExchangeRates(c context.Context, _ *types.QueryExchangeRatesRequest) (*types.QueryExchangeRatesResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + + var exchangeRates types.ExchangeRateTuples + for _, er := range q.Keeper.ExchangeRates.Iterate(ctx, collections.Range[asset.Pair]{}).KeyValues() { + exchangeRates = append(exchangeRates, types.ExchangeRateTuple{ + Pair: er.Key, + ExchangeRate: er.Value.ExchangeRate, + }) + } + + return &types.QueryExchangeRatesResponse{ExchangeRates: exchangeRates}, nil +} + +// Actives queries all pairs for which exchange rates exist +func (q querier) Actives(c context.Context, _ *types.QueryActivesRequest) (*types.QueryActivesResponse, error) { + return &types.QueryActivesResponse{Actives: q.Keeper.ExchangeRates.Iterate(sdk.UnwrapSDKContext(c), collections.Range[asset.Pair]{}).Keys()}, nil +} + +// VoteTargets queries the voting target list on current vote period +func (q querier) VoteTargets(c context.Context, _ *types.QueryVoteTargetsRequest) (*types.QueryVoteTargetsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + return &types.QueryVoteTargetsResponse{VoteTargets: q.GetWhitelistedPairs(ctx)}, nil +} + +// FeederDelegation queries the account address that the validator operator delegated oracle vote rights to +func (q querier) FeederDelegation(c context.Context, req *types.QueryFeederDelegationRequest) (*types.QueryFeederDelegationResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(c) + return &types.QueryFeederDelegationResponse{ + FeederAddr: q.Keeper.FeederDelegations.GetOr(ctx, valAddr, sdk.AccAddress(valAddr)).String(), + }, nil +} + +// MissCounter queries oracle miss counter of a validator +func (q querier) MissCounter(c context.Context, req *types.QueryMissCounterRequest) (*types.QueryMissCounterResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(c) + return &types.QueryMissCounterResponse{ + MissCounter: q.MissCounters.GetOr(ctx, valAddr, 0), + }, nil +} + +// AggregatePrevote queries an aggregate prevote of a validator +func (q querier) AggregatePrevote(c context.Context, req *types.QueryAggregatePrevoteRequest) (*types.QueryAggregatePrevoteResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(c) + prevote, err := q.Prevotes.Get(ctx, valAddr) + if err != nil { + return nil, err + } + + return &types.QueryAggregatePrevoteResponse{ + AggregatePrevote: prevote, + }, nil +} + +// AggregatePrevotes queries aggregate prevotes of all validators +func (q querier) AggregatePrevotes(c context.Context, _ *types.QueryAggregatePrevotesRequest) (*types.QueryAggregatePrevotesResponse, error) { + return &types.QueryAggregatePrevotesResponse{AggregatePrevotes: q.Prevotes.Iterate(sdk.UnwrapSDKContext(c), collections.Range[sdk.ValAddress]{}).Values()}, nil +} + +// AggregateVote queries an aggregate vote of a validator +func (q querier) AggregateVote(c context.Context, req *types.QueryAggregateVoteRequest) (*types.QueryAggregateVoteResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(c) + vote, err := q.Keeper.Votes.Get(ctx, valAddr) + if err != nil { + return nil, err + } + + return &types.QueryAggregateVoteResponse{ + AggregateVote: vote, + }, nil +} + +// AggregateVotes queries aggregate votes of all validators +func (q querier) AggregateVotes(c context.Context, _ *types.QueryAggregateVotesRequest) (*types.QueryAggregateVotesResponse, error) { + return &types.QueryAggregateVotesResponse{AggregateVotes: q.Keeper.Votes.Iterate(sdk.UnwrapSDKContext(c), collections.Range[sdk.ValAddress]{}).Values()}, nil +} diff --git a/x/oracle/keeper/querier_test.go b/x/oracle/keeper/querier_test.go new file mode 100644 index 00000000..0c7b1553 --- /dev/null +++ b/x/oracle/keeper/querier_test.go @@ -0,0 +1,365 @@ +package keeper + +import ( + "sort" + "testing" + "time" + + "cosmossdk.io/math" + "github.com/NibiruChain/collections" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + testutilevents "github.com/archway-network/archway/x/common/testutil" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestQueryParams(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + + querier := NewQuerier(input.OracleKeeper) + res, err := querier.Params(ctx, &types.QueryParamsRequest{}) + require.NoError(t, err) + + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + + require.Equal(t, params, res.Params) +} + +func TestQueryExchangeRate(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + rate := math.LegacyNewDec(1700) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.ETH, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + + // empty request + _, err := querier.ExchangeRate(ctx, nil) + require.Error(t, err) + + // Query to grpc + res, err := querier.ExchangeRate(ctx, &types.QueryExchangeRateRequest{ + Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD), + }) + require.NoError(t, err) + require.Equal(t, rate, res.ExchangeRate) +} + +func TestQueryMissCounter(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + missCounter := uint64(1) + input.OracleKeeper.MissCounters.Insert(input.Ctx, ValAddrs[0], missCounter) + + // empty request + _, err := querier.MissCounter(ctx, nil) + require.Error(t, err) + + // Query to grpc + res, err := querier.MissCounter(ctx, &types.QueryMissCounterRequest{ + ValidatorAddr: ValAddrs[0].String(), + }) + require.NoError(t, err) + require.Equal(t, missCounter, res.MissCounter) +} + +func TestQueryExchangeRates(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + rate := math.LegacyNewDec(1700) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.ETH, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + + res, err := querier.ExchangeRates(ctx, &types.QueryExchangeRatesRequest{}) + require.NoError(t, err) + + require.Equal(t, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), ExchangeRate: rate}, + {Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD), ExchangeRate: rate}, + }, res.ExchangeRates) +} + +func TestQueryExchangeRateTwap(t *testing.T) { + input := CreateTestFixture(t) + querier := NewQuerier(input.OracleKeeper) + + rate := math.LegacyNewDec(1700) + input.OracleKeeper.SetPrice(input.Ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), rate) + testutilevents.RequireContainsTypedEvent( + t, + input.Ctx, + &types.EventPriceUpdate{ + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD).String(), + Price: rate, + TimestampMs: input.Ctx.BlockTime().UnixMilli(), + }, + ) + + ctx := sdk.WrapSDKContext(input.Ctx. + WithBlockTime(input.Ctx.BlockTime().Add(time.Second)). + WithBlockHeight(input.Ctx.BlockHeight() + 1), + ) + + _, err := querier.ExchangeRateTwap(ctx, &types.QueryExchangeRateRequest{Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD)}) + require.Error(t, err) + + res, err := querier.ExchangeRateTwap(ctx, &types.QueryExchangeRateRequest{Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD)}) + require.NoError(t, err) + require.Equal(t, math.LegacyMustNewDecFromStr("1700"), res.ExchangeRate) +} + +func TestCalcTwap(t *testing.T) { + tests := []struct { + name string + pair asset.Pair + priceSnapshots []types.PriceSnapshot + currentBlockTime time.Time + currentBlockHeight int64 + lookbackInterval time.Duration + assetAmount math.LegacyDec + expectedPrice math.LegacyDec + expectedErr error + }{ + // expected price: (9.5 * (35 - 30) + 8.5 * (30 - 20) + 9.0 * (20 - 5)) / 30 = 8.916666 + { + name: "spot price twap calc, t=(5,35]", + pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + priceSnapshots: []types.PriceSnapshot{ + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + Price: math.LegacyMustNewDecFromStr("90000.0"), + TimestampMs: time.UnixMilli(1).UnixMilli(), + }, + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + Price: math.LegacyMustNewDecFromStr("9.0"), + TimestampMs: time.UnixMilli(10).UnixMilli(), + }, + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + Price: math.LegacyMustNewDecFromStr("8.5"), + TimestampMs: time.UnixMilli(20).UnixMilli(), + }, + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + Price: math.LegacyMustNewDecFromStr("9.5"), + TimestampMs: time.UnixMilli(30).UnixMilli(), + }, + }, + currentBlockTime: time.UnixMilli(35), + currentBlockHeight: 3, + lookbackInterval: 30 * time.Millisecond, + expectedPrice: math.LegacyMustNewDecFromStr("8.900000000000000000"), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + input := CreateTestFixture(t) + querier := NewQuerier(input.OracleKeeper) + ctx := input.Ctx + + newParams := types.Params{ + VotePeriod: types.DefaultVotePeriod, + VoteThreshold: types.DefaultVoteThreshold, + MinVoters: types.DefaultMinVoters, + RewardBand: types.DefaultRewardBand, + Whitelist: types.DefaultWhitelist, + SlashFraction: types.DefaultSlashFraction, + SlashWindow: types.DefaultSlashWindow, + MinValidPerWindow: types.DefaultMinValidPerWindow, + TwapLookbackWindow: tc.lookbackInterval, + ValidatorFeeRatio: types.DefaultValidatorFeeRatio, + } + + input.OracleKeeper.Params.Set(ctx, newParams) + ctx = ctx.WithBlockTime(time.UnixMilli(0)) + for _, reserve := range tc.priceSnapshots { + ctx = ctx.WithBlockTime(time.UnixMilli(reserve.TimestampMs)) + input.OracleKeeper.SetPrice(ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), reserve.Price) + } + + ctx = ctx.WithBlockTime(tc.currentBlockTime).WithBlockHeight(tc.currentBlockHeight) + + price, err := querier.ExchangeRateTwap(sdk.WrapSDKContext(ctx), &types.QueryExchangeRateRequest{Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD)}) + require.NoError(t, err) + + require.EqualValuesf(t, tc.expectedPrice, price.ExchangeRate, + "expected %s, got %s", tc.expectedPrice.String(), price.ExchangeRate.String()) + }) + } +} + +func TestQueryActives(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + queryClient := NewQuerier(input.OracleKeeper) + + rate := math.LegacyNewDec(1700) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.NIBI, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + input.OracleKeeper.ExchangeRates.Insert(input.Ctx, asset.Registry.Pair(denoms.ETH, denoms.NUSD), types.DatedPrice{ExchangeRate: rate, CreatedBlock: uint64(input.Ctx.BlockHeight())}) + + res, err := queryClient.Actives(ctx, &types.QueryActivesRequest{}) + require.NoError(t, err) + + targetPairs := []asset.Pair{ + asset.Registry.Pair(denoms.BTC, denoms.NUSD), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + asset.Registry.Pair(denoms.NIBI, denoms.NUSD), + } + + require.Equal(t, targetPairs, res.Actives) +} + +func TestQueryFeederDelegation(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + input.OracleKeeper.FeederDelegations.Insert(input.Ctx, ValAddrs[0], Addrs[1]) + + // empty request + _, err := querier.FeederDelegation(ctx, nil) + require.Error(t, err) + + res, err := querier.FeederDelegation(ctx, &types.QueryFeederDelegationRequest{ + ValidatorAddr: ValAddrs[0].String(), + }) + require.NoError(t, err) + + require.Equal(t, Addrs[1].String(), res.FeederAddr) +} + +func TestQueryAggregatePrevote(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + prevote1 := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{}, ValAddrs[0], 0) + input.OracleKeeper.Prevotes.Insert(input.Ctx, ValAddrs[0], prevote1) + prevote2 := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{}, ValAddrs[1], 0) + input.OracleKeeper.Prevotes.Insert(input.Ctx, ValAddrs[1], prevote2) + + // validator 0 address params + res, err := querier.AggregatePrevote(ctx, &types.QueryAggregatePrevoteRequest{ + ValidatorAddr: ValAddrs[0].String(), + }) + require.NoError(t, err) + require.Equal(t, prevote1, res.AggregatePrevote) + + // empty request + _, err = querier.AggregatePrevote(ctx, nil) + require.Error(t, err) + + // validator 1 address params + res, err = querier.AggregatePrevote(ctx, &types.QueryAggregatePrevoteRequest{ + ValidatorAddr: ValAddrs[1].String(), + }) + require.NoError(t, err) + require.Equal(t, prevote2, res.AggregatePrevote) +} + +func TestQueryAggregatePrevotes(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + prevote1 := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{}, ValAddrs[0], 0) + input.OracleKeeper.Prevotes.Insert(input.Ctx, ValAddrs[0], prevote1) + prevote2 := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{}, ValAddrs[1], 0) + input.OracleKeeper.Prevotes.Insert(input.Ctx, ValAddrs[1], prevote2) + prevote3 := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash{}, ValAddrs[2], 0) + input.OracleKeeper.Prevotes.Insert(input.Ctx, ValAddrs[2], prevote3) + + expectedPrevotes := []types.AggregateExchangeRatePrevote{prevote1, prevote2, prevote3} + sort.SliceStable(expectedPrevotes, func(i, j int) bool { + return expectedPrevotes[i].Voter <= expectedPrevotes[j].Voter + }) + + res, err := querier.AggregatePrevotes(ctx, &types.QueryAggregatePrevotesRequest{}) + require.NoError(t, err) + require.Equal(t, expectedPrevotes, res.AggregatePrevotes) +} + +func TestQueryAggregateVote(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + vote1 := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "", ExchangeRate: math.LegacyOneDec()}}, ValAddrs[0]) + input.OracleKeeper.Votes.Insert(input.Ctx, ValAddrs[0], vote1) + vote2 := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "", ExchangeRate: math.LegacyOneDec()}}, ValAddrs[1]) + input.OracleKeeper.Votes.Insert(input.Ctx, ValAddrs[1], vote2) + + // empty request + _, err := querier.AggregateVote(ctx, nil) + require.Error(t, err) + + // validator 0 address params + res, err := querier.AggregateVote(ctx, &types.QueryAggregateVoteRequest{ + ValidatorAddr: ValAddrs[0].String(), + }) + require.NoError(t, err) + require.Equal(t, vote1, res.AggregateVote) + + // validator 1 address params + res, err = querier.AggregateVote(ctx, &types.QueryAggregateVoteRequest{ + ValidatorAddr: ValAddrs[1].String(), + }) + require.NoError(t, err) + require.Equal(t, vote2, res.AggregateVote) +} + +func TestQueryAggregateVotes(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + vote1 := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "", ExchangeRate: math.LegacyOneDec()}}, ValAddrs[0]) + input.OracleKeeper.Votes.Insert(input.Ctx, ValAddrs[0], vote1) + vote2 := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "", ExchangeRate: math.LegacyOneDec()}}, ValAddrs[1]) + input.OracleKeeper.Votes.Insert(input.Ctx, ValAddrs[1], vote2) + vote3 := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{{Pair: "", ExchangeRate: math.LegacyOneDec()}}, ValAddrs[2]) + input.OracleKeeper.Votes.Insert(input.Ctx, ValAddrs[2], vote3) + + expectedVotes := []types.AggregateExchangeRateVote{vote1, vote2, vote3} + sort.SliceStable(expectedVotes, func(i, j int) bool { + return expectedVotes[i].Voter <= expectedVotes[j].Voter + }) + + res, err := querier.AggregateVotes(ctx, &types.QueryAggregateVotesRequest{}) + require.NoError(t, err) + require.Equal(t, expectedVotes, res.AggregateVotes) +} + +func TestQueryVoteTargets(t *testing.T) { + input := CreateTestFixture(t) + ctx := sdk.WrapSDKContext(input.Ctx) + querier := NewQuerier(input.OracleKeeper) + + // clear pairs + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + + voteTargets := []asset.Pair{"denom1:denom2", "denom3:denom4", "denom5:denom6"} + for _, target := range voteTargets { + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, target) + } + + res, err := querier.VoteTargets(ctx, &types.QueryVoteTargetsRequest{}) + require.NoError(t, err) + require.Equal(t, voteTargets, res.VoteTargets) +} diff --git a/x/oracle/keeper/reward.go b/x/oracle/keeper/reward.go new file mode 100644 index 00000000..a268c2b0 --- /dev/null +++ b/x/oracle/keeper/reward.go @@ -0,0 +1,89 @@ +package keeper + +import ( + "github.com/NibiruChain/collections" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/oracle/types" +) + +func (k Keeper) AllocateRewards(ctx sdk.Context, funderModule string, totalCoins sdk.Coins, votePeriods uint64) error { + votePeriodCoins := make(sdk.Coins, len(totalCoins)) + for i, coin := range totalCoins { + newCoin := sdk.NewCoin(coin.Denom, coin.Amount.QuoRaw(int64(votePeriods))) + votePeriodCoins[i] = newCoin + } + + id := k.RewardsID.Next(ctx) + k.Rewards.Insert(ctx, id, types.Rewards{ + Id: id, + VotePeriods: votePeriods, + Coins: votePeriodCoins, + }) + + return k.bankKeeper.SendCoinsFromModuleToModule(ctx, funderModule, types.ModuleName, totalCoins) +} + +// rewardWinners gives out a portion of spread fees collected in the +// oracle reward pool to the oracle voters that voted faithfully. +func (k Keeper) rewardWinners( + ctx sdk.Context, + validatorPerformances types.ValidatorPerformances, +) { + totalRewardWeight := validatorPerformances.TotalRewardWeight() + if totalRewardWeight == 0 { + return + } + + var totalRewards sdk.DecCoins + rewards := k.GatherRewardsForVotePeriod(ctx) + totalRewards = totalRewards.Add(sdk.NewDecCoinsFromCoins(rewards...)...) + + var distributedRewards sdk.Coins + for _, validatorPerformance := range validatorPerformances { + validator, err := k.StakingKeeper.Validator(ctx, validatorPerformance.ValAddress) + if err != nil { + continue + } + + rewardPortion, _ := totalRewards.MulDec(math.LegacyNewDec(validatorPerformance.RewardWeight).QuoInt64(totalRewardWeight)).TruncateDecimal() + k.distrKeeper.AllocateTokensToValidator(ctx, validator, sdk.NewDecCoinsFromCoins(rewardPortion...)) + distributedRewards = distributedRewards.Add(rewardPortion...) + } + + // Move distributed reward to distribution module + err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.distrModuleName, distributedRewards) + if err != nil { + k.Logger(ctx).Error("Failed to send coins to distribution module", "err", err) + } +} + +// GatherRewardsForVotePeriod retrieves the pair rewards for the provided pair and current vote period. +func (k Keeper) GatherRewardsForVotePeriod(ctx sdk.Context) sdk.Coins { + coins := sdk.NewCoins() + // iterate over + for _, rewardId := range k.Rewards.Iterate(ctx, collections.Range[uint64]{}).Keys() { + pairReward, err := k.Rewards.Get(ctx, rewardId) + if err != nil { + k.Logger(ctx).Error("Failed to get reward", "err", err) + continue + } + coins = coins.Add(pairReward.Coins...) + + // Decrease the remaining vote periods of the PairReward. + pairReward.VotePeriods -= 1 + if pairReward.VotePeriods == 0 { + // If the distribution period count drops to 0: the reward instance is removed. + err := k.Rewards.Delete(ctx, rewardId) + if err != nil { + k.Logger(ctx).Error("Failed to delete pair reward", "err", err) + } + } else { + k.Rewards.Insert(ctx, rewardId, pairReward) + } + } + + return coins +} diff --git a/x/oracle/keeper/reward_test.go b/x/oracle/keeper/reward_test.go new file mode 100644 index 00000000..27659092 --- /dev/null +++ b/x/oracle/keeper/reward_test.go @@ -0,0 +1,70 @@ +package keeper + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/NibiruChain/collections" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestKeeperRewardsDistributionMultiVotePeriods(t *testing.T) { + // this simulates allocating rewards for the pair atom:usd + // over 5 voting periods. It simulates rewards are correctly + // distributed over 5 voting periods to 5 validators. + // then we simulate that after the 5 voting periods are + // finished no more rewards distribution happen. + const periods uint64 = 5 + const validators = 5 + + fixture, msgServer := Setup(t) + votePeriod := fixture.OracleKeeper.VotePeriod(fixture.Ctx) + + rewards := sdk.NewInt64Coin("reward", 1*common.TO_MICRO) + valPeriodicRewards := sdk.NewDecCoinsFromCoins(rewards). + QuoDec(math.LegacyNewDec(int64(periods))). + QuoDec(math.LegacyNewDec(int64(validators))) + AllocateRewards(t, fixture, sdk.NewCoins(rewards), periods) + + for i := uint64(1); i <= periods; i++ { + for valIndex := 0; valIndex < validators; valIndex++ { + // for doc's sake, this function is capable of making prevotes and votes because it + // passes the current context block height for pre vote + // then changes the height to current height + vote period for the vote + MakeAggregatePrevoteAndVote(t, fixture, msgServer, fixture.Ctx.BlockHeight(), types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), + ExchangeRate: testExchangeRate, + }, + }, valIndex) + } + + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + for valIndex := 0; valIndex < validators; valIndex++ { + distributionRewards := fixture.DistrKeeper.GetValidatorOutstandingRewards(fixture.Ctx, ValAddrs[0]) + truncatedGot, _ := distributionRewards.Rewards. + QuoDec(math.LegacyNewDec(int64(i))). // outstanding rewards will count for the previous vote period too, so we divide it by current period + TruncateDecimal() // NOTE: not applying this on truncatedExpected because of rounding the test fails + truncatedExpected, _ := valPeriodicRewards.TruncateDecimal() + + require.Equalf(t, truncatedExpected, truncatedGot, "period: %d, %s <-> %s", i, truncatedExpected.String(), truncatedGot.String()) + } + // assert rewards + + fixture.Ctx = fixture.Ctx.WithBlockHeight(fixture.Ctx.BlockHeight() + int64(votePeriod)) + } + + // assert there are no rewards + require.True(t, fixture.OracleKeeper.GatherRewardsForVotePeriod(fixture.Ctx).IsZero()) + + // assert that there are no rewards instances + require.Empty(t, fixture.OracleKeeper.Rewards.Iterate(fixture.Ctx, collections.Range[uint64]{}).Keys()) +} diff --git a/x/oracle/keeper/slash.go b/x/oracle/keeper/slash.go new file mode 100644 index 00000000..696306f4 --- /dev/null +++ b/x/oracle/keeper/slash.go @@ -0,0 +1,59 @@ +package keeper + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" +) + +// SlashAndResetMissCounters do slash any operator who over criteria & clear all operators miss counter to zero +func (k Keeper) SlashAndResetMissCounters(ctx sdk.Context) { + height := ctx.BlockHeight() + distributionHeight := height - sdk.ValidatorUpdateDelay - 1 + + // slash_window / vote_period + votePeriodsPerWindow := uint64( + math.LegacyNewDec(int64(k.SlashWindow(ctx))). + QuoInt64(int64(k.VotePeriod(ctx))). + TruncateInt64(), + ) + minValidPerWindow := k.MinValidPerWindow(ctx) + slashFraction := k.SlashFraction(ctx) + powerReduction := k.StakingKeeper.PowerReduction(ctx) + + for _, mc := range k.MissCounters.Iterate(ctx, collections.Range[sdk.ValAddress]{}).KeyValues() { + operator := mc.Key + missCounter := mc.Value + // Calculate valid vote rate; (SlashWindow - MissCounter)/SlashWindow + validVoteRate := math.LegacyNewDecFromInt( + math.NewInt(int64(votePeriodsPerWindow - missCounter))). + QuoInt64(int64(votePeriodsPerWindow)) + + // Penalize the validator whose the valid vote rate is smaller than min threshold + if validVoteRate.LT(minValidPerWindow) { + validator, err := k.StakingKeeper.Validator(ctx, operator) + if err != nil { + k.Logger(ctx).Error("fail to get validator", "operator", operator) + } + if validator.IsBonded() && !validator.IsJailed() { + consAddr, err := validator.GetConsAddr() + if err != nil { + k.Logger(ctx).Error("fail to get consensus address", "validator", validator.GetOperator()) + continue + } + + k.slashingKeeper.Slash( + ctx, consAddr, slashFraction, validator.GetConsensusPower(powerReduction), distributionHeight, + ) + k.Logger(ctx).Info("oracle slash", "validator", string(consAddr), "fraction", slashFraction.String()) + k.slashingKeeper.Jail(ctx, consAddr) + } + } + + err := k.MissCounters.Delete(ctx, operator) + if err != nil { + k.Logger(ctx).Error("fail to delete miss counter", "operator", operator.String(), "error", err) + } + } +} diff --git a/x/oracle/keeper/slash_test.go b/x/oracle/keeper/slash_test.go new file mode 100644 index 00000000..ad38eb3e --- /dev/null +++ b/x/oracle/keeper/slash_test.go @@ -0,0 +1,269 @@ +package keeper + +import ( + "testing" + + "cosmossdk.io/math" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestSlashAndResetMissCounters(t *testing.T) { + // initial setup + input := CreateTestFixture(t) + addr, val := ValAddrs[0], ValPubKeys[0] + addr1, val1 := ValAddrs[1], ValPubKeys[1] + amt := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction) + sh := stakingkeeper.NewMsgServerImpl(&input.StakingKeeper) + ctx := input.Ctx + + // Validator created + _, err := sh.CreateValidator(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.NoError(t, err) + _, err = sh.CreateValidator(ctx, NewTestMsgCreateValidator(addr1, val1, amt)) + require.NoError(t, err) + staking.EndBlocker(ctx, &input.StakingKeeper) + + require.Equal( + t, input.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(input.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), + ) + require.Equal(t, amt, input.StakingKeeper.Validator(ctx, addr).GetBondedTokens()) + require.Equal( + t, input.BankKeeper.GetAllBalances(ctx, sdk.AccAddress(addr1)), + sdk.NewCoins(sdk.NewCoin(input.StakingKeeper.GetParams(ctx).BondDenom, InitTokens.Sub(amt))), + ) + require.Equal(t, amt, input.StakingKeeper.Validator(ctx, addr1).GetBondedTokens()) + + votePeriodsPerWindow := math.LegacyNewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64() + slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + minValidVotes := input.OracleKeeper.MinValidPerWindow(input.Ctx).MulInt64(votePeriodsPerWindow).Ceil().TruncateInt64() + // Case 1, no slash + input.OracleKeeper.MissCounters.Insert(input.Ctx, ValAddrs[0], uint64(votePeriodsPerWindow-minValidVotes)) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + staking.EndBlocker(input.Ctx, &input.StakingKeeper) + + validator, _ := input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + require.Equal(t, amt, validator.GetBondedTokens()) + + // Case 2, slash + input.OracleKeeper.MissCounters.Insert(input.Ctx, ValAddrs[0], uint64(votePeriodsPerWindow-minValidVotes+1)) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + validator, _ = input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + require.Equal(t, amt.Sub(slashFraction.MulInt(amt).TruncateInt()), validator.GetBondedTokens()) + require.True(t, validator.IsJailed()) + + // Case 3, slash unbonded validator + validator, _ = input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + validator.Status = stakingtypes.Unbonded + validator.Jailed = false + validator.Tokens = amt + input.StakingKeeper.SetValidator(input.Ctx, validator) + + input.OracleKeeper.MissCounters.Insert(input.Ctx, ValAddrs[0], uint64(votePeriodsPerWindow-minValidVotes+1)) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + validator, _ = input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + require.Equal(t, amt, validator.Tokens) + require.False(t, validator.IsJailed()) + + // Case 4, slash jailed validator + validator, _ = input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + validator.Status = stakingtypes.Bonded + validator.Jailed = true + validator.Tokens = amt + input.StakingKeeper.SetValidator(input.Ctx, validator) + + input.OracleKeeper.MissCounters.Insert(input.Ctx, ValAddrs[0], uint64(votePeriodsPerWindow-minValidVotes+1)) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + validator, _ = input.StakingKeeper.GetValidator(input.Ctx, ValAddrs[0]) + require.Equal(t, amt, validator.Tokens) +} + +func TestInvalidVotesSlashing(t *testing.T) { + input, h := Setup(t) + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)} + input.OracleKeeper.Params.Set(input.Ctx, params) + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, asset.Registry.Pair(denoms.ATOM, denoms.USD)) + + votePeriodsPerWindow := math.LegacyNewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64() + slashFraction := input.OracleKeeper.SlashFraction(input.Ctx) + minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + for i := uint64(0); i < uint64(math.LegacyOneDec().Sub(minValidPerWindow).MulInt64(votePeriodsPerWindow).TruncateInt64()); i++ { + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 0) + + // Account 2, govstable, miss vote + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Add(math.LegacyNewDec(100000000000000))}, + }, 1) + + // Account 3, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 2) + + // Account 4, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 3) + + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + // input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + // input.OracleKeeper.UpdateExchangeRates(input.Ctx) + + require.Equal(t, i+1, input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[1], 0)) + } + + validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) + require.Equal(t, testStakingAmt, validator.GetBondedTokens()) + + // one more miss vote will inccur ValAddrs[1] slashing + // Account 1, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 0) + + // Account 2, govstable, miss vote + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Add(math.LegacyNewDec(100000000000000))}, + }, 1) + + // Account 3, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 2) + + // Account 4, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 3) + + input.Ctx = input.Ctx.WithBlockHeight(votePeriodsPerWindow - 1) + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + // input.OracleKeeper.UpdateExchangeRates(input.Ctx) + + validator = input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) + require.Equal(t, math.LegacyOneDec().Sub(slashFraction).MulInt(testStakingAmt).TruncateInt(), validator.GetBondedTokens()) +} + +// TestWhitelistSlashing: Creates a scenario where one valoper (valIdx 0) does +// not vote throughout an entire vote window, while valopers 1 and 2 do. +func TestWhitelistSlashing(t *testing.T) { + input, msgServer := Setup(t) + + votePeriodsPerSlashWindow := math.LegacyNewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64() + minValidVotePeriodsPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + pair := asset.Registry.Pair(denoms.ATOM, denoms.USD) + priceVoteFromVal := func(valIdx int, block int64, erate math.LegacyDec) { + MakeAggregatePrevoteAndVote(t, input, msgServer, block, + types.ExchangeRateTuples{{Pair: pair, ExchangeRate: erate}}, + valIdx) + } + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, pair) + perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx) + require.EqualValues(t, 0, perfs.TotalRewardWeight()) + + allowedMissPct := math.LegacyOneDec().Sub(minValidVotePeriodsPerWindow) + allowedMissVotePeriods := allowedMissPct.MulInt64(votePeriodsPerSlashWindow). + TruncateInt64() + t.Logf("For %v blocks, valoper0 does not vote, while 1 and 2 do.", allowedMissVotePeriods) + for idxMissPeriod := uint64(0); idxMissPeriod < uint64(allowedMissVotePeriods); idxMissPeriod++ { + block := input.Ctx.BlockHeight() + 1 + input.Ctx = input.Ctx.WithBlockHeight(block) + + valIdx := 0 // Valoper doesn't vote (abstain) + priceVoteFromVal(valIdx+1, block, testExchangeRate) + priceVoteFromVal(valIdx+2, block, testExchangeRate) + + perfs := input.OracleKeeper.UpdateExchangeRates(input.Ctx) + missCount := input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0) + require.EqualValues(t, 0, missCount, perfs.String()) + } + + t.Log("valoper0 should not be slashed") + validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[0]) + require.Equal(t, testStakingAmt, validator.GetBondedTokens()) +} + +func TestNotPassedBallotSlashing(t *testing.T) { + input, h := Setup(t) + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)} + input.OracleKeeper.Params.Set(input.Ctx, params) + + // clear tobin tax to reset vote targets + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, asset.Registry.Pair(denoms.ATOM, denoms.USD)) + + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, govstable + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}}, 0) + + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + // input.OracleKeeper.UpdateExchangeRates(input.Ctx) + require.Equal(t, uint64(0), input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[0], 0)) + require.Equal(t, uint64(0), input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[1], 0)) + require.Equal(t, uint64(0), input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[2], 0)) +} + +func TestAbstainSlashing(t *testing.T) { + input, h := Setup(t) + + // reset whitelisted pairs + params, err := input.OracleKeeper.Params.Get(input.Ctx) + require.NoError(t, err) + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)} + input.OracleKeeper.Params.Set(input.Ctx, params) + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, asset.Registry.Pair(denoms.ATOM, denoms.USD)) + + votePeriodsPerWindow := math.LegacyNewDec(int64(input.OracleKeeper.SlashWindow(input.Ctx))).QuoInt64(int64(input.OracleKeeper.VotePeriod(input.Ctx))).TruncateInt64() + minValidPerWindow := input.OracleKeeper.MinValidPerWindow(input.Ctx) + + for i := uint64(0); i <= uint64(math.LegacyOneDec().Sub(minValidPerWindow).MulInt64(votePeriodsPerWindow).TruncateInt64()); i++ { + input.Ctx = input.Ctx.WithBlockHeight(input.Ctx.BlockHeight() + 1) + + // Account 1, ATOM/USD + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}}, 0) + + // Account 2, ATOM/USD, abstain vote + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: math.LegacyOneDec().Neg()}}, 1) + + // Account 3, ATOM/USD + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}}, 2) + + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + input.OracleKeeper.SlashAndResetMissCounters(input.Ctx) + // input.OracleKeeper.UpdateExchangeRates(input.Ctx) + require.Equal(t, uint64(0), input.OracleKeeper.MissCounters.GetOr(input.Ctx, ValAddrs[1], 0)) + } + + validator := input.StakingKeeper.Validator(input.Ctx, ValAddrs[1]) + require.Equal(t, testStakingAmt, validator.GetBondedTokens()) +} diff --git a/x/oracle/keeper/test_utils.go b/x/oracle/keeper/test_utils.go new file mode 100644 index 00000000..83c02dca --- /dev/null +++ b/x/oracle/keeper/test_utils.go @@ -0,0 +1,384 @@ +// nolint +package keeper + +import ( + "testing" + "time" + + "cosmossdk.io/log" + "cosmossdk.io/math" + "cosmossdk.io/store" + "cosmossdk.io/store/metrics" + storetypes "cosmossdk.io/store/types" + "github.com/archway-network/archway/app/appconst" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" + + // "github.com/archway-network/archway/x/sudo" + // sudokeeper "github.com/archway-network/archway/x/sudo/keeper" + // sudotypes "github.com/archway-network/archway/x/sudo/types" + "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/crypto/secp256k1" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/auth" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" +) + +const faucetAccountName = "faucet" + +// ModuleBasics nolint +var ModuleBasics = module.NewBasicManager( + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + distr.AppModuleBasic{}, + staking.AppModuleBasic{}, + params.AppModuleBasic{}, + // sudo.AppModuleBasic{}, +) + +// MakeTestCodec nolint +func MakeTestCodec(t *testing.T) codec.Codec { + return MakeEncodingConfig(t).Codec +} + +// MakeEncodingConfig nolint +func MakeEncodingConfig(_ *testing.T) testutil.TestEncodingConfig { + amino := codec.NewLegacyAmino() + interfaceRegistry := codectypes.NewInterfaceRegistry() + codec := codec.NewProtoCodec(interfaceRegistry) + txCfg := tx.NewTxConfig(codec, tx.DefaultSignModes) + + std.RegisterInterfaces(interfaceRegistry) + std.RegisterLegacyAminoCodec(amino) + + ModuleBasics.RegisterLegacyAminoCodec(amino) + ModuleBasics.RegisterInterfaces(interfaceRegistry) + types.RegisterLegacyAminoCodec(amino) + types.RegisterInterfaces(interfaceRegistry) + return testutil.TestEncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: codec, + TxConfig: txCfg, + Amino: amino, + } +} + +// Test addresses +var ( + ValPubKeys = sims.CreateTestPubKeys(5) + + pubKeys = []crypto.PubKey{ + secp256k1.GenPrivKey().PubKey(), + secp256k1.GenPrivKey().PubKey(), + secp256k1.GenPrivKey().PubKey(), + secp256k1.GenPrivKey().PubKey(), + secp256k1.GenPrivKey().PubKey(), + } + + Addrs = []sdk.AccAddress{ + sdk.AccAddress(pubKeys[0].Address()), + sdk.AccAddress(pubKeys[1].Address()), + sdk.AccAddress(pubKeys[2].Address()), + sdk.AccAddress(pubKeys[3].Address()), + sdk.AccAddress(pubKeys[4].Address()), + } + + ValAddrs = []sdk.ValAddress{ + sdk.ValAddress(pubKeys[0].Address()), + sdk.ValAddress(pubKeys[1].Address()), + sdk.ValAddress(pubKeys[2].Address()), + sdk.ValAddress(pubKeys[3].Address()), + sdk.ValAddress(pubKeys[4].Address()), + } + + InitTokens = sdk.TokensFromConsensusPower(200, sdk.DefaultPowerReduction) + InitCoins = sdk.NewCoins(sdk.NewCoin(denoms.NIBI, InitTokens)) + + OracleDecPrecision = 8 +) + +// TestFixture nolint +type TestFixture struct { + Ctx sdk.Context + Cdc *codec.LegacyAmino + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + OracleKeeper Keeper + StakingKeeper stakingkeeper.Keeper + DistrKeeper distrkeeper.Keeper + // SudoKeeper types.SudoKeeper +} + +// CreateTestFixture nolint +// Creates a base app, with 5 accounts, +func CreateTestFixture(t *testing.T) TestFixture { + keyAcc := storetypes.NewKVStoreKey(authtypes.StoreKey) + keyBank := storetypes.NewKVStoreKey(banktypes.StoreKey) + keyParams := storetypes.NewKVStoreKey(paramstypes.StoreKey) + tKeyParams := storetypes.NewTransientStoreKey(paramstypes.TStoreKey) + keyOracle := storetypes.NewKVStoreKey(types.StoreKey) + keyStaking := storetypes.NewKVStoreKey(stakingtypes.StoreKey) + keySlashing := storetypes.NewKVStoreKey(slashingtypes.StoreKey) + keyDistr := storetypes.NewKVStoreKey(distrtypes.StoreKey) + // keySudo := storetypes.NewKVStoreKey(sudotypes.StoreKey) + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + ctx := sdk.NewContext(ms, cmtproto.Header{Time: time.Now().UTC(), Height: 1}, false, log.NewNopLogger()) + encodingConfig := MakeEncodingConfig(t) + appCodec, legacyAmino := encodingConfig.Codec, encodingConfig.Amino + + ms.MountStoreWithDB(keyAcc, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyBank, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyParams, storetypes.StoreTypeTransient, db) + ms.MountStoreWithDB(keyParams, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyOracle, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyStaking, storetypes.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyDistr, storetypes.StoreTypeIAVL, db) + + require.NoError(t, ms.LoadLatestVersion()) + + blackListAddrs := map[string]bool{ + authtypes.FeeCollectorName: true, + stakingtypes.NotBondedPoolName: true, + stakingtypes.BondedPoolName: true, + distrtypes.ModuleName: true, + faucetAccountName: true, + } + + maccPerms := map[string][]string{ + faucetAccountName: {authtypes.Minter}, + authtypes.FeeCollectorName: nil, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + distrtypes.ModuleName: nil, + types.ModuleName: nil, + } + + accountKeeper := authkeeper.NewAccountKeeper( + appCodec, + runtime.NewKVStoreService(keyAcc), + authtypes.ProtoBaseAccount, + maccPerms, + authcodec.NewBech32Codec(appconst.Bech32PrefixAccAddr), + appconst.Bech32PrefixAccAddr, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + bankKeeper := bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keyBank), + accountKeeper, + blackListAddrs, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ctx.Logger().With("module", "x/bank"), + ) + + totalSupply := sdk.NewCoins(sdk.NewCoin(denoms.NIBI, InitTokens.MulRaw(int64(len(Addrs)*10)))) + bankKeeper.MintCoins(ctx, faucetAccountName, totalSupply) + + stakingKeeper := stakingkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keyStaking), + accountKeeper, + bankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + authcodec.NewBech32Codec(appconst.Bech32PrefixValAddr), + authcodec.NewBech32Codec(appconst.Bech32PrefixConsAddr), + ) + stakingParams := stakingtypes.DefaultParams() + stakingParams.BondDenom = denoms.NIBI + stakingKeeper.SetParams(ctx, stakingParams) + + slashingKeeper := slashingkeeper.NewKeeper( + appCodec, + legacyAmino, + runtime.NewKVStoreService(keySlashing), + stakingKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + distrKeeper := distrkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keyDistr), + accountKeeper, + bankKeeper, + stakingKeeper, + authtypes.FeeCollectorName, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // TODO + // distrKeeper.SetFeePool(ctx, distrtypes.InitialFeePool()) + distrParams := distrtypes.DefaultParams() + distrParams.CommunityTax = math.LegacyNewDecWithPrec(2, 2) + // distrParams.BaseProposerReward = math.LegacyNewDecWithPrec(1, 2) + // distrParams.BonusProposerReward = math.LegacyNewDecWithPrec(4, 2) + // distrKeeper.SetParams(ctx, distrParams) + stakingKeeper.SetHooks(stakingtypes.NewMultiStakingHooks(distrKeeper.Hooks())) + + feeCollectorAcc := authtypes.NewEmptyModuleAccount(authtypes.FeeCollectorName) + notBondedPool := authtypes.NewEmptyModuleAccount(stakingtypes.NotBondedPoolName, authtypes.Burner, authtypes.Staking) + bondPool := authtypes.NewEmptyModuleAccount(stakingtypes.BondedPoolName, authtypes.Burner, authtypes.Staking) + distrAcc := authtypes.NewEmptyModuleAccount(distrtypes.ModuleName) + oracleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName, authtypes.Minter) + + bankKeeper.SendCoinsFromModuleToModule(ctx, faucetAccountName, stakingtypes.NotBondedPoolName, sdk.NewCoins(sdk.NewCoin(denoms.NIBI, InitTokens.MulRaw(int64(len(Addrs)))))) + + // sudoKeeper := sudokeeper.NewKeeper(appCodec, keySudo) + // sudoAcc := authtypes.NewEmptyModuleAccount(sudotypes.ModuleName) + + accountKeeper.SetModuleAccount(ctx, feeCollectorAcc) + accountKeeper.SetModuleAccount(ctx, bondPool) + accountKeeper.SetModuleAccount(ctx, notBondedPool) + accountKeeper.SetModuleAccount(ctx, distrAcc) + accountKeeper.SetModuleAccount(ctx, oracleAcc) + // accountKeeper.SetModuleAccount(ctx, sudoAcc) + + for _, addr := range Addrs { + accountKeeper.SetAccount(ctx, authtypes.NewBaseAccountWithAddress(addr)) + err := bankKeeper.SendCoinsFromModuleToAccount(ctx, faucetAccountName, addr, InitCoins) + require.NoError(t, err) + } + + keeper := NewKeeper( + appCodec, + keyOracle, + accountKeeper, + bankKeeper, + distrKeeper, + stakingKeeper, + slashingKeeper, + // sudoKeeper, + distrtypes.ModuleName, + ) + + defaults := types.DefaultParams() + + for _, pair := range defaults.Whitelist { + keeper.WhitelistedPairs.Insert(ctx, pair) + } + + keeper.Params.Set(ctx, defaults) + + return TestFixture{ + ctx, legacyAmino, accountKeeper, bankKeeper, + keeper, + *stakingKeeper, + distrKeeper, + // sudoKeeper, + } +} + +// NewTestMsgCreateValidator test msg creator +func NewTestMsgCreateValidator( + address sdk.ValAddress, pubKey cryptotypes.PubKey, amt math.Int, +) *stakingtypes.MsgCreateValidator { + commission := stakingtypes.NewCommissionRates(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()) + msg, _ := stakingtypes.NewMsgCreateValidator( + address.String(), pubKey, sdk.NewCoin(denoms.NIBI, amt), + stakingtypes.Description{}, commission, math.OneInt(), + ) + + return msg +} + +// FundAccount is a utility function that funds an account by minting and +// sending the coins to the address. This should be used for testing purposes +// only! +func FundAccount(input TestFixture, addr sdk.AccAddress, amounts sdk.Coins) error { + if err := input.BankKeeper.MintCoins(input.Ctx, faucetAccountName, amounts); err != nil { + return err + } + + return input.BankKeeper.SendCoinsFromModuleToAccount(input.Ctx, faucetAccountName, addr, amounts) +} + +func AllocateRewards(t *testing.T, input TestFixture, rewards sdk.Coins, votePeriods uint64) { + require.NoError(t, input.BankKeeper.MintCoins(input.Ctx, faucetAccountName, rewards)) + require.NoError(t, input.OracleKeeper.AllocateRewards(input.Ctx, faucetAccountName, rewards, votePeriods)) +} + +var ( + testStakingAmt = sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction) + testExchangeRate = math.LegacyNewDec(1700) +) + +func Setup(t *testing.T) (TestFixture, types.MsgServer) { + fixture := CreateTestFixture(t) + + params, _ := fixture.OracleKeeper.Params.Get(fixture.Ctx) + + params.VotePeriod = 1 + params.SlashWindow = 100 + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + + params, _ = fixture.OracleKeeper.Params.Get(fixture.Ctx) + + h := NewMsgServerImpl(fixture.OracleKeeper) + sh := stakingkeeper.NewMsgServerImpl(&fixture.StakingKeeper) + + // Validator created + _, err := sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[0], ValPubKeys[0], testStakingAmt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[1], ValPubKeys[1], testStakingAmt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[2], ValPubKeys[2], testStakingAmt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[3], ValPubKeys[3], testStakingAmt)) + require.NoError(t, err) + _, err = sh.CreateValidator(fixture.Ctx, NewTestMsgCreateValidator(ValAddrs[4], ValPubKeys[4], testStakingAmt)) + require.NoError(t, err) + // staking.EndBlocker(fixture.Ctx, &fixture.StakingKeeper) // TODO + + return fixture, h +} + +func MakeAggregatePrevoteAndVote( + t *testing.T, + input TestFixture, + msgServer types.MsgServer, + height int64, + rates types.ExchangeRateTuples, + valIdx int, +) { + salt := "1" + ratesStr, err := rates.ToString() + require.NoError(t, err) + hash := types.GetAggregateVoteHash(salt, ratesStr, ValAddrs[valIdx]) + + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[valIdx], ValAddrs[valIdx]) + _, err = msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(height)), prevoteMsg) + require.NoError(t, err) + + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, ratesStr, Addrs[valIdx], ValAddrs[valIdx]) + _, err = msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(input.Ctx.WithBlockHeight(height+1)), voteMsg) + require.NoError(t, err) +} diff --git a/x/oracle/keeper/update_exchange_rates.go b/x/oracle/keeper/update_exchange_rates.go new file mode 100644 index 00000000..b5880fce --- /dev/null +++ b/x/oracle/keeper/update_exchange_rates.go @@ -0,0 +1,166 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/omap" + "github.com/archway-network/archway/x/common/set" + "github.com/archway-network/archway/x/oracle/types" +) + +// UpdateExchangeRates updates the ExchangeRates, this is supposed to be executed on EndBlock. +func (k Keeper) UpdateExchangeRates(ctx sdk.Context) types.ValidatorPerformances { + k.Logger(ctx).Info("processing validator price votes") + validatorPerformances := k.newValidatorPerformances(ctx) + whitelistedPairs := set.New[asset.Pair](k.GetWhitelistedPairs(ctx)...) + + pairVotes := k.getPairVotes(ctx, validatorPerformances, whitelistedPairs) + + k.clearExchangeRates(ctx, pairVotes) + k.tallyVotesAndUpdatePrices(ctx, pairVotes, validatorPerformances) + + k.incrementMissCounters(ctx, whitelistedPairs, validatorPerformances) + k.incrementAbstainsByOmission(ctx, len(whitelistedPairs), validatorPerformances) + + k.rewardWinners(ctx, validatorPerformances) + + params, _ := k.Params.Get(ctx) + k.clearVotesAndPrevotes(ctx, params.VotePeriod) + k.refreshWhitelist(ctx, params.Whitelist, whitelistedPairs) + + for _, validatorPerformance := range validatorPerformances { + _ = ctx.EventManager().EmitTypedEvent(&types.EventValidatorPerformance{ + Validator: validatorPerformance.ValAddress.String(), + VotingPower: validatorPerformance.Power, + RewardWeight: validatorPerformance.RewardWeight, + WinCount: validatorPerformance.WinCount, + AbstainCount: validatorPerformance.AbstainCount, + MissCount: validatorPerformance.MissCount, + }) + } + + return validatorPerformances +} + +// incrementMissCounters it parses all validators performance and increases the +// missed vote of those that did not vote. +func (k Keeper) incrementMissCounters( + ctx sdk.Context, + whitelistedPairs set.Set[asset.Pair], + validatorPerformances types.ValidatorPerformances, +) { + for _, validatorPerformance := range validatorPerformances { + if int(validatorPerformance.MissCount) > 0 { + k.MissCounters.Insert( + ctx, validatorPerformance.ValAddress, + k.MissCounters.GetOr(ctx, validatorPerformance.ValAddress, 0)+uint64(validatorPerformance.MissCount), + ) + + k.Logger(ctx).Info("vote miss", "validator", validatorPerformance.ValAddress.String()) + } + } +} + +func (k Keeper) incrementAbstainsByOmission( + ctx sdk.Context, + numPairs int, + validatorPerformances types.ValidatorPerformances, +) { + for valAddr, performance := range validatorPerformances { + omitCount := int64(numPairs) - (performance.WinCount + performance.AbstainCount + performance.MissCount) + if omitCount > 0 { + performance.AbstainCount += omitCount + validatorPerformances[valAddr] = performance + } + } +} + +// tallyVotesAndUpdatePrices processes the votes and updates the ExchangeRates based on the results. +func (k Keeper) tallyVotesAndUpdatePrices( + ctx sdk.Context, + pairVotes map[asset.Pair]types.ExchangeRateVotes, + validatorPerformances types.ValidatorPerformances, +) { + rewardBand := k.RewardBand(ctx) + // Iterate through sorted keys for deterministic ordering. + orderedPairVotes := omap.OrderedMap_Pair[types.ExchangeRateVotes](pairVotes) + for pair := range orderedPairVotes.Range() { + exchangeRate := Tally(pairVotes[pair], rewardBand, validatorPerformances) + k.SetPrice(ctx, pair, exchangeRate) + } +} + +// getPairVotes returns a map of pairs and votes excluding abstained votes and votes that don't meet the threshold criteria +func (k Keeper) getPairVotes( + ctx sdk.Context, + validatorPerformances types.ValidatorPerformances, + whitelistedPairs set.Set[asset.Pair], +) (pairVotes map[asset.Pair]types.ExchangeRateVotes) { + pairVotes = k.groupVotesByPair(ctx, validatorPerformances) + + k.removeInvalidVotes(ctx, pairVotes, whitelistedPairs) + + return pairVotes +} + +// clearExchangeRates removes all exchange rates from the state +// We remove the price for pair with expired prices or valid votes +func (k Keeper) clearExchangeRates(ctx sdk.Context, pairVotes map[asset.Pair]types.ExchangeRateVotes) { + params, _ := k.Params.Get(ctx) + + for _, key := range k.ExchangeRates.Iterate(ctx, collections.Range[asset.Pair]{}).Keys() { + _, isValid := pairVotes[key] + previousExchangeRate, _ := k.ExchangeRates.Get(ctx, key) + isExpired := previousExchangeRate.CreatedBlock+params.ExpirationBlocks <= uint64(ctx.BlockHeight()) + + if isValid || isExpired { + err := k.ExchangeRates.Delete(ctx, key) + if err != nil { + k.Logger(ctx).Error("failed to delete exchange rate", "pair", key.String(), "error", err) + } + } + } +} + +// newValidatorPerformances creates a new map of validators and their performance, excluding validators that are +// not bonded. +func (k Keeper) newValidatorPerformances(ctx sdk.Context) types.ValidatorPerformances { + validatorPerformances := make(map[string]types.ValidatorPerformance) + + maxValidators, err := k.StakingKeeper.MaxValidators(ctx) + if err != nil { + panic(err) + } + powerReduction := k.StakingKeeper.PowerReduction(ctx) + + iterator, err := k.StakingKeeper.ValidatorsPowerStoreIterator(ctx) + if err != nil { + panic(err) + } + defer iterator.Close() + + for i := 0; iterator.Valid() && i < int(maxValidators); iterator.Next() { + validator, err := k.StakingKeeper.Validator(ctx, iterator.Value()) + + // exclude not bonded + if err != nil || !validator.IsBonded() { + continue + } + + valAddrStr := validator.GetOperator() + valAddr, err := sdk.ValAddressFromBech32(valAddrStr) + if err != nil { + panic(err) + } + validatorPerformances[valAddrStr] = types.NewValidatorPerformance( + validator.GetConsensusPower(powerReduction), + valAddr, + ) + i++ + } + + return validatorPerformances +} diff --git a/x/oracle/keeper/update_exchange_rates_test.go b/x/oracle/keeper/update_exchange_rates_test.go new file mode 100644 index 00000000..afa8a820 --- /dev/null +++ b/x/oracle/keeper/update_exchange_rates_test.go @@ -0,0 +1,491 @@ +package keeper + +import ( + "fmt" + "math" + "sort" + "testing" + + "github.com/cometbft/cometbft/libs/rand" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common" + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestOracleThreshold(t *testing.T) { + exchangeRates := types.ExchangeRateTuples{ + { + Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), + ExchangeRate: testExchangeRate, + }, + } + exchangeRateStr, err := exchangeRates.ToString() + require.NoError(t, err) + + fixture, msgServer := Setup(t) + params, _ := fixture.OracleKeeper.Params.Get(fixture.Ctx) + params.ExpirationBlocks = 0 + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + + // Case 1. + // Less than the threshold signs, exchange rate consensus fails + for i := 0; i < 1; i++ { + salt := fmt.Sprintf("%d", i) + hash := types.GetAggregateVoteHash(salt, exchangeRateStr, ValAddrs[i]) + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[i], ValAddrs[i]) + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[i], ValAddrs[i]) + + _, err1 := msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(0)), prevoteMsg) + _, err2 := msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(1)), voteMsg) + require.NoError(t, err1) + require.NoError(t, err2) + } + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + _, err = fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx.WithBlockHeight(1), exchangeRates[0].Pair) + assert.Error(t, err) + + // Case 2. + // More than the threshold signs, exchange rate consensus succeeds + for i := 0; i < 4; i++ { + salt := fmt.Sprintf("%d", i) + hash := types.GetAggregateVoteHash(salt, exchangeRateStr, ValAddrs[i]) + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[i], ValAddrs[i]) + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[i], ValAddrs[i]) + + _, err1 := msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(0)), prevoteMsg) + _, err2 := msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(1)), voteMsg) + require.NoError(t, err1) + require.NoError(t, err2) + } + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + rate, err := fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, exchangeRates[0].Pair) + require.NoError(t, err) + assert.Equal(t, testExchangeRate, rate.ExchangeRate) + + // Case 3. + // Increase voting power of absent validator, exchange rate consensus fails + val, _ := fixture.StakingKeeper.GetValidator(fixture.Ctx, ValAddrs[4]) + _, _ = fixture.StakingKeeper.Delegate(fixture.Ctx.WithBlockHeight(0), Addrs[4], testStakingAmt.MulRaw(8), stakingtypes.Unbonded, val, false) + + for i := 0; i < 4; i++ { + salt := fmt.Sprintf("%d", i) + hash := types.GetAggregateVoteHash(salt, exchangeRateStr, ValAddrs[i]) + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, Addrs[i], ValAddrs[i]) + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, Addrs[i], ValAddrs[i]) + + _, err1 := msgServer.AggregateExchangeRatePrevote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(0)), prevoteMsg) + _, err2 := msgServer.AggregateExchangeRateVote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(1)), voteMsg) + require.NoError(t, err1) + require.NoError(t, err2) + } + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + _, err = fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, exchangeRates[0].Pair) + assert.Error(t, err) +} + +func TestResetExchangeRates(t *testing.T) { + pair := asset.Registry.Pair(denoms.BTC, denoms.USD) + fixture, _ := Setup(t) + + emptyVotes := map[asset.Pair]types.ExchangeRateVotes{} + validVotes := map[asset.Pair]types.ExchangeRateVotes{pair: {}} + + // Set expiration blocks to 10 + params, _ := fixture.OracleKeeper.Params.Get(fixture.Ctx) + params.ExpirationBlocks = 10 + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + + // Post a price at block 1 + fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(1), pair, testExchangeRate) + + // reset exchange rates at block 2 + // Price should still be there because not expired yet + fixture.OracleKeeper.clearExchangeRates(fixture.Ctx.WithBlockHeight(2), emptyVotes) + _, err := fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, pair) + assert.NoError(t, err) + + // reset exchange rates at block 3 but pair is in votes + // Price should be removed there because there was a valid votes + fixture.OracleKeeper.clearExchangeRates(fixture.Ctx.WithBlockHeight(3), validVotes) + _, err = fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, pair) + assert.Error(t, err) + + // Post a price at block 69 + // reset exchange rates at block 79 + // Price should not be there anymore because expired + fixture.OracleKeeper.SetPrice(fixture.Ctx.WithBlockHeight(69), pair, testExchangeRate) + fixture.OracleKeeper.clearExchangeRates(fixture.Ctx.WithBlockHeight(79), emptyVotes) + + _, err = fixture.OracleKeeper.ExchangeRates.Get(fixture.Ctx, pair) + assert.Error(t, err) +} + +func TestOracleTally(t *testing.T) { + fixture, _ := Setup(t) + + votes := types.ExchangeRateVotes{} + rates, valAddrs, stakingKeeper := types.GenerateRandomTestCase() + fixture.OracleKeeper.StakingKeeper = stakingKeeper + h := NewMsgServerImpl(fixture.OracleKeeper) + + for i, rate := range rates { + decExchangeRate := sdkmath.LegacyNewDecWithPrec(int64(rate*math.Pow10(OracleDecPrecision)), int64(OracleDecPrecision)) + exchangeRateStr, err := types.ExchangeRateTuples{ + {ExchangeRate: decExchangeRate, Pair: asset.Registry.Pair(denoms.BTC, denoms.USD)}, + }.ToString() + require.NoError(t, err) + + salt := fmt.Sprintf("%d", i) + hash := types.GetAggregateVoteHash(salt, exchangeRateStr, valAddrs[i]) + prevoteMsg := types.NewMsgAggregateExchangeRatePrevote(hash, sdk.AccAddress(valAddrs[i]), valAddrs[i]) + voteMsg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRateStr, sdk.AccAddress(valAddrs[i]), valAddrs[i]) + + _, err1 := h.AggregateExchangeRatePrevote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(0)), prevoteMsg) + _, err2 := h.AggregateExchangeRateVote(sdk.WrapSDKContext(fixture.Ctx.WithBlockHeight(1)), voteMsg) + require.NoError(t, err1) + require.NoError(t, err2) + + power := testStakingAmt.QuoRaw(int64(6)).Int64() + if decExchangeRate.IsZero() { + power = int64(0) + } + + vote := types.NewExchangeRateVote( + decExchangeRate, asset.Registry.Pair(denoms.BTC, denoms.USD), valAddrs[i], power) + votes = append(votes, vote) + + // change power of every three validator + if i%3 == 0 { + stakingKeeper.Validators()[i].SetConsensusPower(int64(i + 1)) + } + } + + validatorPerformances := make(types.ValidatorPerformances) + for _, valAddr := range valAddrs { + validatorPerformances[valAddr.String()] = types.NewValidatorPerformance( + stakingKeeper.Validator(fixture.Ctx, valAddr).GetConsensusPower(sdk.DefaultPowerReduction), + valAddr, + ) + } + sort.Sort(votes) + weightedMedian := votes.WeightedMedianWithAssertion() + standardDeviation := votes.StandardDeviation(weightedMedian) + maxSpread := weightedMedian.Mul(fixture.OracleKeeper.RewardBand(fixture.Ctx).QuoInt64(2)) + + if standardDeviation.GT(maxSpread) { + maxSpread = standardDeviation + } + + expectedValidatorPerformances := make(types.ValidatorPerformances) + for _, valAddr := range valAddrs { + expectedValidatorPerformances[valAddr.String()] = types.NewValidatorPerformance( + stakingKeeper.Validator(fixture.Ctx, valAddr).GetConsensusPower(sdk.DefaultPowerReduction), + valAddr, + ) + } + + for _, vote := range votes { + key := vote.Voter.String() + validatorPerformance := expectedValidatorPerformances[key] + if vote.ExchangeRate.GTE(weightedMedian.Sub(maxSpread)) && + vote.ExchangeRate.LTE(weightedMedian.Add(maxSpread)) { + validatorPerformance.RewardWeight += vote.Power + validatorPerformance.WinCount++ + } else if !vote.ExchangeRate.IsPositive() { + validatorPerformance.AbstainCount++ + } else { + validatorPerformance.MissCount++ + } + expectedValidatorPerformances[key] = validatorPerformance + } + + tallyMedian := Tally( + votes, fixture.OracleKeeper.RewardBand(fixture.Ctx), validatorPerformances) + + assert.Equal(t, expectedValidatorPerformances, validatorPerformances) + assert.Equal(t, tallyMedian.MulInt64(100).TruncateInt(), weightedMedian.MulInt64(100).TruncateInt()) + assert.NotEqualValues(t, 0, validatorPerformances.TotalRewardWeight(), validatorPerformances.String()) +} + +func TestOracleRewardBand(t *testing.T) { + fixture, msgServer := Setup(t) + params, err := fixture.OracleKeeper.Params.Get(fixture.Ctx) + require.NoError(t, err) + + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)} + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + + // clear pairs to reset vote targets + for _, p := range fixture.OracleKeeper.WhitelistedPairs.Iterate(fixture.Ctx, collections.Range[asset.Pair]{}).Keys() { + fixture.OracleKeeper.WhitelistedPairs.Delete(fixture.Ctx, p) + } + fixture.OracleKeeper.WhitelistedPairs.Insert(fixture.Ctx, asset.Registry.Pair(denoms.ATOM, denoms.USD)) + + rewardSpread := testExchangeRate.Mul(fixture.OracleKeeper.RewardBand(fixture.Ctx).QuoInt64(2)) + + // Account 1, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Sub(rewardSpread)}, + }, 0) + + // Account 2, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 1) + + // Account 3, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 2) + + // Account 4, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Add(rewardSpread)}, + }, 3) + + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[3], 0)) + + // Account 1 will miss the vote due to raward band condition + // Account 1, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Sub(rewardSpread.Add(sdkmath.LegacyOneDec()))}, + }, 0) + + // Account 2, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 1) + + // Account 3, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}, + }, 2) + + // Account 4, atom:usd + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate.Add(rewardSpread)}, + }, 3) + + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + assert.Equal(t, uint64(1), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[3], 0)) +} + +/* TODO(Mercilex): not appliable right now: https://github.com/archway-network/archway/issues/805 +func TestOracleMultiRewardDistribution(t *testing.T) { + input, h := setup(t) + + // SDR and KRW have the same voting power, but KRW has been chosen as referencepair by alphabetical order. + // Account 1, SDR, KRW + makeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: common.Pairbtc:usd.String(), ExchangeRate: randomExchangeRate}, {Pair: common.Pairatom:usd.String(), ExchangeRate: randomExchangeRate}}, 0) + + // Account 2, SDR + makeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: common.Pairbtc:usd.String(), ExchangeRate: randomExchangeRate}}, 1) + + // Account 3, KRW + makeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{{Pair: common.Pairbtc:usd.String(), ExchangeRate: randomExchangeRate}}, 2) + + rewardAmt := math.NewInt(1e6) + err := input.BankKeeper.MintCoins(input.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(denoms.Gov, rewardAmt))) + require.NoError(t, err) + + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + + rewardDistributedWindow := input.OracleKeeper.RewardDistributionWindow(input.Ctx) + + expectedRewardAmt := math.LegacyNewDecFromInt(rewardAmt.QuoRaw(3).MulRaw(2)).QuoInt64(int64(rewardDistributedWindow)).TruncateInt() + expectedRewardAmt2 := math.ZeroInt() // even vote power is same KRW with SDR, KRW chosen referenceTerra because alphabetical order + expectedRewardAmt3 := math.LegacyNewDecFromInt(rewardAmt.QuoRaw(3)).QuoInt64(int64(rewardDistributedWindow)).TruncateInt() + + rewards := input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[0]) + assert.Equal(t, expectedRewardAmt, rewards.Rewards.AmountOf(denoms.Gov).TruncateInt()) + rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[1]) + assert.Equal(t, expectedRewardAmt2, rewards.Rewards.AmountOf(denoms.Gov).TruncateInt()) + rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[2]) + assert.Equal(t, expectedRewardAmt3, rewards.Rewards.AmountOf(denoms.Gov).TruncateInt()) +} +*/ + +func TestOracleExchangeRate(t *testing.T) { + // The following scenario tests four validators providing prices for eth:usd, atom:usd, and btc:usd. + // eth:usd and atom:usd pass, but btc:usd fails due to not enough validators voting. + input, h := Setup(t) + + atomUsdExchangeRate := sdkmath.LegacyNewDec(1000000) + ethUsdExchangeRate := sdkmath.LegacyNewDec(1000000) + btcusdExchangeRate := sdkmath.LegacyNewDec(1e6) + + // Account 1, eth:usd, atom:usd, btc:usd + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), ExchangeRate: ethUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: atomUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), ExchangeRate: btcusdExchangeRate}, + }, 0) + + // Account 2, eth:usd, atom:usd + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), ExchangeRate: ethUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: atomUsdExchangeRate}, + }, 1) + + // Account 3, eth:usd, atom:usd, btc:usd(abstain) + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), ExchangeRate: ethUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: atomUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), ExchangeRate: sdkmath.LegacyZeroDec()}, + }, 2) + + // Account 4, eth:usd, atom:usd, btc:usd + MakeAggregatePrevoteAndVote(t, input, h, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), ExchangeRate: ethUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: atomUsdExchangeRate}, + {Pair: asset.Registry.Pair(denoms.BTC, denoms.USD), ExchangeRate: sdkmath.LegacyZeroDec()}, + }, 3) + + ethUsdRewards := sdk.NewInt64Coin("ETHREWARD", 1*common.TO_MICRO) + atomUsdRewards := sdk.NewInt64Coin("ATOMREWARD", 1*common.TO_MICRO) + + AllocateRewards(t, input, sdk.NewCoins(ethUsdRewards), 1) + AllocateRewards(t, input, sdk.NewCoins(atomUsdRewards), 1) + + input.OracleKeeper.UpdateExchangeRates(input.Ctx) + + // total reward pool for the current vote period is 1* common.TO_MICRO for eth:usd and 1* common.TO_MICRO for atom:usd + // val 1,2,3,4 all won on 2 pairs + // so total votes are 2 * 2 + 2 + 2 = 8 + expectedRewardAmt := sdk.NewDecCoinsFromCoins(ethUsdRewards, atomUsdRewards). + QuoDec(sdkmath.LegacyNewDec(8)). // total votes + MulDec(sdkmath.LegacyNewDec(2)) // votes won by val1 and val2 + rewards := input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[0]) + assert.Equalf(t, expectedRewardAmt, rewards.Rewards, "%s <-> %s", expectedRewardAmt, rewards.Rewards) + rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[1]) + assert.Equalf(t, expectedRewardAmt, rewards.Rewards, "%s <-> %s", expectedRewardAmt, rewards.Rewards) + rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[2]) + assert.Equalf(t, expectedRewardAmt, rewards.Rewards, "%s <-> %s", expectedRewardAmt, rewards.Rewards) + rewards = input.DistrKeeper.GetValidatorOutstandingRewards(input.Ctx.WithBlockHeight(2), ValAddrs[3]) + assert.Equalf(t, expectedRewardAmt, rewards.Rewards, "%s <-> %s", expectedRewardAmt, rewards.Rewards) +} + +func TestOracleRandomPrices(t *testing.T) { + fixture, msgServer := Setup(t) + + for i := 0; i < 100; i++ { + for val := 0; val < 4; val++ { + MakeAggregatePrevoteAndVote(t, fixture, msgServer, 0, types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.ETH, denoms.USD), ExchangeRate: sdkmath.LegacyNewDec(int64(rand.Uint64() % 1e6))}, + {Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: sdkmath.LegacyNewDec(int64(rand.Uint64() % 1e6))}, + }, val) + } + + require.NotPanics(t, func() { + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + }) + } +} + +func TestWhitelistedPairs(t *testing.T) { + fixture, msgServer := Setup(t) + params, err := fixture.OracleKeeper.Params.Get(fixture.Ctx) + require.NoError(t, err) + + t.Log("whitelist ONLY atom:usd") + for _, p := range fixture.OracleKeeper.WhitelistedPairs.Iterate(fixture.Ctx, collections.Range[asset.Pair]{}).Keys() { + fixture.OracleKeeper.WhitelistedPairs.Delete(fixture.Ctx, p) + } + fixture.OracleKeeper.WhitelistedPairs.Insert(fixture.Ctx, asset.Registry.Pair(denoms.ATOM, denoms.USD)) + + t.Log("vote and prevote from all vals on atom:usd") + priceVoteFromVal := func(valIdx int, block int64) { + MakeAggregatePrevoteAndVote(t, fixture, msgServer, block, types.ExchangeRateTuples{{Pair: asset.Registry.Pair(denoms.ATOM, denoms.USD), ExchangeRate: testExchangeRate}}, valIdx) + } + block := int64(0) + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + priceVoteFromVal(3, block) + + t.Log("whitelist btc:usd for next vote period") + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD), asset.Registry.Pair(denoms.BTC, denoms.USD)} + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + t.Log("assert: no miss counts for all vals") + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[0], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[1], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[2], 0)) + assert.Equal(t, uint64(0), fixture.OracleKeeper.MissCounters.GetOr(fixture.Ctx, ValAddrs[3], 0)) + + t.Log("whitelisted pairs are {atom:usd, btc:usd}") + assert.Equal(t, + []asset.Pair{ + asset.Registry.Pair(denoms.ATOM, denoms.USD), + asset.Registry.Pair(denoms.BTC, denoms.USD), + }, + fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + + t.Log("vote from vals 0-3 on atom:usd (but not btc:usd)") + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + priceVoteFromVal(3, block) + + t.Log("delete btc:usd for next vote period") + params.Whitelist = []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)} + fixture.OracleKeeper.Params.Set(fixture.Ctx, params) + perfs := fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + t.Log("validators 0-3 all voted -> expect win") + for valIdx := 0; valIdx < 4; valIdx++ { + perf := perfs[ValAddrs[valIdx].String()] + assert.EqualValues(t, 1, perf.WinCount) + assert.EqualValues(t, 1, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + } + t.Log("validators 4 didn't vote -> expect abstain") + perf := perfs[ValAddrs[4].String()] + assert.EqualValues(t, 0, perf.WinCount) + assert.EqualValues(t, 2, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + + t.Log("btc:usd must be deleted") + assert.Equal(t, []asset.Pair{asset.Registry.Pair(denoms.ATOM, denoms.USD)}, + fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + require.False(t, fixture.OracleKeeper.WhitelistedPairs.Has( + fixture.Ctx, asset.Registry.Pair(denoms.BTC, denoms.USD))) + + t.Log("vote from vals 0-3 on atom:usd") + priceVoteFromVal(0, block) + priceVoteFromVal(1, block) + priceVoteFromVal(2, block) + priceVoteFromVal(3, block) + perfs = fixture.OracleKeeper.UpdateExchangeRates(fixture.Ctx) + + t.Log("Although validators 0-2 voted, it's for the same period -> expect abstains for everyone") + for valIdx := 0; valIdx < 4; valIdx++ { + perf := perfs[ValAddrs[valIdx].String()] + assert.EqualValues(t, 1, perf.WinCount) + assert.EqualValues(t, 0, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) + } + perf = perfs[ValAddrs[4].String()] + assert.EqualValues(t, 0, perf.WinCount) + assert.EqualValues(t, 1, perf.AbstainCount) + assert.EqualValues(t, 0, perf.MissCount) +} diff --git a/x/oracle/keeper/whitelist.go b/x/oracle/keeper/whitelist.go new file mode 100644 index 00000000..0b16e396 --- /dev/null +++ b/x/oracle/keeper/whitelist.go @@ -0,0 +1,47 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/set" +) + +// IsWhitelistedPair returns existence of a pair in the voting target list +func (k Keeper) IsWhitelistedPair(ctx sdk.Context, pair asset.Pair) bool { + return k.WhitelistedPairs.Has(ctx, pair) +} + +// GetWhitelistedPairs returns the whitelisted pairs list on current vote period +func (k Keeper) GetWhitelistedPairs(ctx sdk.Context) []asset.Pair { + return k.WhitelistedPairs.Iterate(ctx, collections.Range[asset.Pair]{}).Keys() +} + +// refreshWhitelist updates the whitelist by detecting possible changes between +// the current vote targets and the current updated whitelist. +func (k Keeper) refreshWhitelist(ctx sdk.Context, nextWhitelist []asset.Pair, currentWhitelist set.Set[asset.Pair]) { + updateRequired := false + + if len(currentWhitelist) != len(nextWhitelist) { + updateRequired = true + } else { + for _, pair := range nextWhitelist { + _, exists := currentWhitelist[pair] + if !exists { + updateRequired = true + break + } + } + } + + if updateRequired { + for _, p := range k.WhitelistedPairs.Iterate(ctx, collections.Range[asset.Pair]{}).Keys() { + k.WhitelistedPairs.Delete(ctx, p) + } + for _, pair := range nextWhitelist { + k.WhitelistedPairs.Insert(ctx, pair) + } + } +} diff --git a/x/oracle/keeper/whitelist_test.go b/x/oracle/keeper/whitelist_test.go new file mode 100644 index 00000000..dc9e2454 --- /dev/null +++ b/x/oracle/keeper/whitelist_test.go @@ -0,0 +1,129 @@ +package keeper + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/common/set" +) + +func TestKeeper_GetVoteTargets(t *testing.T) { + type TestCase struct { + name string + in []asset.Pair + panic bool + } + + panicCases := []TestCase{ + {name: "blank pair", in: []asset.Pair{""}, panic: true}, + {name: "blank pair and others", in: []asset.Pair{"", "x", "abc", "defafask"}, panic: true}, + {name: "denom len too short", in: []asset.Pair{"x:y", "xx:yy"}, panic: true}, + } + happyCases := []TestCase{ + {name: "happy", in: []asset.Pair{"foo:bar", "whoo:whoo"}}, + } + + for _, testCase := range append(panicCases, happyCases...) { + tc := testCase + t.Run(tc.name, func(t *testing.T) { + input := CreateTestFixture(t) + + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + + expectedTargets := tc.in + for _, target := range expectedTargets { + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, target) + } + + var panicAssertFn func(t assert.TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) bool + switch tc.panic { + case true: + panicAssertFn = assert.Panics + default: + panicAssertFn = assert.NotPanics + } + panicAssertFn(t, func() { + targets := input.OracleKeeper.GetWhitelistedPairs(input.Ctx) + assert.Equal(t, expectedTargets, targets) + }) + }) + } + + input := CreateTestFixture(t) + + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + + expectedTargets := []asset.Pair{"foo:bar", "whoo:whoo"} + for _, target := range expectedTargets { + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, target) + } + + targets := input.OracleKeeper.GetWhitelistedPairs(input.Ctx) + require.Equal(t, expectedTargets, targets) +} + +func TestIsWhitelistedPair(t *testing.T) { + input := CreateTestFixture(t) + + for _, p := range input.OracleKeeper.WhitelistedPairs.Iterate(input.Ctx, collections.Range[asset.Pair]{}).Keys() { + input.OracleKeeper.WhitelistedPairs.Delete(input.Ctx, p) + } + + validPairs := []asset.Pair{"foo:bar", "xxx:yyy", "whoo:whoo"} + for _, target := range validPairs { + input.OracleKeeper.WhitelistedPairs.Insert(input.Ctx, target) + require.True(t, input.OracleKeeper.IsWhitelistedPair(input.Ctx, target)) + } +} + +func TestUpdateWhitelist(t *testing.T) { + fixture := CreateTestFixture(t) + // prepare test by resetting the genesis pairs + for _, p := range fixture.OracleKeeper.WhitelistedPairs.Iterate(fixture.Ctx, collections.Range[asset.Pair]{}).Keys() { + fixture.OracleKeeper.WhitelistedPairs.Delete(fixture.Ctx, p) + } + + currentWhitelist := set.New(asset.NewPair(denoms.NIBI, denoms.USD), asset.NewPair(denoms.BTC, denoms.USD)) + for p := range currentWhitelist { + fixture.OracleKeeper.WhitelistedPairs.Insert(fixture.Ctx, p) + } + + nextWhitelist := set.New(asset.NewPair(denoms.NIBI, denoms.USD), asset.NewPair(denoms.BTC, denoms.USD)) + + // no updates case + whitelistSlice := nextWhitelist.ToSlice() + sort.Slice(whitelistSlice, func(i, j int) bool { + return whitelistSlice[i].String() < whitelistSlice[j].String() + }) + fixture.OracleKeeper.refreshWhitelist(fixture.Ctx, whitelistSlice, currentWhitelist) + assert.Equal(t, whitelistSlice, fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + + // len update (fast path) + nextWhitelist.Add(asset.NewPair(denoms.NIBI, denoms.ETH)) + whitelistSlice = nextWhitelist.ToSlice() + sort.Slice(whitelistSlice, func(i, j int) bool { + return whitelistSlice[i].String() < whitelistSlice[j].String() + }) + fixture.OracleKeeper.refreshWhitelist(fixture.Ctx, whitelistSlice, currentWhitelist) + assert.Equal(t, whitelistSlice, fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) + + // diff update (slow path) + currentWhitelist.Add(asset.NewPair(denoms.NIBI, denoms.ATOM)) + whitelistSlice = nextWhitelist.ToSlice() + sort.Slice(whitelistSlice, func(i, j int) bool { + return whitelistSlice[i].String() < whitelistSlice[j].String() + }) + fixture.OracleKeeper.refreshWhitelist(fixture.Ctx, whitelistSlice, currentWhitelist) + assert.Equal(t, whitelistSlice, fixture.OracleKeeper.GetWhitelistedPairs(fixture.Ctx)) +} diff --git a/x/oracle/module.go b/x/oracle/module.go new file mode 100644 index 00000000..fe46c41a --- /dev/null +++ b/x/oracle/module.go @@ -0,0 +1,187 @@ +package oracle + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "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" + + "github.com/archway-network/archway/x/oracle/client/cli" + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/simulation" + "github.com/archway-network/archway/x/oracle/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the oracle module. +type AppModuleBasic struct { + cdc codec.Codec +} + +// Name returns the module's name +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (b AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the staking +// module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the oracle module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var data types.GenesisState + if err := cdc.UnmarshalJSON(bz, &data); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return types.ValidateGenesis(&data) +} + +// RegisterRESTRoutes registers the REST routes for the oracle module. +func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) { +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the oracle module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + _ = types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) +} + +// GetTxCmd returns the root tx command for the oracle module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns no root query command for the oracle module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +//___________________________ + +// AppModule implements an application module for the oracle module. +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper keeper.Keeper, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc}, + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// Name returns the oracle module's name. +func (AppModule) Name() string { return types.ModuleName } + +// RegisterInvariants performs a no-op. +func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + querier := keeper.NewQuerier(am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), querier) +} + +// InitGenesis performs genesis initialization for the oracle module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, &genesisState) + + return nil +} + +// ExportGenesis returns the exported genesis state as raw bytes for the oracle +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock returns the begin blocker for the oracle module. +func (AppModule) BeginBlock(_ sdk.Context) {} + +// EndBlock returns the end blocker for the oracle module. +func (am AppModule) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { + EndBlocker(ctx, am.keeper) + return []abci.ValidatorUpdate{} +} + +//____________________________________________________________________________ + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the oracle module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents returns all the oracle content functions used to +// simulate governance proposals. +func (am AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalMsg { + return nil +} + +// RegisterStoreDecoder registers a decoder for oracle module's types +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the 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.keeper, + ) +} diff --git a/x/oracle/simulation/decoder.go b/x/oracle/simulation/decoder.go new file mode 100644 index 00000000..a395b3cd --- /dev/null +++ b/x/oracle/simulation/decoder.go @@ -0,0 +1,48 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + gogotypes "github.com/cosmos/gogoproto/types" + + "github.com/NibiruChain/collections" + + "github.com/archway-network/archway/x/oracle/types" +) + +// 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 kvA.Key[0] { + case 1: + return fmt.Sprintf("%v\n%v", collections.DecValueEncoder.Decode(kvA.Value), collections.DecValueEncoder.Decode(kvB.Value)) + case 2: + return fmt.Sprintf("%v\n%v", sdk.AccAddress(kvA.Value), sdk.AccAddress(kvB.Value)) + case 3: + var counterA, counterB gogotypes.UInt64Value + cdc.MustUnmarshal(kvA.Value, &counterA) + cdc.MustUnmarshal(kvB.Value, &counterB) + return fmt.Sprintf("%v\n%v", counterA.Value, counterB.Value) + case 4: + var prevoteA, prevoteB types.AggregateExchangeRatePrevote + cdc.MustUnmarshal(kvA.Value, &prevoteA) + cdc.MustUnmarshal(kvB.Value, &prevoteB) + return fmt.Sprintf("%v\n%v", prevoteA, prevoteB) + case 5: + var voteA, voteB types.AggregateExchangeRateVote + cdc.MustUnmarshal(kvA.Value, &voteA) + cdc.MustUnmarshal(kvB.Value, &voteB) + return fmt.Sprintf("%v\n%v", voteA, voteB) + case 6: + _, a := collections.StringKeyEncoder.Decode(kvA.Key[1:]) + _, b := collections.StringKeyEncoder.Decode(kvB.Key[1:]) + return fmt.Sprintf("%s\n%s", a, b) + default: + panic(fmt.Sprintf("invalid oracle key prefix %X", kvA.Key[:1])) + } + } +} diff --git a/x/oracle/simulation/decoder_test.go b/x/oracle/simulation/decoder_test.go new file mode 100644 index 00000000..521624f6 --- /dev/null +++ b/x/oracle/simulation/decoder_test.go @@ -0,0 +1,81 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "cosmossdk.io/math" + "github.com/cometbft/cometbft/crypto/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + gogotypes "github.com/cosmos/gogoproto/types" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/keeper" + sim "github.com/archway-network/archway/x/oracle/simulation" + "github.com/archway-network/archway/x/oracle/types" +) + +var ( + delPk = ed25519.GenPrivKey().PubKey() + feederAddr = sdk.AccAddress(delPk.Address()) + valAddr = sdk.ValAddress(delPk.Address()) +) + +func TestDecodeDistributionStore(t *testing.T) { + cdc := keeper.MakeTestCodec(t) + dec := sim.NewDecodeStore(cdc) + + exchangeRate := math.LegacyNewDecWithPrec(1234, 1) + missCounter := uint64(23) + + aggregatePrevote := types.NewAggregateExchangeRatePrevote(types.AggregateVoteHash([]byte("12345")), valAddr, 123) + aggregateVote := types.NewAggregateExchangeRateVote(types.ExchangeRateTuples{ + {Pair: asset.Registry.Pair(denoms.NIBI, denoms.NUSD), ExchangeRate: math.LegacyNewDecWithPrec(1234, 1)}, + {Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD), ExchangeRate: math.LegacyNewDecWithPrec(4321, 1)}, + }, valAddr) + + pair := "btc:usd" + + erBytes, err := exchangeRate.Marshal() + require.NoError(t, err) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: []byte{0x1, 0x2, 0x3, 0x4, 0x5}, Value: erBytes}, + {Key: []byte{0x2, 0x3, 0x4, 0x5, 0x6}, Value: feederAddr.Bytes()}, + {Key: []byte{0x3, 0x4, 0x5, 0x6, 0x7}, Value: cdc.MustMarshal(&gogotypes.UInt64Value{Value: missCounter})}, + {Key: []byte{0x4, 0x3, 0x5, 0x7, 0x8}, Value: cdc.MustMarshal(&aggregatePrevote)}, + {Key: []byte{0x5, 0x6, 0x7, 0x8, 0x9}, Value: cdc.MustMarshal(&aggregateVote)}, + {Key: append([]byte{0x6}, append([]byte(pair), 0x0)...), Value: []byte{}}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"ExchangeRate", fmt.Sprintf("%v\n%v", exchangeRate, exchangeRate)}, + {"FeederDelegation", fmt.Sprintf("%v\n%v", feederAddr, feederAddr)}, + {"MissCounter", fmt.Sprintf("%v\n%v", missCounter, missCounter)}, + {"AggregatePrevote", fmt.Sprintf("%v\n%v", aggregatePrevote, aggregatePrevote)}, + {"AggregateVote", fmt.Sprintf("%v\n%v", aggregateVote, aggregateVote)}, + {"Pairs", fmt.Sprintf("%s\n%s", pair, pair)}, + {"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 00000000..a2e4fca4 --- /dev/null +++ b/x/oracle/simulation/genesis.go @@ -0,0 +1,127 @@ +package simulation + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/archway-network/archway/x/oracle/types" +) + +// Simulation parameter constants +const ( + voteThresholdKey = "vote_threshold" + rewardBandKey = "reward_band" + slashFractionKey = "slash_fraction" + slashWindowKey = "slash_window" + minValidPerWindowKey = "min_valid_per_window" +) + +// GenVotePeriod randomized VotePeriod +func GenVotePeriod(r *rand.Rand) uint64 { + return uint64(1 + r.Intn(100)) +} + +// GenVoteThreshold randomized VoteThreshold +func GenVoteThreshold(r *rand.Rand) math.LegacyDec { + return math.LegacyNewDecWithPrec(333, 3).Add(math.LegacyNewDecWithPrec(int64(r.Intn(333)), 3)) +} + +// GenRewardBand randomized RewardBand +func GenRewardBand(r *rand.Rand) math.LegacyDec { + return math.LegacyZeroDec().Add(math.LegacyNewDecWithPrec(int64(r.Intn(100)), 3)) +} + +// GenRewardDistributionWindow randomized RewardDistributionWindow +func GenRewardDistributionWindow(r *rand.Rand) uint64 { + return uint64(100 + r.Intn(100000)) +} + +// GenSlashFraction randomized SlashFraction +func GenSlashFraction(r *rand.Rand) math.LegacyDec { + return math.LegacyZeroDec().Add(math.LegacyNewDecWithPrec(int64(r.Intn(100)), 3)) +} + +// GenSlashWindow randomized SlashWindow +func GenSlashWindow(r *rand.Rand) uint64 { + return uint64(100 + r.Intn(100000)) +} + +// GenMinValidPerWindow randomized MinValidPerWindow +func GenMinValidPerWindow(r *rand.Rand) math.LegacyDec { + return math.LegacyZeroDec().Add(math.LegacyNewDecWithPrec(int64(r.Intn(500)), 3)) +} + +// RandomizedGenState generates a random GenesisState for oracle +func RandomizedGenState(simState *module.SimulationState) { + var voteThreshold math.LegacyDec + simState.AppParams.GetOrGenerate( + voteThresholdKey, &voteThreshold, simState.Rand, + func(r *rand.Rand) { voteThreshold = GenVoteThreshold(r) }, + ) + + var rewardBand math.LegacyDec + simState.AppParams.GetOrGenerate( + rewardBandKey, &rewardBand, simState.Rand, + func(r *rand.Rand) { rewardBand = GenRewardBand(r) }, + ) + + var slashFraction math.LegacyDec + simState.AppParams.GetOrGenerate( + slashFractionKey, &slashFraction, simState.Rand, + func(r *rand.Rand) { slashFraction = GenSlashFraction(r) }, + ) + + var slashWindow uint64 + simState.AppParams.GetOrGenerate( + slashWindowKey, &slashWindow, simState.Rand, + func(r *rand.Rand) { slashWindow = GenSlashWindow(r) }, + ) + + var minValidPerWindow math.LegacyDec + simState.AppParams.GetOrGenerate( + minValidPerWindowKey, &minValidPerWindow, simState.Rand, + func(r *rand.Rand) { minValidPerWindow = GenMinValidPerWindow(r) }, + ) + + oracleGenesis := types.NewGenesisState( + types.Params{ + VotePeriod: uint64(10_000), + VoteThreshold: voteThreshold, + RewardBand: rewardBand, + Whitelist: []asset.Pair{ + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + asset.Registry.Pair(denoms.USDC, denoms.NUSD), + asset.Registry.Pair(denoms.BTC, denoms.NUSD), + asset.Registry.Pair(denoms.NIBI, denoms.NUSD), + }, + SlashFraction: slashFraction, + SlashWindow: slashWindow, + MinValidPerWindow: minValidPerWindow, + }, + []types.ExchangeRateTuple{ + {Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), ExchangeRate: math.LegacyNewDec(20_000)}, + }, + []types.FeederDelegation{}, + []types.MissCounter{}, + []types.AggregateExchangeRatePrevote{}, + []types.AggregateExchangeRateVote{}, + []asset.Pair{}, + []types.Rewards{}, + ) + + 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/operations.go b/x/oracle/simulation/operations.go new file mode 100644 index 00000000..963fcf8a --- /dev/null +++ b/x/oracle/simulation/operations.go @@ -0,0 +1,277 @@ +package simulation + +// DONTCOVER + +import ( + "math/rand" + "strings" + + "cosmossdk.io/math" + "github.com/CosmWasm/wasmd/app/params" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + + helpers "github.com/cosmos/cosmos-sdk/testutil/sims" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/archway-network/archway/x/oracle/keeper" + "github.com/archway-network/archway/x/oracle/types" +) + +// Simulation operation weights constants +const ( + OpWeightMsgAggregateExchangeRatePrevote = "op_weight_msg_exchange_rate_aggregate_prevote" + OpWeightMsgAggregateExchangeRateVote = "op_weight_msg_exchange_rate_aggregate_vote" + OpWeightMsgDelegateFeedConsent = "op_weight_msg_exchange_feed_consent" + + salt = "1234" +) + +var ( + whitelist = []asset.Pair{asset.Registry.Pair(denoms.BTC, denoms.NUSD), asset.Registry.Pair(denoms.ETH, denoms.NUSD), asset.Registry.Pair(denoms.NIBI, denoms.NUSD)} + voteHashMap map[string]string = make(map[string]string) +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simtypes.AppParams, + cdc codec.JSONCodec, + ak types.AccountKeeper, + bk types.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgAggregateExchangeRatePrevote int + weightMsgAggregateExchangeRateVote int + weightMsgDelegateFeedConsent int + ) + appParams.GetOrGenerate(OpWeightMsgAggregateExchangeRatePrevote, &weightMsgAggregateExchangeRatePrevote, nil, + func(_ *rand.Rand) { + weightMsgAggregateExchangeRatePrevote = params.DefaultWeightMsgSend * 2 + }, + ) + + appParams.GetOrGenerate(OpWeightMsgAggregateExchangeRateVote, &weightMsgAggregateExchangeRateVote, nil, + func(_ *rand.Rand) { + weightMsgAggregateExchangeRateVote = params.DefaultWeightMsgSend * 2 + }, + ) + + appParams.GetOrGenerate(OpWeightMsgDelegateFeedConsent, &weightMsgDelegateFeedConsent, nil, + func(_ *rand.Rand) { + weightMsgDelegateFeedConsent = params.DefaultWeightMsgDelegate // TODO: temp fix + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgAggregateExchangeRatePrevote, + SimulateMsgAggregateExchangeRatePrevote(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgAggregateExchangeRateVote, + SimulateMsgAggregateExchangeRateVote(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgDelegateFeedConsent, + SimulateMsgDelegateFeedConsent(ak, bk, k), + ), + } +} + +// SimulateMsgAggregateExchangeRatePrevote generates a MsgAggregateExchangeRatePrevote with random values. +// nolint: funlen +func SimulateMsgAggregateExchangeRatePrevote(ak types.AccountKeeper, bk types.BankKeeper, k 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) + address := sdk.ValAddress(simAccount.Address) + + // ensure the validator exists + val, err := k.StakingKeeper.Validator(ctx, address) + if err != nil || !val.IsBonded() { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRatePrevote, "unable to find validator"), nil, err + } + + exchangeRatesStr := "" + for _, pair := range whitelist { + price := math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 1, 10000)), int64(1)) + exchangeRatesStr += price.String() + pair.String() + "," + } + + exchangeRatesStr = strings.TrimRight(exchangeRatesStr, ",") + voteHash := types.GetAggregateVoteHash(salt, exchangeRatesStr, address) + + feederAddr := k.FeederDelegations.GetOr(ctx, address, sdk.AccAddress(address)) + feederSimAccount, _ := simtypes.FindAccount(accs, feederAddr) + + feederAccount := ak.GetAccount(ctx, feederAddr) + spendable := bk.SpendableCoins(ctx, feederAccount.GetAddress()) + + fees, err := simtypes.RandomFees(r, ctx, spendable) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRatePrevote, "unable to generate fees"), nil, err + } + + msg := types.NewMsgAggregateExchangeRatePrevote(voteHash, feederAddr, address) + + txGen := testutil.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{feederAccount.GetAccountNumber()}, + []uint64{feederAccount.GetSequence()}, + feederSimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + voteHashMap[address.String()] = exchangeRatesStr + + return simtypes.NewOperationMsg(msg, true, ""), nil, nil + } +} + +// SimulateMsgAggregateExchangeRateVote generates a MsgAggregateExchangeRateVote with random values. +// nolint: funlen +func SimulateMsgAggregateExchangeRateVote(ak types.AccountKeeper, bk types.BankKeeper, k 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) + address := sdk.ValAddress(simAccount.Address) + + // ensure the validator exists + val, err := k.StakingKeeper.Validator(ctx, address) + if err != nil || !val.IsBonded() { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "unable to find validator"), nil, err + } + + // ensure vote hash exists + exchangeRatesStr, ok := voteHashMap[address.String()] + if !ok { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "vote hash not exists"), nil, nil + } + + // get prevote + prevote, err := k.Prevotes.Get(ctx, address) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "prevote not found"), nil, nil + } + + params, _ := k.Params.Get(ctx) + if (uint64(ctx.BlockHeight())/params.VotePeriod)-(prevote.SubmitBlock/params.VotePeriod) != 1 { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "reveal period of submitted vote do not match with registered prevote"), nil, nil + } + + feederAddr := k.FeederDelegations.GetOr(ctx, address, sdk.AccAddress(address)) + feederSimAccount, _ := simtypes.FindAccount(accs, feederAddr) + feederAccount := ak.GetAccount(ctx, feederAddr) + spendableCoins := bk.SpendableCoins(ctx, feederAddr) + + fees, err := simtypes.RandomFees(r, ctx, spendableCoins) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "unable to generate fees"), nil, err + } + + msg := types.NewMsgAggregateExchangeRateVote(salt, exchangeRatesStr, feederAddr, address) + + txGen := testutil.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{feederAccount.GetAccountNumber()}, + []uint64{feederAccount.GetSequence()}, + feederSimAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, ""), nil, nil + } +} + +// SimulateMsgDelegateFeedConsent generates a MsgDelegateFeedConsent with random values. +// nolint: funlen +func SimulateMsgDelegateFeedConsent(ak types.AccountKeeper, bk types.BankKeeper, k 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) + delegateAccount, _ := simtypes.RandomAcc(r, accs) + valAddress := sdk.ValAddress(simAccount.Address) + delegateValAddress := sdk.ValAddress(delegateAccount.Address) + account := ak.GetAccount(ctx, simAccount.Address) + + // ensure the validator exists + _, err := k.StakingKeeper.Validator(ctx, valAddress) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegateFeedConsent, "unable to find validator"), nil, err + } + + // ensure the target address is not a validator + _, err = k.StakingKeeper.Validator(ctx, delegateValAddress) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegateFeedConsent, "unable to delegate to validator"), nil, err + } + + spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) + fees, err := simtypes.RandomFees(r, ctx, spendableCoins) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgAggregateExchangeRateVote, "unable to generate fees"), nil, err + } + + msg := types.NewMsgDelegateFeedConsent(valAddress, delegateAccount.Address) + + txGen := testutil.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenSignedMockTx( + r, + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + simAccount.PrivKey, + ) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to generate mock tx"), nil, err + } + + _, _, err = app.SimDeliver(txGen.TxEncoder(), tx) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to deliver tx"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, ""), nil, nil + } +} diff --git a/x/oracle/types/ballot.go b/x/oracle/types/ballot.go new file mode 100644 index 00000000..1e25c10a --- /dev/null +++ b/x/oracle/types/ballot.go @@ -0,0 +1,220 @@ +package types + +import ( + "encoding/json" + "sort" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/common" + "github.com/archway-network/archway/x/common/asset" +) + +// NOTE: we don't need to implement proto interface on this file +// these are not used in store or rpc response + +// ExchangeRateVote is a convenience wrapper to reduce redundant lookup cost +type ExchangeRateVote struct { + Pair asset.Pair + ExchangeRate math.LegacyDec // aka price + Voter sdk.ValAddress + Power int64 // how much tendermint consensus power this vote should have +} + +// NewExchangeRateVote returns a new ExchangeRateVote instance +func NewExchangeRateVote(rate math.LegacyDec, pair asset.Pair, voter sdk.ValAddress, power int64) ExchangeRateVote { + return ExchangeRateVote{ + ExchangeRate: rate, + Pair: pair, + Voter: voter, + Power: power, + } +} + +// ExchangeRateVotes is a convenience wrapper around a ExchangeRateVote slice +type ExchangeRateVotes []ExchangeRateVote + +// ToMap return organized exchange rate map by validator +func (pb ExchangeRateVotes) ToMap() map[string]math.LegacyDec { + validatorExchangeRateMap := make(map[string]math.LegacyDec) + for _, vote := range pb { + if vote.ExchangeRate.IsPositive() { + validatorExchangeRateMap[string(vote.Voter)] = vote.ExchangeRate + } + } + + return validatorExchangeRateMap +} + +// ToCrossRate return cross_rate(base/exchange_rate) votes +func (pb ExchangeRateVotes) ToCrossRate(bases map[string]math.LegacyDec) (cb ExchangeRateVotes) { + for i := range pb { + vote := pb[i] + + if exchangeRateRT, ok := bases[string(vote.Voter)]; ok && vote.ExchangeRate.IsPositive() { + vote.ExchangeRate = exchangeRateRT.Quo(vote.ExchangeRate) + } else { + // If we can't get reference exchange rate, we just convert the vote as abstain vote + vote.ExchangeRate = math.LegacyZeroDec() + vote.Power = 0 + } + + cb = append(cb, vote) + } + + return +} + +// NumValidVoters returns the number of voters who actually voted (i.e. did not abstain from voting for a pair). +func (v ExchangeRateVotes) NumValidVoters() uint64 { + count := 0 + for _, vote := range v { + if vote.ExchangeRate.IsPositive() { + count++ + } + } + return uint64(count) +} + +// Power returns the total amount of voting power in the votes +func (v ExchangeRateVotes) Power() int64 { + totalPower := int64(0) + for _, vote := range v { + totalPower += vote.Power + } + + return totalPower +} + +// WeightedMedian returns the median weighted by the power of the ExchangeRateVote. +// CONTRACT: votes must be sorted +func (votes ExchangeRateVotes) WeightedMedian() math.LegacyDec { + totalPower := votes.Power() + if votes.Len() > 0 { + pivot := int64(0) + for _, v := range votes { + votePower := v.Power + + pivot += votePower + if pivot >= (totalPower / 2) { + return v.ExchangeRate + } + } + } + return math.LegacyZeroDec() +} + +// WeightedMedianWithAssertion returns the median weighted by the power of the ExchangeRateVote. +func (pb ExchangeRateVotes) WeightedMedianWithAssertion() math.LegacyDec { + sort.Sort(pb) + totalPower := pb.Power() + if pb.Len() > 0 { + pivot := int64(0) + for _, v := range pb { + votePower := v.Power + + pivot += votePower + if pivot >= (totalPower / 2) { + return v.ExchangeRate + } + } + } + return math.LegacyZeroDec() +} + +// StandardDeviation returns the standard deviation by the power of the ExchangeRateVote. +func (pb ExchangeRateVotes) StandardDeviation(median math.LegacyDec) (standardDeviation math.LegacyDec) { + if len(pb) == 0 { + return math.LegacyZeroDec() + } + + defer func() { + if e := recover(); e != nil { + standardDeviation = math.LegacyZeroDec() + } + }() + + sum := math.LegacyZeroDec() + n := 0 + for _, v := range pb { + // ignore abstain votes in std dev calculation + if v.ExchangeRate.IsPositive() { + deviation := v.ExchangeRate.Sub(median) + sum = sum.Add(deviation.Mul(deviation)) + n += 1 + } + } + + variance := sum.QuoInt64(int64(n)) + + standardDeviation, err := common.SqrtDec(variance) + if err != nil { + return math.LegacyZeroDec() + } + + return +} + +// Len implements sort.Interface +func (pb ExchangeRateVotes) Len() int { + return len(pb) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (pb ExchangeRateVotes) Less(i, j int) bool { + return pb[i].ExchangeRate.LT(pb[j].ExchangeRate) +} + +// Swap implements sort.Interface. +func (pb ExchangeRateVotes) Swap(i, j int) { + pb[i], pb[j] = pb[j], pb[i] +} + +// ValidatorPerformance keeps track of a validator performance in the voting period. +type ValidatorPerformance struct { + // Tendermint consensus voting power + Power int64 + // RewardWeight: Weight of rewards the validator should receive in units of + // consensus power. + RewardWeight int64 + WinCount int64 // Number of valid votes for which the validator will be rewarded + AbstainCount int64 // Number of abstained votes for which there will be no reward or punishment + MissCount int64 // Number of invalid/punishable votes + ValAddress sdk.ValAddress +} + +// NewValidatorPerformance generates a ValidatorPerformance instance. +func NewValidatorPerformance(power int64, recipient sdk.ValAddress) ValidatorPerformance { + return ValidatorPerformance{ + Power: power, + RewardWeight: 0, + WinCount: 0, + AbstainCount: 0, + MissCount: 0, + ValAddress: recipient, + } +} + +type ValidatorPerformances map[string]ValidatorPerformance + +// TotalRewardWeight returns the sum of the reward weight of all the validators included in the map +func (vp ValidatorPerformances) TotalRewardWeight() int64 { + totalRewardWeight := int64(0) + for _, validator := range vp { + totalRewardWeight += validator.RewardWeight + } + + return totalRewardWeight +} + +func (vp ValidatorPerformances) String() string { + jsonBz, _ := json.MarshalIndent(vp, "", " ") + return string(jsonBz) +} + +func (vp ValidatorPerformance) String() string { + jsonBz, _ := json.MarshalIndent(vp, "", " ") + return string(jsonBz) +} diff --git a/x/oracle/types/ballot_test.go b/x/oracle/types/ballot_test.go new file mode 100644 index 00000000..5e7acc4c --- /dev/null +++ b/x/oracle/types/ballot_test.go @@ -0,0 +1,364 @@ +package types_test + +import ( + "fmt" + basicmath "math" + "sort" + "strconv" + "testing" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" + + "github.com/stretchr/testify/require" + + "github.com/cometbft/cometbft/crypto/secp256k1" + tmproto "github.com/cometbft/cometbft/types" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/archway-network/archway/x/oracle/types" +) + +func TestExchangeRateVotesToMap(t *testing.T) { + tests := struct { + votes []types.ExchangeRateVote + isValid []bool + }{ + []types.ExchangeRateVote{ + { + Voter: sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()), + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + ExchangeRate: math.LegacyNewDec(1600), + Power: 100, + }, + { + Voter: sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()), + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + ExchangeRate: math.LegacyZeroDec(), + Power: 100, + }, + { + Voter: sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()), + Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), + ExchangeRate: math.LegacyNewDec(1500), + Power: 100, + }, + }, + []bool{true, false, true}, + } + + pb := types.ExchangeRateVotes(tests.votes) + mapData := pb.ToMap() + for i, vote := range tests.votes { + exchangeRate, ok := mapData[string(vote.Voter)] + if tests.isValid[i] { + require.True(t, ok) + require.Equal(t, exchangeRate, vote.ExchangeRate) + } else { + require.False(t, ok) + } + } + require.NotPanics(t, func() { + types.ExchangeRateVotes(tests.votes).NumValidVoters() + }) +} + +func TestToCrossRate(t *testing.T) { + data := []struct { + base math.LegacyDec + quote math.LegacyDec + expected math.LegacyDec + }{ + { + base: math.LegacyNewDec(1600), + quote: math.LegacyNewDec(100), + expected: math.LegacyNewDec(16), + }, + { + base: math.LegacyZeroDec(), + quote: math.LegacyNewDec(100), + expected: math.LegacyNewDec(16), + }, + { + base: math.LegacyNewDec(1600), + quote: math.LegacyZeroDec(), + expected: math.LegacyNewDec(16), + }, + } + + pbBase := types.ExchangeRateVotes{} + pbQuote := types.ExchangeRateVotes{} + cb := types.ExchangeRateVotes{} + for _, data := range data { + valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) + if !data.base.IsZero() { + pbBase = append(pbBase, types.NewExchangeRateVote(data.base, asset.Registry.Pair(denoms.BTC, denoms.NUSD), valAddr, 100)) + } + + pbQuote = append(pbQuote, types.NewExchangeRateVote(data.quote, asset.Registry.Pair(denoms.BTC, denoms.NUSD), valAddr, 100)) + + if !data.base.IsZero() && !data.quote.IsZero() { + cb = append(cb, types.NewExchangeRateVote(data.base.Quo(data.quote), asset.Registry.Pair(denoms.BTC, denoms.NUSD), valAddr, 100)) + } else { + cb = append(cb, types.NewExchangeRateVote(math.LegacyZeroDec(), asset.Registry.Pair(denoms.BTC, denoms.NUSD), valAddr, 0)) + } + } + + basePairPrices := pbBase.ToMap() + require.Equal(t, cb, pbQuote.ToCrossRate(basePairPrices)) + + sort.Sort(cb) +} + +func TestSqrt(t *testing.T) { + num := math.LegacyNewDecWithPrec(144, 4) + floatNum, err := strconv.ParseFloat(num.String(), 64) + require.NoError(t, err) + + floatNum = basicmath.Sqrt(floatNum) + num, err = math.LegacyNewDecFromStr(fmt.Sprintf("%f", floatNum)) + require.NoError(t, err) + + require.Equal(t, math.LegacyNewDecWithPrec(12, 2), num) +} + +func TestPBPower(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, nil) + _, valAccAddrs, sk := types.GenerateRandomTestCase() + pb := types.ExchangeRateVotes{} + totalPower := int64(0) + + for i := 0; i < len(sk.Validators()); i++ { + power := sk.Validator(ctx, valAccAddrs[i]).GetConsensusPower(sdk.DefaultPowerReduction) + vote := types.NewExchangeRateVote( + math.LegacyZeroDec(), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + valAccAddrs[i], + power, + ) + + pb = append(pb, vote) + + require.NotEqual(t, int64(0), vote.Power) + + totalPower += vote.Power + } + + require.Equal(t, totalPower, pb.Power()) + + // Mix in a fake validator, the total power should not have changed. + pubKey := secp256k1.GenPrivKey().PubKey() + faceValAddr := sdk.ValAddress(pubKey.Address()) + fakeVote := types.NewExchangeRateVote( + math.LegacyOneDec(), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + faceValAddr, + 0, + ) + + pb = append(pb, fakeVote) + require.Equal(t, totalPower, pb.Power()) +} + +func TestPBWeightedMedian(t *testing.T) { + tests := []struct { + inputs []int64 + weights []int64 + isValidator []bool + median math.LegacyDec + }{ + { + // Supermajority one number + []int64{1, 2, 10, 100000}, + []int64{1, 1, 100, 1}, + []bool{true, true, true, true}, + math.LegacyNewDec(10), + }, + { + // Adding fake validator doesn't change outcome + []int64{1, 2, 10, 100000, 10000000000}, + []int64{1, 1, 100, 1, 10000}, + []bool{true, true, true, true, false}, + math.LegacyNewDec(10), + }, + { + // Tie votes + []int64{1, 2, 3, 4}, + []int64{1, 100, 100, 1}, + []bool{true, true, true, true}, + math.LegacyNewDec(2), + }, + { + // No votes + []int64{}, + []int64{}, + []bool{true, true, true, true}, + math.LegacyZeroDec(), + }, + { + // not sorted + []int64{2, 1, 10, 100000}, + []int64{1, 1, 100, 1}, + []bool{true, true, true, true}, + math.LegacyNewDec(10), + }, + } + + for _, tc := range tests { + pb := types.ExchangeRateVotes{} + for i, input := range tc.inputs { + valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) + + power := tc.weights[i] + if !tc.isValidator[i] { + power = 0 + } + + vote := types.NewExchangeRateVote( + math.LegacyNewDec(int64(input)), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + valAddr, + power, + ) + + pb = append(pb, vote) + } + + require.Equal(t, tc.median, pb.WeightedMedian()) + require.Equal(t, tc.median, pb.WeightedMedianWithAssertion()) + } +} + +func TestPBStandardDeviation(t *testing.T) { + tests := []struct { + inputs []float64 + weights []int64 + isValidator []bool + standardDeviation math.LegacyDec + }{ + { + // Supermajority one number + []float64{1.0, 2.0, 10.0, 100000.0}, + []int64{1, 1, 100, 1}, + []bool{true, true, true, true}, + math.LegacyMustNewDecFromStr("49995.000362536000000000"), + }, + { + // Adding fake validator doesn't change outcome + []float64{1.0, 2.0, 10.0, 100000.0, 10000000000}, + []int64{1, 1, 100, 1, 10000}, + []bool{true, true, true, true, false}, + math.LegacyMustNewDecFromStr("4472135950.751005519000000000"), + }, + { + // Tie votes + []float64{1.0, 2.0, 3.0, 4.0}, + []int64{1, 100, 100, 1}, + []bool{true, true, true, true}, + math.LegacyMustNewDecFromStr("1.224744871000000000"), + }, + { + // No votes + []float64{}, + []int64{}, + []bool{true, true, true, true}, + math.LegacyNewDecWithPrec(0, 0), + }, + { + // Abstain votes are ignored + []float64{1.0, 2.0, 10.0, 100000.0, -99999999999.0, 0}, + []int64{1, 1, 100, 1, 1, 1}, + []bool{true, true, true, true, true, true}, + math.LegacyMustNewDecFromStr("49995.000362536000000000"), + }, + } + + base := basicmath.Pow10(types.OracleDecPrecision) + for _, tc := range tests { + pb := types.ExchangeRateVotes{} + for i, input := range tc.inputs { + valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) + + power := tc.weights[i] + if !tc.isValidator[i] { + power = 0 + } + + vote := types.NewExchangeRateVote( + math.LegacyNewDecWithPrec(int64(input*base), int64(types.OracleDecPrecision)), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + valAddr, + power, + ) + + pb = append(pb, vote) + } + + require.Equal(t, tc.standardDeviation, pb.StandardDeviation(pb.WeightedMedianWithAssertion())) + } +} + +func TestPBStandardDeviationOverflow(t *testing.T) { + valAddr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) + exchangeRate, err := math.LegacyNewDecFromStr("100000000000000000000000000000000000000000000000000000000.0") + require.NoError(t, err) + + pb := types.ExchangeRateVotes{types.NewExchangeRateVote( + math.LegacyZeroDec(), + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + valAddr, + 2, + ), types.NewExchangeRateVote( + exchangeRate, + asset.Registry.Pair(denoms.ETH, denoms.NUSD), + valAddr, + 1, + )} + + require.Equal(t, math.LegacyZeroDec(), pb.StandardDeviation(pb.WeightedMedianWithAssertion())) +} + +func TestNewClaim(t *testing.T) { + power := int64(10) + weight := int64(11) + winCount := int64(1) + addr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address().Bytes()) + claim := types.ValidatorPerformance{ + Power: power, + RewardWeight: weight, + WinCount: winCount, + ValAddress: addr, + } + require.Equal(t, types.ValidatorPerformance{ + Power: power, + RewardWeight: weight, + WinCount: winCount, + ValAddress: addr, + }, claim) +} + +func TestValidatorPerformances(t *testing.T) { + power := int64(42) + valNames := []string{"val0", "val1", "val2", "val3"} + perfList := []types.ValidatorPerformance{ + types.NewValidatorPerformance(power, sdk.ValAddress([]byte(valNames[0]))), + types.NewValidatorPerformance(power, sdk.ValAddress([]byte(valNames[1]))), + types.NewValidatorPerformance(power, sdk.ValAddress([]byte(valNames[2]))), + types.NewValidatorPerformance(power, sdk.ValAddress([]byte(valNames[3]))), + } + perfs := make(types.ValidatorPerformances) + for idx, perf := range perfList { + perfs[valNames[idx]] = perf + } + + require.NotPanics(t, func() { + out := perfs.String() + require.NotEmpty(t, out) + + out = perfs[valNames[0]].String() + require.NotEmpty(t, out) + }) +} diff --git a/x/oracle/types/codec.go b/x/oracle/types/codec.go new file mode 100644 index 00000000..efe53d14 --- /dev/null +++ b/x/oracle/types/codec.go @@ -0,0 +1,46 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterLegacyAminoCodec registers the necessary x/oracle interfaces and concrete types +// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgAggregateExchangeRatePrevote{}, "oracle/MsgAggregateExchangeRatePrevote", nil) + cdc.RegisterConcrete(&MsgAggregateExchangeRateVote{}, "oracle/MsgAggregateExchangeRateVote", nil) + cdc.RegisterConcrete(&MsgDelegateFeedConsent{}, "oracle/MsgDelegateFeedConsent", nil) +} + +// RegisterInterfaces registers the x/oracle interfaces types with the interface registry +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgDelegateFeedConsent{}, + &MsgAggregateExchangeRatePrevote{}, + &MsgAggregateExchangeRateVote{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + + // ModuleCdc references the global x/oracle module codec. Note, the codec should + // ONLY be used in certain instances of tests and for JSON encoding as Amino is + // still used for that purpose. + // + // The actual codec used for serialization should be provided to x/staking and + // defined at the application level. + ModuleCdc = codec.NewAminoCodec(amino) +) + +func init() { + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) + amino.Seal() +} diff --git a/x/oracle/types/core.go b/x/oracle/types/core.go new file mode 100644 index 00000000..774c4485 --- /dev/null +++ b/x/oracle/types/core.go @@ -0,0 +1,8 @@ +package types + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// IsPeriodLastBlock returns true if we are at the last block of the period +func IsPeriodLastBlock(ctx sdk.Context, blocksPerPeriod uint64) bool { + return ((uint64)(ctx.BlockHeight())+1)%blocksPerPeriod == 0 +} diff --git a/x/oracle/types/errors.go b/x/oracle/types/errors.go new file mode 100644 index 00000000..95332e11 --- /dev/null +++ b/x/oracle/types/errors.go @@ -0,0 +1,38 @@ +package types + +import ( + "fmt" + "sync/atomic" + + "github.com/cometbft/cometbft/crypto/tmhash" + + sdkerrors "cosmossdk.io/errors" +) + +var moduleErrorCodeIdx uint32 = 1 + +// registerError: Cleaner way of using 'sdkerrors.Register' without as much time +// manually writing integers. +func registerError(msg string) *sdkerrors.Error { + // Atomic for thread safety on concurrent calls + atomic.AddUint32(&moduleErrorCodeIdx, 1) + return sdkerrors.Register(ModuleName, moduleErrorCodeIdx, msg) +} + +// Oracle Errors +var ( + ErrInvalidExchangeRate = registerError("invalid exchange rate") + ErrNoPrevote = registerError("no prevote") + ErrNoVote = registerError("no vote") + ErrNoVotingPermission = registerError("unauthorized voter") + ErrInvalidHash = registerError("invalid hash") + ErrInvalidHashLength = registerError( + fmt.Sprintf("invalid hash length; should equal %d", tmhash.TruncatedSize)) + ErrHashVerificationFailed = registerError("hash verification failed") + ErrRevealPeriodMissMatch = registerError("reveal period of submitted vote do not match with registered prevote") + ErrInvalidSaltLength = registerError("invalid salt length; should be 1~4") + ErrNoAggregatePrevote = registerError("no aggregate prevote") + ErrNoAggregateVote = registerError("no aggregate vote") + ErrUnknownPair = registerError("unknown pair") + ErrNoValidTWAP = registerError("TWA price not found") +) diff --git a/x/oracle/types/event.pb.go b/x/oracle/types/event.pb.go new file mode 100644 index 00000000..1dbfe914 --- /dev/null +++ b/x/oracle/types/event.pb.go @@ -0,0 +1,1513 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/event.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Emitted when a price is posted +type EventPriceUpdate struct { + Pair string `protobuf:"bytes,1,opt,name=pair,proto3" json:"pair,omitempty"` + Price cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=price,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"price"` + TimestampMs int64 `protobuf:"varint,3,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` +} + +func (m *EventPriceUpdate) Reset() { *m = EventPriceUpdate{} } +func (m *EventPriceUpdate) String() string { return proto.CompactTextString(m) } +func (*EventPriceUpdate) ProtoMessage() {} +func (*EventPriceUpdate) Descriptor() ([]byte, []int) { + return fileDescriptor_ab464c0117763229, []int{0} +} +func (m *EventPriceUpdate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventPriceUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventPriceUpdate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventPriceUpdate) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventPriceUpdate.Merge(m, src) +} +func (m *EventPriceUpdate) XXX_Size() int { + return m.Size() +} +func (m *EventPriceUpdate) XXX_DiscardUnknown() { + xxx_messageInfo_EventPriceUpdate.DiscardUnknown(m) +} + +var xxx_messageInfo_EventPriceUpdate proto.InternalMessageInfo + +func (m *EventPriceUpdate) GetPair() string { + if m != nil { + return m.Pair + } + return "" +} + +func (m *EventPriceUpdate) GetTimestampMs() int64 { + if m != nil { + return m.TimestampMs + } + return 0 +} + +// Emitted when a valoper delegates oracle voting rights to a feeder address. +type EventDelegateFeederConsent struct { + // Validator is the Bech32 address that is delegating voting rights. + Validator string `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator,omitempty"` + // Feeder is the delegate or representative that will be able to send + // vote and prevote transaction messages. + Feeder string `protobuf:"bytes,2,opt,name=feeder,proto3" json:"feeder,omitempty"` +} + +func (m *EventDelegateFeederConsent) Reset() { *m = EventDelegateFeederConsent{} } +func (m *EventDelegateFeederConsent) String() string { return proto.CompactTextString(m) } +func (*EventDelegateFeederConsent) ProtoMessage() {} +func (*EventDelegateFeederConsent) Descriptor() ([]byte, []int) { + return fileDescriptor_ab464c0117763229, []int{1} +} +func (m *EventDelegateFeederConsent) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventDelegateFeederConsent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventDelegateFeederConsent.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventDelegateFeederConsent) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventDelegateFeederConsent.Merge(m, src) +} +func (m *EventDelegateFeederConsent) XXX_Size() int { + return m.Size() +} +func (m *EventDelegateFeederConsent) XXX_DiscardUnknown() { + xxx_messageInfo_EventDelegateFeederConsent.DiscardUnknown(m) +} + +var xxx_messageInfo_EventDelegateFeederConsent proto.InternalMessageInfo + +func (m *EventDelegateFeederConsent) GetValidator() string { + if m != nil { + return m.Validator + } + return "" +} + +func (m *EventDelegateFeederConsent) GetFeeder() string { + if m != nil { + return m.Feeder + } + return "" +} + +// Emitted by MsgAggregateExchangeVote when an aggregate vote is added to state +type EventAggregateVote struct { + // Validator is the Bech32 address to which the vote will be credited. + Validator string `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator,omitempty"` + // Feeder is the delegate or representative that will send vote and prevote + // transaction messages on behalf of the voting validator. + Feeder string `protobuf:"bytes,2,opt,name=feeder,proto3" json:"feeder,omitempty"` + Prices ExchangeRateTuples `protobuf:"bytes,3,rep,name=prices,proto3,castrepeated=ExchangeRateTuples" json:"prices"` +} + +func (m *EventAggregateVote) Reset() { *m = EventAggregateVote{} } +func (m *EventAggregateVote) String() string { return proto.CompactTextString(m) } +func (*EventAggregateVote) ProtoMessage() {} +func (*EventAggregateVote) Descriptor() ([]byte, []int) { + return fileDescriptor_ab464c0117763229, []int{2} +} +func (m *EventAggregateVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventAggregateVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventAggregateVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventAggregateVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventAggregateVote.Merge(m, src) +} +func (m *EventAggregateVote) XXX_Size() int { + return m.Size() +} +func (m *EventAggregateVote) XXX_DiscardUnknown() { + xxx_messageInfo_EventAggregateVote.DiscardUnknown(m) +} + +var xxx_messageInfo_EventAggregateVote proto.InternalMessageInfo + +func (m *EventAggregateVote) GetValidator() string { + if m != nil { + return m.Validator + } + return "" +} + +func (m *EventAggregateVote) GetFeeder() string { + if m != nil { + return m.Feeder + } + return "" +} + +func (m *EventAggregateVote) GetPrices() ExchangeRateTuples { + if m != nil { + return m.Prices + } + return nil +} + +// Emitted by MsgAggregateExchangePrevote when an aggregate prevote is added +// to state +type EventAggregatePrevote struct { + // Validator is the Bech32 address to which the vote will be credited. + Validator string `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator,omitempty"` + // Feeder is the delegate or representative that will send vote and prevote + // transaction messages on behalf of the voting validator. + Feeder string `protobuf:"bytes,2,opt,name=feeder,proto3" json:"feeder,omitempty"` +} + +func (m *EventAggregatePrevote) Reset() { *m = EventAggregatePrevote{} } +func (m *EventAggregatePrevote) String() string { return proto.CompactTextString(m) } +func (*EventAggregatePrevote) ProtoMessage() {} +func (*EventAggregatePrevote) Descriptor() ([]byte, []int) { + return fileDescriptor_ab464c0117763229, []int{3} +} +func (m *EventAggregatePrevote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventAggregatePrevote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventAggregatePrevote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventAggregatePrevote) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventAggregatePrevote.Merge(m, src) +} +func (m *EventAggregatePrevote) XXX_Size() int { + return m.Size() +} +func (m *EventAggregatePrevote) XXX_DiscardUnknown() { + xxx_messageInfo_EventAggregatePrevote.DiscardUnknown(m) +} + +var xxx_messageInfo_EventAggregatePrevote proto.InternalMessageInfo + +func (m *EventAggregatePrevote) GetValidator() string { + if m != nil { + return m.Validator + } + return "" +} + +func (m *EventAggregatePrevote) GetFeeder() string { + if m != nil { + return m.Feeder + } + return "" +} + +type EventValidatorPerformance struct { + // Validator is the Bech32 address to which the vote will be credited. + Validator string `protobuf:"bytes,1,opt,name=validator,proto3" json:"validator,omitempty"` + // Tendermint consensus voting power + VotingPower int64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + // RewardWeight: Weight of rewards the validator should receive in units of + // consensus power. + RewardWeight int64 `protobuf:"varint,3,opt,name=reward_weight,json=rewardWeight,proto3" json:"reward_weight,omitempty"` + // Number of valid votes for which the validator will be rewarded + WinCount int64 `protobuf:"varint,4,opt,name=win_count,json=winCount,proto3" json:"win_count,omitempty"` + // Number of abstained votes for which there will be no reward or punishment + AbstainCount int64 `protobuf:"varint,5,opt,name=abstain_count,json=abstainCount,proto3" json:"abstain_count,omitempty"` + // Number of invalid/punishable votes + MissCount int64 `protobuf:"varint,6,opt,name=miss_count,json=missCount,proto3" json:"miss_count,omitempty"` +} + +func (m *EventValidatorPerformance) Reset() { *m = EventValidatorPerformance{} } +func (m *EventValidatorPerformance) String() string { return proto.CompactTextString(m) } +func (*EventValidatorPerformance) ProtoMessage() {} +func (*EventValidatorPerformance) Descriptor() ([]byte, []int) { + return fileDescriptor_ab464c0117763229, []int{4} +} +func (m *EventValidatorPerformance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventValidatorPerformance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventValidatorPerformance.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventValidatorPerformance) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventValidatorPerformance.Merge(m, src) +} +func (m *EventValidatorPerformance) XXX_Size() int { + return m.Size() +} +func (m *EventValidatorPerformance) XXX_DiscardUnknown() { + xxx_messageInfo_EventValidatorPerformance.DiscardUnknown(m) +} + +var xxx_messageInfo_EventValidatorPerformance proto.InternalMessageInfo + +func (m *EventValidatorPerformance) GetValidator() string { + if m != nil { + return m.Validator + } + return "" +} + +func (m *EventValidatorPerformance) GetVotingPower() int64 { + if m != nil { + return m.VotingPower + } + return 0 +} + +func (m *EventValidatorPerformance) GetRewardWeight() int64 { + if m != nil { + return m.RewardWeight + } + return 0 +} + +func (m *EventValidatorPerformance) GetWinCount() int64 { + if m != nil { + return m.WinCount + } + return 0 +} + +func (m *EventValidatorPerformance) GetAbstainCount() int64 { + if m != nil { + return m.AbstainCount + } + return 0 +} + +func (m *EventValidatorPerformance) GetMissCount() int64 { + if m != nil { + return m.MissCount + } + return 0 +} + +func init() { + proto.RegisterType((*EventPriceUpdate)(nil), "archway.oracle.v1.EventPriceUpdate") + proto.RegisterType((*EventDelegateFeederConsent)(nil), "archway.oracle.v1.EventDelegateFeederConsent") + proto.RegisterType((*EventAggregateVote)(nil), "archway.oracle.v1.EventAggregateVote") + proto.RegisterType((*EventAggregatePrevote)(nil), "archway.oracle.v1.EventAggregatePrevote") + proto.RegisterType((*EventValidatorPerformance)(nil), "archway.oracle.v1.EventValidatorPerformance") +} + +func init() { proto.RegisterFile("archway/oracle/v1/event.proto", fileDescriptor_ab464c0117763229) } + +var fileDescriptor_ab464c0117763229 = []byte{ + // 515 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0x41, 0x6f, 0xd3, 0x4c, + 0x10, 0x8d, 0xbf, 0xb4, 0xd1, 0x97, 0x4d, 0x90, 0x60, 0x05, 0x28, 0xa4, 0xad, 0xd3, 0xa6, 0x1c, + 0x72, 0xc1, 0x56, 0xe0, 0xc4, 0x91, 0xb4, 0xe5, 0x02, 0x95, 0x22, 0x0b, 0x0a, 0xe2, 0x12, 0x6d, + 0x9c, 0xe9, 0x66, 0xd5, 0x78, 0xc7, 0xda, 0xdd, 0xd8, 0xcd, 0x9d, 0x1f, 0xc0, 0x8f, 0xe0, 0xc4, + 0x2f, 0xe9, 0xb1, 0x47, 0xc4, 0xa1, 0xa0, 0xe4, 0x8f, 0x20, 0xaf, 0x37, 0x45, 0x28, 0x07, 0xa4, + 0xde, 0x66, 0xde, 0x7b, 0xf3, 0xfc, 0xbc, 0xa3, 0x21, 0x7b, 0x4c, 0xc5, 0xd3, 0x9c, 0x2d, 0x42, + 0x54, 0x2c, 0x9e, 0x41, 0x98, 0xf5, 0x43, 0xc8, 0x40, 0x9a, 0x20, 0x55, 0x68, 0x90, 0x3e, 0x70, + 0x74, 0x50, 0xd2, 0x41, 0xd6, 0x6f, 0xfb, 0x9b, 0x13, 0x8e, 0xb4, 0x23, 0xed, 0x87, 0x1c, 0x39, + 0xda, 0x32, 0x2c, 0x2a, 0x87, 0xee, 0x72, 0x44, 0x3e, 0x83, 0x90, 0xa5, 0x22, 0x64, 0x52, 0xa2, + 0x61, 0x46, 0xa0, 0xd4, 0x25, 0xdb, 0xfd, 0xec, 0x91, 0xfb, 0x27, 0xc5, 0x67, 0x87, 0x4a, 0xc4, + 0xf0, 0x3e, 0x9d, 0x30, 0x03, 0x94, 0x92, 0xad, 0x94, 0x09, 0xd5, 0xf2, 0xf6, 0xbd, 0x5e, 0x3d, + 0xb2, 0x35, 0x7d, 0x49, 0xb6, 0xd3, 0x42, 0xd2, 0xfa, 0xaf, 0x00, 0x07, 0x87, 0x57, 0x37, 0x9d, + 0xca, 0x8f, 0x9b, 0xce, 0x4e, 0x8c, 0x3a, 0x41, 0xad, 0x27, 0x17, 0x81, 0xc0, 0x30, 0x61, 0x66, + 0x1a, 0xbc, 0x05, 0xce, 0xe2, 0xc5, 0x31, 0xc4, 0x51, 0x39, 0x41, 0x0f, 0x48, 0xd3, 0x88, 0x04, + 0xb4, 0x61, 0x49, 0x3a, 0x4a, 0x74, 0xab, 0xba, 0xef, 0xf5, 0xaa, 0x51, 0xe3, 0x16, 0x3b, 0xd5, + 0xdd, 0x88, 0xb4, 0x6d, 0x8a, 0x63, 0x98, 0x01, 0x67, 0x06, 0x5e, 0x03, 0x4c, 0x40, 0x1d, 0xa1, + 0xd4, 0x20, 0x0d, 0xdd, 0x25, 0xf5, 0x8c, 0xcd, 0xc4, 0x84, 0x19, 0x5c, 0x87, 0xfa, 0x03, 0xd0, + 0xc7, 0xa4, 0x76, 0x6e, 0xe5, 0x65, 0xb4, 0xc8, 0x75, 0xdd, 0xaf, 0x1e, 0xa1, 0xd6, 0xf4, 0x15, + 0xe7, 0xca, 0xba, 0x9e, 0xa1, 0x81, 0xbb, 0x99, 0xd1, 0x8f, 0xa4, 0x66, 0x7f, 0xa6, 0x48, 0x5f, + 0xed, 0x35, 0x9e, 0x3f, 0x0d, 0x36, 0xf6, 0x13, 0x9c, 0x5c, 0xc6, 0x53, 0x26, 0x39, 0x44, 0xcc, + 0xc0, 0xbb, 0x79, 0x3a, 0x83, 0x41, 0xbb, 0x78, 0xa5, 0x6f, 0x3f, 0x3b, 0x74, 0x83, 0xd2, 0x91, + 0xf3, 0xeb, 0x9e, 0x92, 0x47, 0x7f, 0xa7, 0x1c, 0x2a, 0xc8, 0xee, 0x1c, 0xb4, 0xbb, 0xf4, 0xc8, + 0x13, 0xeb, 0x77, 0xb6, 0x96, 0x0e, 0x41, 0x9d, 0xa3, 0x4a, 0x98, 0x8c, 0xff, 0xe5, 0x79, 0x40, + 0x9a, 0x19, 0x1a, 0x21, 0xf9, 0x28, 0xc5, 0xdc, 0x39, 0x57, 0xa3, 0x46, 0x89, 0x0d, 0x0b, 0x88, + 0x1e, 0x92, 0x7b, 0x0a, 0x72, 0xa6, 0x26, 0xa3, 0x1c, 0x04, 0x9f, 0x1a, 0xb7, 0xcc, 0x66, 0x09, + 0x7e, 0xb0, 0x18, 0xdd, 0x21, 0xf5, 0x5c, 0xc8, 0x51, 0x8c, 0x73, 0x69, 0x5a, 0x5b, 0x56, 0xf0, + 0x7f, 0x2e, 0xe4, 0x51, 0xd1, 0x17, 0x0e, 0x6c, 0xac, 0x0d, 0xbb, 0x15, 0x6c, 0x97, 0x0e, 0x0e, + 0x2c, 0x45, 0x7b, 0x84, 0x24, 0x42, 0x6b, 0xa7, 0xa8, 0x59, 0x45, 0xbd, 0x40, 0x2c, 0x3d, 0x78, + 0x73, 0xb5, 0xf4, 0xbd, 0xeb, 0xa5, 0xef, 0xfd, 0x5a, 0xfa, 0xde, 0x97, 0x95, 0x5f, 0xb9, 0x5e, + 0xf9, 0x95, 0xef, 0x2b, 0xbf, 0xf2, 0xa9, 0xcf, 0x85, 0x99, 0xce, 0xc7, 0x41, 0x8c, 0x49, 0xe8, + 0x36, 0xf4, 0x4c, 0x82, 0xc9, 0x51, 0x5d, 0xac, 0xfb, 0xf0, 0x72, 0x7d, 0x40, 0x66, 0x91, 0x82, + 0x1e, 0xd7, 0xec, 0x25, 0xbc, 0xf8, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xf2, 0xff, 0xe5, 0x7e, 0x91, + 0x03, 0x00, 0x00, +} + +func (m *EventPriceUpdate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventPriceUpdate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventPriceUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TimestampMs != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.TimestampMs)) + i-- + dAtA[i] = 0x18 + } + { + size := m.Price.Size() + i -= size + if _, err := m.Price.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintEvent(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Pair) > 0 { + i -= len(m.Pair) + copy(dAtA[i:], m.Pair) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Pair))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventDelegateFeederConsent) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventDelegateFeederConsent) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventDelegateFeederConsent) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Feeder) > 0 { + i -= len(m.Feeder) + copy(dAtA[i:], m.Feeder) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Feeder))) + i-- + dAtA[i] = 0x12 + } + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventAggregateVote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventAggregateVote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventAggregateVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Prices) > 0 { + for iNdEx := len(m.Prices) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Prices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvent(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Feeder) > 0 { + i -= len(m.Feeder) + copy(dAtA[i:], m.Feeder) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Feeder))) + i-- + dAtA[i] = 0x12 + } + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventAggregatePrevote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventAggregatePrevote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventAggregatePrevote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Feeder) > 0 { + i -= len(m.Feeder) + copy(dAtA[i:], m.Feeder) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Feeder))) + i-- + dAtA[i] = 0x12 + } + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventValidatorPerformance) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventValidatorPerformance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventValidatorPerformance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MissCount != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.MissCount)) + i-- + dAtA[i] = 0x30 + } + if m.AbstainCount != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.AbstainCount)) + i-- + dAtA[i] = 0x28 + } + if m.WinCount != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.WinCount)) + i-- + dAtA[i] = 0x20 + } + if m.RewardWeight != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.RewardWeight)) + i-- + dAtA[i] = 0x18 + } + if m.VotingPower != 0 { + i = encodeVarintEvent(dAtA, i, uint64(m.VotingPower)) + i-- + dAtA[i] = 0x10 + } + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintEvent(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintEvent(dAtA []byte, offset int, v uint64) int { + offset -= sovEvent(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *EventPriceUpdate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Pair) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + l = m.Price.Size() + n += 1 + l + sovEvent(uint64(l)) + if m.TimestampMs != 0 { + n += 1 + sovEvent(uint64(m.TimestampMs)) + } + return n +} + +func (m *EventDelegateFeederConsent) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + l = len(m.Feeder) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + return n +} + +func (m *EventAggregateVote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + l = len(m.Feeder) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + if len(m.Prices) > 0 { + for _, e := range m.Prices { + l = e.Size() + n += 1 + l + sovEvent(uint64(l)) + } + } + return n +} + +func (m *EventAggregatePrevote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + l = len(m.Feeder) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + return n +} + +func (m *EventValidatorPerformance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovEvent(uint64(l)) + } + if m.VotingPower != 0 { + n += 1 + sovEvent(uint64(m.VotingPower)) + } + if m.RewardWeight != 0 { + n += 1 + sovEvent(uint64(m.RewardWeight)) + } + if m.WinCount != 0 { + n += 1 + sovEvent(uint64(m.WinCount)) + } + if m.AbstainCount != 0 { + n += 1 + sovEvent(uint64(m.AbstainCount)) + } + if m.MissCount != 0 { + n += 1 + sovEvent(uint64(m.MissCount)) + } + return n +} + +func sovEvent(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvent(x uint64) (n int) { + return sovEvent(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EventPriceUpdate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventPriceUpdate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventPriceUpdate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pair", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pair = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Price", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Price.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimestampMs", wireType) + } + m.TimestampMs = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimestampMs |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvent(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvent + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventDelegateFeederConsent) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventDelegateFeederConsent: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventDelegateFeederConsent: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Feeder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Feeder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvent(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvent + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventAggregateVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventAggregateVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventAggregateVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Feeder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Feeder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prices = append(m.Prices, ExchangeRateTuple{}) + if err := m.Prices[len(m.Prices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvent(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvent + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventAggregatePrevote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventAggregatePrevote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventAggregatePrevote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Feeder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Feeder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvent(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvent + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventValidatorPerformance) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventValidatorPerformance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventValidatorPerformance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvent + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvent + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotingPower", wireType) + } + m.VotingPower = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotingPower |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RewardWeight", wireType) + } + m.RewardWeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RewardWeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field WinCount", wireType) + } + m.WinCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.WinCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AbstainCount", wireType) + } + m.AbstainCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AbstainCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MissCount", wireType) + } + m.MissCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvent + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MissCount |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipEvent(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvent + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipEvent(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvent + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvent + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvent + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthEvent + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupEvent + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthEvent + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthEvent = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowEvent = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupEvent = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/expected_keeper.go b/x/oracle/types/expected_keeper.go new file mode 100644 index 00000000..fa2a5de4 --- /dev/null +++ b/x/oracle/types/expected_keeper.go @@ -0,0 +1,51 @@ +package types + +import ( + context "context" + + corestore "cosmossdk.io/core/store" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// StakingKeeper is expected keeper for staking module +type StakingKeeper interface { + Validator(ctx context.Context, address sdk.ValAddress) (stakingtypes.ValidatorI, error) // get validator by operator address; nil when validator not found + TotalBondedTokens(context.Context) (sdkmath.Int, error) // total bonded tokens within the validator set + Slash(context.Context, sdk.ConsAddress, int64, int64, sdkmath.LegacyDec) (sdkmath.Int, error) // slash the validator and delegators of the validator, specifying offense height, offense power, and slash fraction + Jail(context.Context, sdk.ConsAddress) error // jail a validator + ValidatorsPowerStoreIterator(ctx context.Context) (corestore.Iterator, error) + MaxValidators(context.Context) (uint32, error) // MaxValidators returns the maximum amount of bonded validators + PowerReduction(ctx context.Context) (res sdkmath.Int) +} + +type SlashingKeeper interface { + Slash(ctx context.Context, consAddr sdk.ConsAddress, fraction sdkmath.LegacyDec, power int64, height int64) error + Jail(context.Context, sdk.ConsAddress) error +} + +// DistributionKeeper is expected keeper for distribution module +type DistributionKeeper interface { + AllocateTokensToValidator(ctx context.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins) error + + // only used for simulation + GetValidatorOutstandingRewardsCoins(ctx context.Context, val sdk.ValAddress) (sdk.DecCoins, error) +} + +// AccountKeeper is expected keeper for auth module +type AccountKeeper interface { + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx context.Context, moduleName string) sdk.ModuleAccountI + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI // only used for simulation +} + +// BankKeeper defines the expected interface needed to retrieve account balances. +type BankKeeper interface { + GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin + GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins + SendCoinsFromModuleToModule(ctx context.Context, senderModule string, recipientModule string, amt sdk.Coins) error + // only used for simulation + SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins +} diff --git a/x/oracle/types/export.go b/x/oracle/types/export.go new file mode 100644 index 00000000..fb4b6e3b --- /dev/null +++ b/x/oracle/types/export.go @@ -0,0 +1,12 @@ +package types + +import ( + grpc "google.golang.org/grpc" +) + +// GrpcQueryServiceDesc represents the query server's RPC service specification. +// This gives access to the service name and method names needed for stargate +// queries. +func GrpcQueryServiceDesc() grpc.ServiceDesc { + return _Query_serviceDesc +} diff --git a/x/oracle/types/genesis.go b/x/oracle/types/genesis.go new file mode 100644 index 00000000..341d9bf2 --- /dev/null +++ b/x/oracle/types/genesis.go @@ -0,0 +1,60 @@ +package types + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/archway-network/archway/x/common/asset" +) + +// NewGenesisState creates a new GenesisState object +func NewGenesisState( + params Params, rates []ExchangeRateTuple, + feederDelegations []FeederDelegation, missCounters []MissCounter, + aggregateExchangeRatePrevotes []AggregateExchangeRatePrevote, + aggregateExchangeRateVotes []AggregateExchangeRateVote, + pairs []asset.Pair, + rewards []Rewards, +) *GenesisState { + return &GenesisState{ + Params: params, + FeederDelegations: feederDelegations, + ExchangeRates: rates, + MissCounters: missCounters, + AggregateExchangeRatePrevotes: aggregateExchangeRatePrevotes, + AggregateExchangeRateVotes: aggregateExchangeRateVotes, + Pairs: pairs, + Rewards: rewards, + } +} + +// DefaultGenesisState - default GenesisState +func DefaultGenesisState() *GenesisState { + return NewGenesisState( + DefaultParams(), + []ExchangeRateTuple{}, + []FeederDelegation{}, + []MissCounter{}, + []AggregateExchangeRatePrevote{}, + []AggregateExchangeRateVote{}, + []asset.Pair{}, + []Rewards{}) +} + +// ValidateGenesis validates the oracle genesis state +func ValidateGenesis(data *GenesisState) error { + return data.Params.Validate() +} + +// GetGenesisStateFromAppState returns x/oracle GenesisState given raw application +// genesis state. +func GetGenesisStateFromAppState(cdc codec.JSONCodec, appState map[string]json.RawMessage) *GenesisState { + var genesisState GenesisState + + if appState[ModuleName] != nil { + cdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) + } + + return &genesisState +} diff --git a/x/oracle/types/genesis.pb.go b/x/oracle/types/genesis.pb.go new file mode 100644 index 00000000..fa8c5aab --- /dev/null +++ b/x/oracle/types/genesis.pb.go @@ -0,0 +1,1205 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/genesis.proto + +package types + +import ( + fmt "fmt" + github_com_archway_network_archway_x_common_asset "github.com/archway-network/archway/x/common/asset" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the oracle module's genesis state. +type GenesisState struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + FeederDelegations []FeederDelegation `protobuf:"bytes,2,rep,name=feeder_delegations,json=feederDelegations,proto3" json:"feeder_delegations"` + ExchangeRates ExchangeRateTuples `protobuf:"bytes,3,rep,name=exchange_rates,json=exchangeRates,proto3,castrepeated=ExchangeRateTuples" json:"exchange_rates"` + MissCounters []MissCounter `protobuf:"bytes,4,rep,name=miss_counters,json=missCounters,proto3" json:"miss_counters"` + AggregateExchangeRatePrevotes []AggregateExchangeRatePrevote `protobuf:"bytes,5,rep,name=aggregate_exchange_rate_prevotes,json=aggregateExchangeRatePrevotes,proto3" json:"aggregate_exchange_rate_prevotes"` + AggregateExchangeRateVotes []AggregateExchangeRateVote `protobuf:"bytes,6,rep,name=aggregate_exchange_rate_votes,json=aggregateExchangeRateVotes,proto3" json:"aggregate_exchange_rate_votes"` + Pairs []github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,7,rep,name=pairs,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"pairs"` + Rewards []Rewards `protobuf:"bytes,8,rep,name=rewards,proto3" json:"rewards"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_6285e82cfd585f9b, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func (m *GenesisState) GetFeederDelegations() []FeederDelegation { + if m != nil { + return m.FeederDelegations + } + return nil +} + +func (m *GenesisState) GetExchangeRates() ExchangeRateTuples { + if m != nil { + return m.ExchangeRates + } + return nil +} + +func (m *GenesisState) GetMissCounters() []MissCounter { + if m != nil { + return m.MissCounters + } + return nil +} + +func (m *GenesisState) GetAggregateExchangeRatePrevotes() []AggregateExchangeRatePrevote { + if m != nil { + return m.AggregateExchangeRatePrevotes + } + return nil +} + +func (m *GenesisState) GetAggregateExchangeRateVotes() []AggregateExchangeRateVote { + if m != nil { + return m.AggregateExchangeRateVotes + } + return nil +} + +func (m *GenesisState) GetRewards() []Rewards { + if m != nil { + return m.Rewards + } + return nil +} + +// FeederDelegation is the address for where oracle feeder authority are +// delegated to. By default this struct is only used at genesis to feed in +// default feeder addresses. +type FeederDelegation struct { + FeederAddress string `protobuf:"bytes,1,opt,name=feeder_address,json=feederAddress,proto3" json:"feeder_address,omitempty"` + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` +} + +func (m *FeederDelegation) Reset() { *m = FeederDelegation{} } +func (m *FeederDelegation) String() string { return proto.CompactTextString(m) } +func (*FeederDelegation) ProtoMessage() {} +func (*FeederDelegation) Descriptor() ([]byte, []int) { + return fileDescriptor_6285e82cfd585f9b, []int{1} +} +func (m *FeederDelegation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FeederDelegation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FeederDelegation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FeederDelegation) XXX_Merge(src proto.Message) { + xxx_messageInfo_FeederDelegation.Merge(m, src) +} +func (m *FeederDelegation) XXX_Size() int { + return m.Size() +} +func (m *FeederDelegation) XXX_DiscardUnknown() { + xxx_messageInfo_FeederDelegation.DiscardUnknown(m) +} + +var xxx_messageInfo_FeederDelegation proto.InternalMessageInfo + +func (m *FeederDelegation) GetFeederAddress() string { + if m != nil { + return m.FeederAddress + } + return "" +} + +func (m *FeederDelegation) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + +// MissCounter defines an miss counter and validator address pair used in +// oracle module's genesis state +type MissCounter struct { + ValidatorAddress string `protobuf:"bytes,1,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + MissCounter uint64 `protobuf:"varint,2,opt,name=miss_counter,json=missCounter,proto3" json:"miss_counter,omitempty"` +} + +func (m *MissCounter) Reset() { *m = MissCounter{} } +func (m *MissCounter) String() string { return proto.CompactTextString(m) } +func (*MissCounter) ProtoMessage() {} +func (*MissCounter) Descriptor() ([]byte, []int) { + return fileDescriptor_6285e82cfd585f9b, []int{2} +} +func (m *MissCounter) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MissCounter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MissCounter.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MissCounter) XXX_Merge(src proto.Message) { + xxx_messageInfo_MissCounter.Merge(m, src) +} +func (m *MissCounter) XXX_Size() int { + return m.Size() +} +func (m *MissCounter) XXX_DiscardUnknown() { + xxx_messageInfo_MissCounter.DiscardUnknown(m) +} + +var xxx_messageInfo_MissCounter proto.InternalMessageInfo + +func (m *MissCounter) GetValidatorAddress() string { + if m != nil { + return m.ValidatorAddress + } + return "" +} + +func (m *MissCounter) GetMissCounter() uint64 { + if m != nil { + return m.MissCounter + } + return 0 +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "archway.oracle.v1.GenesisState") + proto.RegisterType((*FeederDelegation)(nil), "archway.oracle.v1.FeederDelegation") + proto.RegisterType((*MissCounter)(nil), "archway.oracle.v1.MissCounter") +} + +func init() { proto.RegisterFile("archway/oracle/v1/genesis.proto", fileDescriptor_6285e82cfd585f9b) } + +var fileDescriptor_6285e82cfd585f9b = []byte{ + // 549 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0x5d, 0x8b, 0xd3, 0x4c, + 0x14, 0xc7, 0x9b, 0x7d, 0x7d, 0x76, 0xda, 0x2e, 0xdb, 0xe1, 0xb9, 0x88, 0x85, 0x4d, 0x6b, 0x55, + 0x28, 0xa8, 0x09, 0x5d, 0x41, 0xc1, 0x0b, 0x61, 0xeb, 0x1b, 0x22, 0x42, 0x89, 0x8b, 0x88, 0x20, + 0x65, 0x9a, 0x9c, 0xa6, 0xc1, 0x26, 0x13, 0xe6, 0x4c, 0xdb, 0xdd, 0x1b, 0x3f, 0x83, 0x9f, 0xc3, + 0x4f, 0xb2, 0x97, 0x7b, 0x29, 0x5e, 0x54, 0x69, 0xbf, 0x88, 0x24, 0x33, 0xdd, 0xd6, 0x6d, 0x56, + 0xf6, 0xae, 0x3d, 0xe7, 0xf7, 0x7f, 0x49, 0x39, 0x0d, 0xa9, 0x31, 0xe1, 0x0d, 0x26, 0xec, 0xcc, + 0xe1, 0x82, 0x79, 0x43, 0x70, 0xc6, 0x2d, 0x27, 0x80, 0x18, 0x30, 0x44, 0x3b, 0x11, 0x5c, 0x72, + 0x5a, 0xd1, 0x80, 0xad, 0x00, 0x7b, 0xdc, 0xaa, 0xfe, 0x1f, 0xf0, 0x80, 0x67, 0x5b, 0x27, 0xfd, + 0xa4, 0xc0, 0xaa, 0xb5, 0xee, 0xa4, 0x25, 0x7a, 0xef, 0x71, 0x8c, 0x38, 0x3a, 0x3d, 0x86, 0xe9, + 0xb2, 0x07, 0x92, 0xb5, 0x1c, 0x8f, 0x87, 0xb1, 0xda, 0x37, 0xa6, 0xdb, 0xa4, 0xf4, 0x5a, 0x45, + 0xbf, 0x97, 0x4c, 0x02, 0x7d, 0x42, 0x76, 0x12, 0x26, 0x58, 0x84, 0xa6, 0x51, 0x37, 0x9a, 0xc5, + 0xa3, 0x5b, 0xf6, 0x5a, 0x15, 0xbb, 0x93, 0x01, 0xed, 0xad, 0xf3, 0x69, 0xad, 0xe0, 0x6a, 0x9c, + 0x7e, 0x24, 0xb4, 0x0f, 0xe0, 0x83, 0xe8, 0xfa, 0x30, 0x84, 0x80, 0xc9, 0x90, 0xc7, 0x68, 0x6e, + 0xd4, 0x37, 0x9b, 0xc5, 0xa3, 0x3b, 0x39, 0x26, 0xaf, 0x32, 0xf8, 0xc5, 0x25, 0xab, 0xed, 0x2a, + 0xfd, 0x2b, 0x73, 0xa4, 0x01, 0xd9, 0x87, 0x53, 0x6f, 0xc0, 0xe2, 0x00, 0xba, 0x82, 0x49, 0x40, + 0x73, 0x33, 0x73, 0xbd, 0x9b, 0xe3, 0xfa, 0x52, 0x83, 0x2e, 0x93, 0x70, 0x32, 0x4a, 0x86, 0xd0, + 0xae, 0xa6, 0xb6, 0xdf, 0x7f, 0xd5, 0xe8, 0xda, 0x0a, 0xdd, 0x32, 0xac, 0xcc, 0x90, 0xbe, 0x21, + 0xe5, 0x28, 0x44, 0xec, 0x7a, 0x7c, 0x14, 0x4b, 0x10, 0x68, 0x6e, 0x65, 0x39, 0x56, 0x4e, 0xce, + 0xbb, 0x10, 0xf1, 0xb9, 0xc2, 0x74, 0xf1, 0x52, 0xb4, 0x1c, 0x21, 0xfd, 0x4a, 0xea, 0x2c, 0x08, + 0x44, 0xfa, 0x0c, 0xd0, 0xfd, 0xab, 0x7d, 0x37, 0x11, 0x30, 0xe6, 0xe9, 0x53, 0x6c, 0x67, 0xee, + 0x4e, 0x8e, 0xfb, 0xf1, 0x42, 0xba, 0xda, 0xb9, 0xa3, 0x74, 0x3a, 0xee, 0x90, 0xfd, 0x83, 0x41, + 0x3a, 0x22, 0x87, 0xd7, 0xe5, 0xab, 0xf0, 0x9d, 0x2c, 0xfc, 0xc1, 0x4d, 0xc3, 0x3f, 0x2c, 0x93, + 0xab, 0xec, 0x3a, 0x00, 0xe9, 0x09, 0xd9, 0x4e, 0x58, 0x28, 0xd0, 0xdc, 0xad, 0x6f, 0x36, 0xf7, + 0xda, 0xcf, 0x52, 0xc1, 0xcf, 0x69, 0xed, 0x71, 0x10, 0xca, 0xc1, 0xa8, 0x67, 0x7b, 0x3c, 0x72, + 0x74, 0xe0, 0xc3, 0x18, 0xe4, 0x84, 0x8b, 0x2f, 0x8b, 0xef, 0xce, 0xa9, 0xe3, 0xf1, 0x28, 0xe2, + 0xb1, 0xc3, 0x10, 0x41, 0xda, 0x1d, 0x16, 0x0a, 0x57, 0x99, 0xd1, 0xa7, 0x64, 0x57, 0xc0, 0x84, + 0x09, 0x1f, 0xcd, 0xff, 0xb2, 0xda, 0xd5, 0x9c, 0xda, 0xae, 0x22, 0x74, 0xc9, 0x85, 0xa0, 0xd1, + 0x27, 0x07, 0x57, 0x2f, 0x8d, 0xde, 0x23, 0xfb, 0xfa, 0x54, 0x99, 0xef, 0x0b, 0x40, 0x75, 0xeb, + 0x7b, 0x6e, 0x59, 0x4d, 0x8f, 0xd5, 0x90, 0xde, 0x27, 0x95, 0x31, 0x1b, 0x86, 0x3e, 0x93, 0x7c, + 0x49, 0x6e, 0x64, 0xe4, 0xc1, 0xe5, 0x42, 0xc3, 0x8d, 0xcf, 0xa4, 0xb8, 0x72, 0x13, 0xf9, 0x5a, + 0x23, 0x5f, 0x4b, 0x6f, 0x93, 0xd2, 0xea, 0xdd, 0x65, 0x19, 0x5b, 0x6e, 0x71, 0xe5, 0xa0, 0xda, + 0x6f, 0xcf, 0x67, 0x96, 0x71, 0x31, 0xb3, 0x8c, 0xdf, 0x33, 0xcb, 0xf8, 0x36, 0xb7, 0x0a, 0x17, + 0x73, 0xab, 0xf0, 0x63, 0x6e, 0x15, 0x3e, 0xb5, 0x6e, 0xf4, 0xdb, 0xea, 0xd7, 0x83, 0x3c, 0x4b, + 0x00, 0x7b, 0x3b, 0xd9, 0x7f, 0xff, 0xd1, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x17, 0x99, 0x22, + 0xf1, 0x87, 0x04, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Rewards) > 0 { + for iNdEx := len(m.Rewards) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Rewards[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + if len(m.Pairs) > 0 { + for iNdEx := len(m.Pairs) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.Pairs[iNdEx].Size() + i -= size + if _, err := m.Pairs[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } + if len(m.AggregateExchangeRateVotes) > 0 { + for iNdEx := len(m.AggregateExchangeRateVotes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AggregateExchangeRateVotes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if len(m.AggregateExchangeRatePrevotes) > 0 { + for iNdEx := len(m.AggregateExchangeRatePrevotes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AggregateExchangeRatePrevotes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if len(m.MissCounters) > 0 { + for iNdEx := len(m.MissCounters) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MissCounters[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.ExchangeRates) > 0 { + for iNdEx := len(m.ExchangeRates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ExchangeRates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.FeederDelegations) > 0 { + for iNdEx := len(m.FeederDelegations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FeederDelegations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *FeederDelegation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FeederDelegation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeederDelegation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0x12 + } + if len(m.FeederAddress) > 0 { + i -= len(m.FeederAddress) + copy(dAtA[i:], m.FeederAddress) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.FeederAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MissCounter) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MissCounter) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MissCounter) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MissCounter != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.MissCounter)) + i-- + dAtA[i] = 0x10 + } + if len(m.ValidatorAddress) > 0 { + i -= len(m.ValidatorAddress) + copy(dAtA[i:], m.ValidatorAddress) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ValidatorAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.FeederDelegations) > 0 { + for _, e := range m.FeederDelegations { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.ExchangeRates) > 0 { + for _, e := range m.ExchangeRates { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.MissCounters) > 0 { + for _, e := range m.MissCounters { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.AggregateExchangeRatePrevotes) > 0 { + for _, e := range m.AggregateExchangeRatePrevotes { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.AggregateExchangeRateVotes) > 0 { + for _, e := range m.AggregateExchangeRateVotes { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Pairs) > 0 { + for _, e := range m.Pairs { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Rewards) > 0 { + for _, e := range m.Rewards { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *FeederDelegation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.FeederAddress) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func (m *MissCounter) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValidatorAddress) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if m.MissCounter != 0 { + n += 1 + sovGenesis(uint64(m.MissCounter)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FeederDelegations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FeederDelegations = append(m.FeederDelegations, FeederDelegation{}) + if err := m.FeederDelegations[len(m.FeederDelegations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExchangeRates = append(m.ExchangeRates, ExchangeRateTuple{}) + if err := m.ExchangeRates[len(m.ExchangeRates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MissCounters", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MissCounters = append(m.MissCounters, MissCounter{}) + if err := m.MissCounters[len(m.MissCounters)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregateExchangeRatePrevotes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AggregateExchangeRatePrevotes = append(m.AggregateExchangeRatePrevotes, AggregateExchangeRatePrevote{}) + if err := m.AggregateExchangeRatePrevotes[len(m.AggregateExchangeRatePrevotes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregateExchangeRateVotes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AggregateExchangeRateVotes = append(m.AggregateExchangeRateVotes, AggregateExchangeRateVote{}) + if err := m.AggregateExchangeRateVotes[len(m.AggregateExchangeRateVotes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pairs", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_archway_network_archway_x_common_asset.Pair + m.Pairs = append(m.Pairs, v) + if err := m.Pairs[len(m.Pairs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rewards", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Rewards = append(m.Rewards, Rewards{}) + if err := m.Rewards[len(m.Rewards)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FeederDelegation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FeederDelegation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FeederDelegation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FeederAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FeederAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MissCounter) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MissCounter: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MissCounter: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MissCounter", wireType) + } + m.MissCounter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MissCounter |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/genesis_test.go b/x/oracle/types/genesis_test.go new file mode 100644 index 00000000..f83d8e66 --- /dev/null +++ b/x/oracle/types/genesis_test.go @@ -0,0 +1,28 @@ +package types_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/app" + "github.com/archway-network/archway/x/oracle/types" +) + +func TestGenesisValidation(t *testing.T) { + genState := types.DefaultGenesisState() + require.NoError(t, types.ValidateGenesis(genState)) + + genState.Params.VotePeriod = 0 + require.Error(t, types.ValidateGenesis(genState)) +} + +func TestGetGenesisStateFromAppState(t *testing.T) { + cdc := app.MakeEncodingConfig().Codec + appState := make(map[string]json.RawMessage) + + defaultGenesisState := types.DefaultGenesisState() + appState[types.ModuleName] = cdc.MustMarshalJSON(defaultGenesisState) + require.Equal(t, *defaultGenesisState, *types.GetGenesisStateFromAppState(cdc, appState)) +} diff --git a/x/oracle/types/hash.go b/x/oracle/types/hash.go new file mode 100644 index 00000000..b9d816ec --- /dev/null +++ b/x/oracle/types/hash.go @@ -0,0 +1,121 @@ +package types + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + + "gopkg.in/yaml.v2" + + "github.com/cometbft/cometbft/crypto/tmhash" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ yaml.Marshaler = AggregateVoteHash{} + +// AggregateVoteHash is hash value to hide vote exchange rates +// which is formatted as hex string in SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") +type AggregateVoteHash []byte + +// GetAggregateVoteHash computes hash value of ExchangeRateVote +// to avoid redundant DecCoins stringify operation, use string argument +// TODO(mercilex): use ExchangeRateTuples +func GetAggregateVoteHash(salt string, exchangeRatesStr string, voter sdk.ValAddress) AggregateVoteHash { + hash := tmhash.NewTruncated() + sourceStr := fmt.Sprintf("%s:%s:%s", salt, exchangeRatesStr, voter.String()) + _, err := hash.Write([]byte(sourceStr)) + if err != nil { + panic(err) + } + bz := hash.Sum(nil) + return bz +} + +// AggregateVoteHashFromHexString convert hex string to AggregateVoteHash +func AggregateVoteHashFromHexString(s string) (AggregateVoteHash, error) { + h, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + + return h, nil +} + +// String implements fmt.Stringer interface +func (h AggregateVoteHash) String() string { + return hex.EncodeToString(h) +} + +// Equal does bytes equal check +func (h AggregateVoteHash) Equal(h2 AggregateVoteHash) bool { + return bytes.Equal(h, h2) +} + +// Empty check the name hash has zero length +func (h AggregateVoteHash) Empty() bool { + return len(h) == 0 +} + +// Bytes returns the raw address bytes. +func (h AggregateVoteHash) Bytes() []byte { + return h +} + +// Size returns the raw address bytes. +func (h AggregateVoteHash) Size() int { + return len(h) +} + +// Format implements the fmt.Formatter interface. +func (h AggregateVoteHash) Format(s fmt.State, verb rune) { + switch verb { + case 's': + _, _ = s.Write([]byte(h.String())) + case 'p': + _, _ = s.Write([]byte(fmt.Sprintf("%p", h))) + default: + _, _ = s.Write([]byte(fmt.Sprintf("%X", []byte(h)))) + } +} + +// Marshal returns the raw address bytes. It is needed for protobuf +// compatibility. +func (h AggregateVoteHash) Marshal() ([]byte, error) { + return h, nil +} + +// Unmarshal sets the address to the given data. It is needed for protobuf +// compatibility. +func (h *AggregateVoteHash) Unmarshal(data []byte) error { + *h = data + return nil +} + +// MarshalJSON marshals to JSON using Bech32. +func (h AggregateVoteHash) MarshalJSON() ([]byte, error) { + return json.Marshal(h.String()) +} + +// MarshalYAML marshals to YAML using Bech32. +func (h AggregateVoteHash) MarshalYAML() (interface{}, error) { + return h.String(), nil +} + +// UnmarshalJSON unmarshals from JSON assuming Bech32 encoding. +func (h *AggregateVoteHash) UnmarshalJSON(data []byte) error { + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + h2, err := AggregateVoteHashFromHexString(s) + if err != nil { + return err + } + + *h = h2 + return nil +} diff --git a/x/oracle/types/hash_test.go b/x/oracle/types/hash_test.go new file mode 100644 index 00000000..3b80f824 --- /dev/null +++ b/x/oracle/types/hash_test.go @@ -0,0 +1,42 @@ +package types_test + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" + + "github.com/archway-network/archway/x/oracle/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestAggregateVoteHash(t *testing.T) { + addrs := []sdk.AccAddress{ + sdk.AccAddress([]byte("addr1_______________")), + } + + aggregateVoteHash := types.GetAggregateVoteHash("salt", "(100,nibi:usd)|(200000,btc:usd)", sdk.ValAddress(addrs[0])) + hexStr := hex.EncodeToString(aggregateVoteHash) + aggregateVoteHashRes, err := types.AggregateVoteHashFromHexString(hexStr) + require.NoError(t, err) + require.Equal(t, aggregateVoteHash, aggregateVoteHashRes) + require.True(t, aggregateVoteHash.Equal(aggregateVoteHash)) + require.True(t, types.AggregateVoteHash([]byte{}).Empty()) + + got, _ := yaml.Marshal(&aggregateVoteHash) + require.Equal(t, aggregateVoteHash.String()+"\n", string(got)) + + res := types.AggregateVoteHash{} + testMarshal(t, &aggregateVoteHash, &res, aggregateVoteHash.MarshalJSON, (&res).UnmarshalJSON) + testMarshal(t, &aggregateVoteHash, &res, aggregateVoteHash.Marshal, (&res).Unmarshal) +} + +func testMarshal(t *testing.T, original interface{}, res interface{}, marshal func() ([]byte, error), unmarshal func([]byte) error) { + bz, err := marshal() + require.Nil(t, err) + err = unmarshal(bz) + require.Nil(t, err) + require.Equal(t, original, res) +} diff --git a/x/oracle/types/keys.go b/x/oracle/types/keys.go new file mode 100644 index 00000000..f27b45a8 --- /dev/null +++ b/x/oracle/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName is the name of the oracle module + ModuleName = "oracle" + + // StoreKey is the string store representation + StoreKey = ModuleName + + // RouterKey is the msg router key for the oracle module + RouterKey = ModuleName + + // QuerierRoute is the query router key for the oracle module + QuerierRoute = ModuleName +) diff --git a/x/oracle/types/msgs.go b/x/oracle/types/msgs.go new file mode 100644 index 00000000..05275b81 --- /dev/null +++ b/x/oracle/types/msgs.go @@ -0,0 +1,197 @@ +package types + +import ( + "github.com/cometbft/cometbft/crypto/tmhash" + "github.com/cosmos/cosmos-sdk/types/errors" + + sdkerrors "cosmossdk.io/errors" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ensure Msg interface compliance at compile time +var ( + _ sdk.Msg = &MsgDelegateFeedConsent{} + _ sdk.Msg = &MsgAggregateExchangeRatePrevote{} + _ sdk.Msg = &MsgAggregateExchangeRateVote{} +) + +// oracle message types +const ( + TypeMsgDelegateFeedConsent = "delegate_feeder" + TypeMsgAggregateExchangeRatePrevote = "aggregate_exchange_rate_prevote" + TypeMsgAggregateExchangeRateVote = "aggregate_exchange_rate_vote" +) + +//------------------------------------------------- +//------------------------------------------------- + +// NewMsgAggregateExchangeRatePrevote returns MsgAggregateExchangeRatePrevote instance +func NewMsgAggregateExchangeRatePrevote(hash AggregateVoteHash, feeder sdk.AccAddress, validator sdk.ValAddress) *MsgAggregateExchangeRatePrevote { + return &MsgAggregateExchangeRatePrevote{ + Hash: hash.String(), + Feeder: feeder.String(), + Validator: validator.String(), + } +} + +// Route implements sdk.Msg +func (msg MsgAggregateExchangeRatePrevote) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgAggregateExchangeRatePrevote) Type() string { return TypeMsgAggregateExchangeRatePrevote } + +// GetSignBytes implements sdk.Msg +func (msg MsgAggregateExchangeRatePrevote) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgAggregateExchangeRatePrevote) GetSigners() []sdk.AccAddress { + feeder, err := sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + panic(err) + } + + return []sdk.AccAddress{feeder} +} + +// ValidateBasic Implements sdk.Msg +func (msg MsgAggregateExchangeRatePrevote) ValidateBasic() error { + _, err := AggregateVoteHashFromHexString(msg.Hash) + if err != nil { + return sdkerrors.Wrapf(ErrInvalidHash, "Invalid vote hash (%s)", err) + } + + // HEX encoding doubles the hash length + if len(msg.Hash) != tmhash.TruncatedSize*2 { + return ErrInvalidHashLength + } + + _, err = sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid feeder address (%s)", err) + } + + _, err = sdk.ValAddressFromBech32(msg.Validator) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid operator address (%s)", err) + } + + return nil +} + +// NewMsgAggregateExchangeRateVote returns MsgAggregateExchangeRateVote instance +// TODO(mercilex): accept ExchangeRatesTuples +func NewMsgAggregateExchangeRateVote(salt string, exchangeRates string, feeder sdk.AccAddress, validator sdk.ValAddress) *MsgAggregateExchangeRateVote { + return &MsgAggregateExchangeRateVote{ + Salt: salt, + ExchangeRates: exchangeRates, + Feeder: feeder.String(), + Validator: validator.String(), + } +} + +// Route implements sdk.Msg +func (msg MsgAggregateExchangeRateVote) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgAggregateExchangeRateVote) Type() string { return TypeMsgAggregateExchangeRateVote } + +// GetSignBytes implements sdk.Msg +func (msg MsgAggregateExchangeRateVote) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgAggregateExchangeRateVote) GetSigners() []sdk.AccAddress { + feeder, err := sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + panic(err) + } + + return []sdk.AccAddress{feeder} +} + +// ValidateBasic implements sdk.Msg +func (msg MsgAggregateExchangeRateVote) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Feeder) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid feeder address (%s)", err) + } + + _, err = sdk.ValAddressFromBech32(msg.Validator) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid operator address (%s)", err) + } + + if l := len(msg.ExchangeRates); l == 0 { + return sdkerrors.Wrap(errors.ErrUnknownRequest, "must provide at least one oracle exchange rate") + } else if l > 4096 { + return sdkerrors.Wrap(errors.ErrInvalidRequest, "exchange rates string can not exceed 4096 characters") + } + + exchangeRates, err := ParseExchangeRateTuples(msg.ExchangeRates) + if err != nil { + return sdkerrors.Wrap(errors.ErrInvalidCoins, "failed to parse exchange rates string cause: "+err.Error()) + } + + for _, exchangeRate := range exchangeRates { + // Check overflow bit length + if exchangeRate.ExchangeRate.BigInt().BitLen() > 255+math.LegacyDecimalPrecisionBits { + return sdkerrors.Wrap(ErrInvalidExchangeRate, "overflow") + } + } + + if len(msg.Salt) > 4 || len(msg.Salt) < 1 { + return sdkerrors.Wrap(ErrInvalidSaltLength, "salt length must be [1, 4]") + } + + return nil +} + +// ------------------------ MsgDelegateFeedConsent ------------------------ + +// NewMsgDelegateFeedConsent creates a MsgDelegateFeedConsent instance +func NewMsgDelegateFeedConsent(operatorAddress sdk.ValAddress, feederAddress sdk.AccAddress) *MsgDelegateFeedConsent { + return &MsgDelegateFeedConsent{ + Operator: operatorAddress.String(), + Delegate: feederAddress.String(), + } +} + +// Route implements sdk.Msg +func (msg MsgDelegateFeedConsent) Route() string { return RouterKey } + +// Type implements sdk.Msg +func (msg MsgDelegateFeedConsent) Type() string { return TypeMsgDelegateFeedConsent } + +// GetSignBytes implements sdk.Msg +func (msg MsgDelegateFeedConsent) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&msg)) +} + +// GetSigners implements sdk.Msg +func (msg MsgDelegateFeedConsent) GetSigners() []sdk.AccAddress { + operator, err := sdk.ValAddressFromBech32(msg.Operator) + if err != nil { + panic(err) + } + + return []sdk.AccAddress{sdk.AccAddress(operator)} +} + +// ValidateBasic implements sdk.Msg +func (msg MsgDelegateFeedConsent) ValidateBasic() error { + _, err := sdk.ValAddressFromBech32(msg.Operator) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid operator address (%s)", err) + } + + _, err = sdk.AccAddressFromBech32(msg.Delegate) + if err != nil { + return sdkerrors.Wrapf(errors.ErrInvalidAddress, "Invalid delegate address (%s)", err) + } + + return nil +} diff --git a/x/oracle/types/msgs_test.go b/x/oracle/types/msgs_test.go new file mode 100644 index 00000000..0502042d --- /dev/null +++ b/x/oracle/types/msgs_test.go @@ -0,0 +1,125 @@ +package types_test + +import ( + "testing" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/denoms" + "github.com/archway-network/archway/x/oracle/types" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestMsgFeederDelegation(t *testing.T) { + addrs := []sdk.AccAddress{ + sdk.AccAddress([]byte("addr1_______________")), + sdk.AccAddress([]byte("addr2_______________")), + } + + tests := []struct { + delegator sdk.ValAddress + delegate sdk.AccAddress + expectPass bool + }{ + {sdk.ValAddress(addrs[0]), addrs[1], true}, + {sdk.ValAddress{}, addrs[1], false}, + {sdk.ValAddress(addrs[0]), sdk.AccAddress{}, false}, + {nil, nil, false}, + } + + for i, tc := range tests { + msg := types.NewMsgDelegateFeedConsent(tc.delegator, tc.delegate) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +func TestMsgAggregateExchangeRatePrevote(t *testing.T) { + addrs := []sdk.AccAddress{ + sdk.AccAddress([]byte("addr1_______________")), + } + + exchangeRates := math.LegacyDecCoins{sdk.NewDecCoinFromDec(denoms.USDC, math.LegacyOneDec()), sdk.NewDecCoinFromDec(denoms.NUSD, math.LegacyNewDecWithPrec(32121, 1))} + bz := types.GetAggregateVoteHash("1", exchangeRates.String(), sdk.ValAddress(addrs[0])) + + tests := []struct { + hash types.AggregateVoteHash + exchangeRates math.LegacyDecCoins + voter sdk.AccAddress + expectPass bool + }{ + {bz, exchangeRates, addrs[0], true}, + {bz[1:], exchangeRates, addrs[0], false}, + {bz, exchangeRates, sdk.AccAddress{}, false}, + {types.AggregateVoteHash{}, exchangeRates, addrs[0], false}, + } + + for i, tc := range tests { + msg := types.NewMsgAggregateExchangeRatePrevote(tc.hash, tc.voter, sdk.ValAddress(tc.voter)) + if tc.expectPass { + require.NoError(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.Error(t, msg.ValidateBasic(), "test: %v", i) + } + } +} + +func TestMsgAggregateExchangeRateVote(t *testing.T) { + addrs := []sdk.AccAddress{ + sdk.AccAddress("addr1_______________"), + } + + exchangeRates := types.ExchangeRateTuples{ + { + Pair: "FOO:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("1.0"), + }, + { + Pair: "BAR:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("1232.132"), + }, + } + + abstainExchangeRates := types.ExchangeRateTuples{ + { + Pair: "FOO:USD", + ExchangeRate: math.LegacyZeroDec(), + }, + { + Pair: "BAR:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("1232.132"), + }, + } + + tests := []struct { + voter sdk.AccAddress + validator sdk.ValAddress + salt string + exchangeRates types.ExchangeRateTuples + expectPass bool + }{ + {addrs[0], sdk.ValAddress(addrs[0]), "123", exchangeRates, true}, + {addrs[0], sdk.ValAddress(addrs[0]), "123", abstainExchangeRates, true}, + {sdk.AccAddress{}, sdk.ValAddress(addrs[0]), "123", exchangeRates, false}, + {addrs[0], sdk.ValAddress(addrs[0]), "123", types.ExchangeRateTuples{}, false}, + {addrs[0], sdk.ValAddress{}, "123", abstainExchangeRates, false}, + {addrs[0], sdk.ValAddress(addrs[0]), "", abstainExchangeRates, false}, + } + + for i, tc := range tests { + exchangeRates, err := tc.exchangeRates.ToString() + require.NoError(t, err) + msg := types.NewMsgAggregateExchangeRateVote(tc.salt, exchangeRates, tc.voter, tc.validator) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", i) + } + } +} diff --git a/x/oracle/types/oracle.pb.go b/x/oracle/types/oracle.pb.go new file mode 100644 index 00000000..4e31d5a4 --- /dev/null +++ b/x/oracle/types/oracle.pb.go @@ -0,0 +1,2021 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/oracle.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + github_com_archway_network_archway_x_common_asset "github.com/archway-network/archway/x/common/asset" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/durationpb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the module parameters for the x/oracle module. +type Params struct { + // VotePeriod defines the number of blocks during which voting takes place. + VotePeriod uint64 `protobuf:"varint,1,opt,name=vote_period,json=votePeriod,proto3" json:"vote_period,omitempty" yaml:"vote_period"` + // VoteThreshold specifies the minimum proportion of votes that must be + // received for a ballot to pass. + VoteThreshold cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=vote_threshold,json=voteThreshold,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"vote_threshold" yaml:"vote_threshold"` + // RewardBand defines a maxium divergence that a price vote can have from the + // weighted median in the ballot. If a vote lies within the valid range + // defined by: + // μ := weightedMedian, + // validRange := μ ± (μ * rewardBand / 2), + // then rewards are added to the validator performance. + // Note that if the reward band is smaller than 1 standard + // deviation, the band is taken to be 1 standard deviation.a price + RewardBand cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=reward_band,json=rewardBand,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"reward_band" yaml:"reward_band"` + // The set of whitelisted markets, or asset pairs, for the module. + // Ex. '["unibi:uusd","ubtc:uusd"]' + Whitelist []github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,4,rep,name=whitelist,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"whitelist,omitempty" yaml:"whitelist"` + // SlashFraction returns the proportion of an oracle's stake that gets + // slashed in the event of slashing. `SlashFraction` specifies the exact + // penalty for failing a voting period. + SlashFraction cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=slash_fraction,json=slashFraction,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slash_fraction" yaml:"slash_fraction"` + // SlashWindow returns the number of voting periods that specify a + // "slash window". After each slash window, all oracles that have missed more + // than the penalty threshold are slashed. Missing the penalty threshold is + // synonymous with submitting fewer valid votes than `MinValidPerWindow`. + SlashWindow uint64 `protobuf:"varint,6,opt,name=slash_window,json=slashWindow,proto3" json:"slash_window,omitempty" yaml:"slash_window"` + MinValidPerWindow cosmossdk_io_math.LegacyDec `protobuf:"bytes,7,opt,name=min_valid_per_window,json=minValidPerWindow,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_valid_per_window" yaml:"min_valid_per_window"` + // Amount of time to look back for TWAP calculations. + // Ex: "900.000000069s" corresponds to 900 seconds and 69 nanoseconds in JSON. + TwapLookbackWindow time.Duration `protobuf:"bytes,8,opt,name=twap_lookback_window,json=twapLookbackWindow,proto3,stdduration" json:"twap_lookback_window,omitempty" yaml:"twap_lookback_window"` + // The minimum number of voters (i.e. oracle validators) per pair for it to be + // considered a passing ballot. Recommended at least 4. + MinVoters uint64 `protobuf:"varint,9,opt,name=min_voters,json=minVoters,proto3" json:"min_voters,omitempty" yaml:"min_voters"` + // The validator fee ratio that is given to validators every epoch. + ValidatorFeeRatio cosmossdk_io_math.LegacyDec `protobuf:"bytes,10,opt,name=validator_fee_ratio,json=validatorFeeRatio,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"validator_fee_ratio" yaml:"validator_fee_ratio"` + ExpirationBlocks uint64 `protobuf:"varint,11,opt,name=expiration_blocks,json=expirationBlocks,proto3" json:"expiration_blocks,omitempty" yaml:"expiration_blocks"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetVotePeriod() uint64 { + if m != nil { + return m.VotePeriod + } + return 0 +} + +func (m *Params) GetSlashWindow() uint64 { + if m != nil { + return m.SlashWindow + } + return 0 +} + +func (m *Params) GetTwapLookbackWindow() time.Duration { + if m != nil { + return m.TwapLookbackWindow + } + return 0 +} + +func (m *Params) GetMinVoters() uint64 { + if m != nil { + return m.MinVoters + } + return 0 +} + +func (m *Params) GetExpirationBlocks() uint64 { + if m != nil { + return m.ExpirationBlocks + } + return 0 +} + +// Struct for aggregate prevoting on the ExchangeRateVote. +// The purpose of aggregate prevote is to hide vote exchange rates with hash +// which is formatted as hex string in +// SHA256("{salt}:({pair},{exchange_rate})|...|({pair},{exchange_rate}):{voter}") +type AggregateExchangeRatePrevote struct { + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty" yaml:"hash"` + Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty" yaml:"voter"` + SubmitBlock uint64 `protobuf:"varint,3,opt,name=submit_block,json=submitBlock,proto3" json:"submit_block,omitempty" yaml:"submit_block"` +} + +func (m *AggregateExchangeRatePrevote) Reset() { *m = AggregateExchangeRatePrevote{} } +func (m *AggregateExchangeRatePrevote) String() string { return proto.CompactTextString(m) } +func (*AggregateExchangeRatePrevote) ProtoMessage() {} +func (*AggregateExchangeRatePrevote) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{1} +} +func (m *AggregateExchangeRatePrevote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AggregateExchangeRatePrevote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AggregateExchangeRatePrevote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AggregateExchangeRatePrevote) XXX_Merge(src proto.Message) { + xxx_messageInfo_AggregateExchangeRatePrevote.Merge(m, src) +} +func (m *AggregateExchangeRatePrevote) XXX_Size() int { + return m.Size() +} +func (m *AggregateExchangeRatePrevote) XXX_DiscardUnknown() { + xxx_messageInfo_AggregateExchangeRatePrevote.DiscardUnknown(m) +} + +var xxx_messageInfo_AggregateExchangeRatePrevote proto.InternalMessageInfo + +// MsgAggregateExchangeRateVote - struct for voting on +// the exchange rates different assets. +type AggregateExchangeRateVote struct { + ExchangeRateTuples ExchangeRateTuples `protobuf:"bytes,1,rep,name=exchange_rate_tuples,json=exchangeRateTuples,proto3,castrepeated=ExchangeRateTuples" json:"exchange_rate_tuples" yaml:"exchange_rate_tuples"` + Voter string `protobuf:"bytes,2,opt,name=voter,proto3" json:"voter,omitempty" yaml:"voter"` +} + +func (m *AggregateExchangeRateVote) Reset() { *m = AggregateExchangeRateVote{} } +func (m *AggregateExchangeRateVote) String() string { return proto.CompactTextString(m) } +func (*AggregateExchangeRateVote) ProtoMessage() {} +func (*AggregateExchangeRateVote) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{2} +} +func (m *AggregateExchangeRateVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AggregateExchangeRateVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AggregateExchangeRateVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AggregateExchangeRateVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_AggregateExchangeRateVote.Merge(m, src) +} +func (m *AggregateExchangeRateVote) XXX_Size() int { + return m.Size() +} +func (m *AggregateExchangeRateVote) XXX_DiscardUnknown() { + xxx_messageInfo_AggregateExchangeRateVote.DiscardUnknown(m) +} + +var xxx_messageInfo_AggregateExchangeRateVote proto.InternalMessageInfo + +// ExchangeRateTuple - struct to store interpreted exchange rates data to store +type ExchangeRateTuple struct { + Pair github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,1,opt,name=pair,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"pair" yaml:"pair"` + ExchangeRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=exchange_rate,json=exchangeRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"exchange_rate" yaml:"exchange_rate"` +} + +func (m *ExchangeRateTuple) Reset() { *m = ExchangeRateTuple{} } +func (m *ExchangeRateTuple) String() string { return proto.CompactTextString(m) } +func (*ExchangeRateTuple) ProtoMessage() {} +func (*ExchangeRateTuple) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{3} +} +func (m *ExchangeRateTuple) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExchangeRateTuple) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExchangeRateTuple.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExchangeRateTuple) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExchangeRateTuple.Merge(m, src) +} +func (m *ExchangeRateTuple) XXX_Size() int { + return m.Size() +} +func (m *ExchangeRateTuple) XXX_DiscardUnknown() { + xxx_messageInfo_ExchangeRateTuple.DiscardUnknown(m) +} + +var xxx_messageInfo_ExchangeRateTuple proto.InternalMessageInfo + +type DatedPrice struct { + ExchangeRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=exchange_rate,json=exchangeRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"exchange_rate" yaml:"exchange_rate"` + CreatedBlock uint64 `protobuf:"varint,2,opt,name=created_block,json=createdBlock,proto3" json:"created_block,omitempty" yaml:"created_block"` +} + +func (m *DatedPrice) Reset() { *m = DatedPrice{} } +func (m *DatedPrice) String() string { return proto.CompactTextString(m) } +func (*DatedPrice) ProtoMessage() {} +func (*DatedPrice) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{4} +} +func (m *DatedPrice) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DatedPrice) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DatedPrice.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DatedPrice) XXX_Merge(src proto.Message) { + xxx_messageInfo_DatedPrice.Merge(m, src) +} +func (m *DatedPrice) XXX_Size() int { + return m.Size() +} +func (m *DatedPrice) XXX_DiscardUnknown() { + xxx_messageInfo_DatedPrice.DiscardUnknown(m) +} + +var xxx_messageInfo_DatedPrice proto.InternalMessageInfo + +func (m *DatedPrice) GetCreatedBlock() uint64 { + if m != nil { + return m.CreatedBlock + } + return 0 +} + +// Rewards defines a credit object towards validators +// which provide prices faithfully for different pairs. +type Rewards struct { + // id uniquely identifies the rewards instance of the pair + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + // vote_periods defines the vote periods left in which rewards will be + // distributed. + VotePeriods uint64 `protobuf:"varint,2,opt,name=vote_periods,json=votePeriods,proto3" json:"vote_periods,omitempty"` + // Coins defines the amount of coins to distribute in a single vote period. + Coins []types.Coin `protobuf:"bytes,3,rep,name=coins,proto3" json:"coins"` +} + +func (m *Rewards) Reset() { *m = Rewards{} } +func (m *Rewards) String() string { return proto.CompactTextString(m) } +func (*Rewards) ProtoMessage() {} +func (*Rewards) Descriptor() ([]byte, []int) { + return fileDescriptor_ceb632d7c2facf1a, []int{5} +} +func (m *Rewards) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Rewards) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Rewards.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Rewards) XXX_Merge(src proto.Message) { + xxx_messageInfo_Rewards.Merge(m, src) +} +func (m *Rewards) XXX_Size() int { + return m.Size() +} +func (m *Rewards) XXX_DiscardUnknown() { + xxx_messageInfo_Rewards.DiscardUnknown(m) +} + +var xxx_messageInfo_Rewards proto.InternalMessageInfo + +func (m *Rewards) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *Rewards) GetVotePeriods() uint64 { + if m != nil { + return m.VotePeriods + } + return 0 +} + +func (m *Rewards) GetCoins() []types.Coin { + if m != nil { + return m.Coins + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "archway.oracle.v1.Params") + proto.RegisterType((*AggregateExchangeRatePrevote)(nil), "archway.oracle.v1.AggregateExchangeRatePrevote") + proto.RegisterType((*AggregateExchangeRateVote)(nil), "archway.oracle.v1.AggregateExchangeRateVote") + proto.RegisterType((*ExchangeRateTuple)(nil), "archway.oracle.v1.ExchangeRateTuple") + proto.RegisterType((*DatedPrice)(nil), "archway.oracle.v1.DatedPrice") + proto.RegisterType((*Rewards)(nil), "archway.oracle.v1.Rewards") +} + +func init() { proto.RegisterFile("archway/oracle/v1/oracle.proto", fileDescriptor_ceb632d7c2facf1a) } + +var fileDescriptor_ceb632d7c2facf1a = []byte{ + // 971 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4d, 0x6f, 0x1b, 0x45, + 0x18, 0xf6, 0x26, 0x4e, 0x1a, 0x8f, 0x93, 0x12, 0x4f, 0x53, 0xd8, 0xa4, 0x95, 0x37, 0x0c, 0x08, + 0xe5, 0x00, 0xbb, 0x4a, 0xf9, 0x12, 0x01, 0x0e, 0x35, 0xa1, 0x52, 0x45, 0x41, 0xd6, 0xa8, 0x02, + 0xa9, 0x17, 0x33, 0xde, 0x9d, 0xec, 0x8e, 0xb2, 0xbb, 0x63, 0x66, 0x26, 0x71, 0xfc, 0x0f, 0x38, + 0xc2, 0x05, 0xc1, 0x2d, 0x67, 0x24, 0x0e, 0xfc, 0x8b, 0x1e, 0x7b, 0x44, 0x3d, 0x6c, 0x21, 0xb9, + 0x54, 0x1c, 0xfd, 0x0b, 0xd0, 0xcc, 0x8e, 0xe3, 0x8d, 0xec, 0x83, 0x55, 0x71, 0xf3, 0xfb, 0x3e, + 0x33, 0xcf, 0xfb, 0xf5, 0xec, 0xbc, 0x06, 0x6d, 0x22, 0xc2, 0x64, 0x48, 0x46, 0x01, 0x17, 0x24, + 0x4c, 0x69, 0x70, 0xba, 0x6f, 0x7f, 0xf9, 0x03, 0xc1, 0x15, 0x87, 0x2d, 0x8b, 0xfb, 0xd6, 0x7b, + 0xba, 0xbf, 0xb3, 0x15, 0xf3, 0x98, 0x1b, 0x34, 0xd0, 0xbf, 0xca, 0x83, 0x3b, 0xed, 0x98, 0xf3, + 0x38, 0xa5, 0x81, 0xb1, 0xfa, 0x27, 0x47, 0x41, 0x74, 0x22, 0x88, 0x62, 0x3c, 0x9f, 0xe0, 0x21, + 0x97, 0x19, 0x97, 0x41, 0x9f, 0x48, 0x1d, 0xa5, 0x4f, 0x15, 0xd9, 0x0f, 0x42, 0xce, 0x2c, 0x8e, + 0x7e, 0x5b, 0x03, 0xab, 0x5d, 0x22, 0x48, 0x26, 0xe1, 0xc7, 0xa0, 0x79, 0xca, 0x15, 0xed, 0x0d, + 0xa8, 0x60, 0x3c, 0x72, 0x9d, 0x5d, 0x67, 0xaf, 0xde, 0x79, 0x7d, 0x5c, 0x78, 0x70, 0x44, 0xb2, + 0xf4, 0x00, 0x55, 0x40, 0x84, 0x81, 0xb6, 0xba, 0xc6, 0x80, 0x21, 0xb8, 0x69, 0x30, 0x95, 0x08, + 0x2a, 0x13, 0x9e, 0x46, 0xee, 0xd2, 0xae, 0xb3, 0xd7, 0xe8, 0x7c, 0xf6, 0xb4, 0xf0, 0x6a, 0xcf, + 0x0b, 0xef, 0x4e, 0x99, 0x83, 0x8c, 0x8e, 0x7d, 0xc6, 0x83, 0x8c, 0xa8, 0xc4, 0x7f, 0x44, 0x63, + 0x12, 0x8e, 0x0e, 0x69, 0x38, 0x2e, 0xbc, 0xdb, 0x15, 0xfa, 0x2b, 0x0a, 0x84, 0x37, 0xb4, 0xe3, + 0xf1, 0xc4, 0x86, 0x4f, 0x40, 0x53, 0xd0, 0x21, 0x11, 0x51, 0xaf, 0x4f, 0xf2, 0xc8, 0x5d, 0x36, + 0x11, 0x3e, 0x59, 0x2c, 0x82, 0x2d, 0xa0, 0x72, 0x1f, 0x61, 0x50, 0x5a, 0x1d, 0x92, 0x47, 0x30, + 0x05, 0x8d, 0x61, 0xc2, 0x14, 0x4d, 0x99, 0x54, 0x6e, 0x7d, 0x77, 0x79, 0xaf, 0xd1, 0xf9, 0xe6, + 0x79, 0xe1, 0x7d, 0x14, 0x33, 0x95, 0x9c, 0xf4, 0xfd, 0x90, 0x67, 0x81, 0x9d, 0xc7, 0x7b, 0x39, + 0x55, 0x43, 0x2e, 0x8e, 0x27, 0x76, 0x70, 0x16, 0x84, 0x3c, 0xcb, 0x78, 0x1e, 0x10, 0x29, 0xa9, + 0xf2, 0xbb, 0x84, 0x89, 0x71, 0xe1, 0x6d, 0x96, 0x01, 0xaf, 0x48, 0x11, 0x9e, 0x06, 0xd0, 0xed, + 0x92, 0x29, 0x91, 0x49, 0xef, 0x48, 0x90, 0x50, 0x8f, 0xca, 0x5d, 0x79, 0x85, 0x76, 0x5d, 0xa7, + 0x40, 0x78, 0xc3, 0x38, 0x1e, 0x58, 0x1b, 0x1e, 0x80, 0xf5, 0xf2, 0xc4, 0x90, 0xe5, 0x11, 0x1f, + 0xba, 0xab, 0x66, 0x9a, 0x6f, 0x8c, 0x0b, 0xef, 0x56, 0xf5, 0x7e, 0x89, 0x22, 0xdc, 0x34, 0xe6, + 0x77, 0xc6, 0x82, 0x12, 0x6c, 0x65, 0x2c, 0xef, 0x9d, 0x92, 0x94, 0x45, 0x7a, 0xe0, 0x13, 0x8e, + 0x1b, 0x26, 0xcd, 0xce, 0x62, 0x69, 0xde, 0x29, 0xc3, 0xcc, 0x23, 0x42, 0xb8, 0x95, 0xb1, 0xfc, + 0x5b, 0xed, 0xed, 0x52, 0x61, 0x83, 0xfe, 0xe2, 0x80, 0x2d, 0x35, 0x24, 0x83, 0x5e, 0xca, 0xf9, + 0x71, 0x9f, 0x84, 0xc7, 0x93, 0xa8, 0x6b, 0xbb, 0xce, 0x5e, 0xf3, 0xde, 0xb6, 0x5f, 0x0a, 0xdd, + 0x9f, 0x08, 0xdd, 0x3f, 0xb4, 0x42, 0xef, 0x3c, 0xd4, 0x09, 0xfd, 0x5b, 0x78, 0xed, 0x79, 0xd7, + 0xdf, 0xe5, 0x19, 0x53, 0x34, 0x1b, 0xa8, 0xd1, 0x34, 0xa7, 0x79, 0xe7, 0xd0, 0xaf, 0x2f, 0x3c, + 0x07, 0x43, 0x0d, 0x3d, 0xb2, 0x88, 0x4d, 0xec, 0x03, 0x00, 0x4c, 0x11, 0x5c, 0x51, 0x21, 0xdd, + 0x86, 0xe9, 0xe3, 0xed, 0x71, 0xe1, 0xb5, 0x2a, 0x05, 0x1a, 0x0c, 0xe1, 0x86, 0x2e, 0xcb, 0xfc, + 0x86, 0x3f, 0x80, 0x5b, 0xa6, 0x6c, 0xa2, 0xb8, 0xe8, 0x1d, 0x51, 0xda, 0x33, 0xc9, 0xba, 0xc0, + 0xb4, 0xf0, 0xfe, 0x62, 0x2d, 0xdc, 0xb1, 0x1f, 0xc6, 0x2c, 0x0f, 0xc2, 0xad, 0x2b, 0xef, 0x03, + 0x4a, 0xb1, 0xf6, 0xc1, 0x87, 0xa0, 0x45, 0xcf, 0x06, 0xac, 0xec, 0x4a, 0xaf, 0x9f, 0xf2, 0xf0, + 0x58, 0xba, 0x4d, 0x93, 0xef, 0xdd, 0x71, 0xe1, 0xb9, 0x25, 0xdb, 0xcc, 0x11, 0x84, 0x37, 0xa7, + 0xbe, 0x8e, 0x71, 0x1d, 0xd4, 0x5f, 0x9e, 0x7b, 0x0e, 0xfa, 0xd3, 0x01, 0x77, 0xef, 0xc7, 0xb1, + 0xa0, 0x31, 0x51, 0xf4, 0xcb, 0xb3, 0x30, 0x21, 0x79, 0xac, 0x63, 0xd1, 0xae, 0xa0, 0xba, 0x64, + 0xf8, 0x16, 0xa8, 0x27, 0x44, 0x26, 0xe6, 0xa9, 0x68, 0x74, 0x5e, 0x1b, 0x17, 0x5e, 0xb3, 0x0c, + 0xa2, 0xbd, 0x08, 0x1b, 0x10, 0xbe, 0x03, 0x56, 0x4c, 0x7f, 0xec, 0xa3, 0xb0, 0x39, 0x2e, 0xbc, + 0xf5, 0xe9, 0x17, 0x2f, 0x10, 0x2e, 0x61, 0xa3, 0xd8, 0x93, 0x7e, 0xc6, 0x54, 0x99, 0x97, 0xf9, + 0xc2, 0xaf, 0x2b, 0xb6, 0x82, 0x6a, 0xc5, 0x1a, 0xd3, 0x24, 0x7c, 0xb0, 0xf6, 0xe3, 0xb9, 0x57, + 0x7b, 0x79, 0xee, 0xd5, 0xd0, 0x3f, 0x0e, 0xd8, 0x9e, 0x9b, 0xb3, 0x9e, 0x0b, 0xfc, 0xd9, 0x01, + 0x5b, 0xd4, 0x3a, 0x75, 0x27, 0x69, 0x4f, 0x9d, 0x0c, 0x52, 0x2a, 0x5d, 0x67, 0x77, 0x79, 0xaf, + 0x79, 0xef, 0x6d, 0x7f, 0xe6, 0xd9, 0xf5, 0xab, 0x1c, 0x8f, 0xf5, 0xe1, 0xf2, 0xd1, 0x99, 0xaa, + 0x69, 0x1e, 0x1f, 0xfa, 0xfd, 0x85, 0x07, 0x67, 0x6e, 0x4a, 0x0c, 0xe9, 0x8c, 0x6f, 0xd1, 0xfe, + 0x54, 0x6a, 0xbc, 0x70, 0x40, 0x6b, 0x86, 0x1c, 0x12, 0x50, 0x1f, 0x10, 0x26, 0xec, 0x30, 0xbe, + 0xb6, 0x12, 0x7b, 0xf5, 0x37, 0xcc, 0x8e, 0x52, 0x73, 0x22, 0x6c, 0xa8, 0xe1, 0xf7, 0x60, 0xe3, + 0x5a, 0xb5, 0x36, 0xe5, 0x4f, 0x17, 0x93, 0xf3, 0xd6, 0x9c, 0x7e, 0x21, 0xbc, 0x5e, 0x6d, 0x49, + 0xa5, 0xc8, 0x3f, 0x1c, 0x00, 0x0e, 0x89, 0xa2, 0x51, 0x57, 0xb0, 0x90, 0xce, 0x86, 0x76, 0xfe, + 0xe7, 0xd0, 0xf0, 0x73, 0xb0, 0x11, 0x0a, 0xaa, 0x23, 0x5a, 0x01, 0x2e, 0x19, 0x01, 0xba, 0xd3, + 0xeb, 0xd7, 0x60, 0x84, 0xd7, 0xad, 0x6d, 0x24, 0x88, 0x24, 0xb8, 0x81, 0xcd, 0x46, 0x91, 0xf0, + 0x26, 0x58, 0x62, 0x76, 0x7f, 0xe2, 0x25, 0x16, 0xc1, 0x37, 0xc1, 0x7a, 0x65, 0x77, 0xca, 0x92, + 0x18, 0x37, 0xa7, 0x1b, 0x54, 0xc2, 0x0f, 0xc1, 0x8a, 0x5e, 0xca, 0xd2, 0x5d, 0x36, 0x42, 0xdc, + 0xf6, 0xcb, 0x7a, 0x7c, 0xbd, 0xb6, 0x7d, 0xbb, 0xb6, 0xfd, 0x2f, 0x38, 0xcb, 0x3b, 0x75, 0x5d, + 0x31, 0x2e, 0x4f, 0x77, 0xbe, 0x7a, 0x7a, 0xd1, 0x76, 0x9e, 0x5d, 0xb4, 0x9d, 0xbf, 0x2f, 0xda, + 0xce, 0x4f, 0x97, 0xed, 0xda, 0xb3, 0xcb, 0x76, 0xed, 0xaf, 0xcb, 0x76, 0xed, 0xc9, 0xfe, 0x42, + 0x73, 0xb7, 0xff, 0x3e, 0xd4, 0x68, 0x40, 0x65, 0x7f, 0xd5, 0x3c, 0xad, 0xef, 0xff, 0x17, 0x00, + 0x00, 0xff, 0xff, 0xd0, 0x0a, 0xd3, 0x36, 0x9c, 0x08, 0x00, 0x00, +} + +func (this *Params) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Params) + if !ok { + that2, ok := that.(Params) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.VotePeriod != that1.VotePeriod { + return false + } + if !this.VoteThreshold.Equal(that1.VoteThreshold) { + return false + } + if !this.RewardBand.Equal(that1.RewardBand) { + return false + } + if len(this.Whitelist) != len(that1.Whitelist) { + return false + } + for i := range this.Whitelist { + if !this.Whitelist[i].Equal(that1.Whitelist[i]) { + return false + } + } + if !this.SlashFraction.Equal(that1.SlashFraction) { + return false + } + if this.SlashWindow != that1.SlashWindow { + return false + } + if !this.MinValidPerWindow.Equal(that1.MinValidPerWindow) { + return false + } + if this.TwapLookbackWindow != that1.TwapLookbackWindow { + return false + } + if this.MinVoters != that1.MinVoters { + return false + } + if !this.ValidatorFeeRatio.Equal(that1.ValidatorFeeRatio) { + return false + } + if this.ExpirationBlocks != that1.ExpirationBlocks { + return false + } + return true +} +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ExpirationBlocks != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.ExpirationBlocks)) + i-- + dAtA[i] = 0x58 + } + { + size := m.ValidatorFeeRatio.Size() + i -= size + if _, err := m.ValidatorFeeRatio.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + if m.MinVoters != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.MinVoters)) + i-- + dAtA[i] = 0x48 + } + n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.TwapLookbackWindow, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.TwapLookbackWindow):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintOracle(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x42 + { + size := m.MinValidPerWindow.Size() + i -= size + if _, err := m.MinValidPerWindow.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + if m.SlashWindow != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.SlashWindow)) + i-- + dAtA[i] = 0x30 + } + { + size := m.SlashFraction.Size() + i -= size + if _, err := m.SlashFraction.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.Whitelist) > 0 { + for iNdEx := len(m.Whitelist) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.Whitelist[iNdEx].Size() + i -= size + if _, err := m.Whitelist[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + { + size := m.RewardBand.Size() + i -= size + if _, err := m.RewardBand.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.VoteThreshold.Size() + i -= size + if _, err := m.VoteThreshold.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.VotePeriod != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.VotePeriod)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *AggregateExchangeRatePrevote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AggregateExchangeRatePrevote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AggregateExchangeRatePrevote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SubmitBlock != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.SubmitBlock)) + i-- + dAtA[i] = 0x18 + } + if len(m.Voter) > 0 { + i -= len(m.Voter) + copy(dAtA[i:], m.Voter) + i = encodeVarintOracle(dAtA, i, uint64(len(m.Voter))) + i-- + dAtA[i] = 0x12 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintOracle(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AggregateExchangeRateVote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AggregateExchangeRateVote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AggregateExchangeRateVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Voter) > 0 { + i -= len(m.Voter) + copy(dAtA[i:], m.Voter) + i = encodeVarintOracle(dAtA, i, uint64(len(m.Voter))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExchangeRateTuples) > 0 { + for iNdEx := len(m.ExchangeRateTuples) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ExchangeRateTuples[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ExchangeRateTuple) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExchangeRateTuple) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExchangeRateTuple) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.ExchangeRate.Size() + i -= size + if _, err := m.ExchangeRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.Pair.Size() + i -= size + if _, err := m.Pair.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *DatedPrice) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DatedPrice) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DatedPrice) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CreatedBlock != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.CreatedBlock)) + i-- + dAtA[i] = 0x10 + } + { + size := m.ExchangeRate.Size() + i -= size + if _, err := m.ExchangeRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Rewards) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Rewards) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Rewards) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintOracle(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if m.VotePeriods != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.VotePeriods)) + i-- + dAtA[i] = 0x10 + } + if m.Id != 0 { + i = encodeVarintOracle(dAtA, i, uint64(m.Id)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintOracle(dAtA []byte, offset int, v uint64) int { + offset -= sovOracle(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.VotePeriod != 0 { + n += 1 + sovOracle(uint64(m.VotePeriod)) + } + l = m.VoteThreshold.Size() + n += 1 + l + sovOracle(uint64(l)) + l = m.RewardBand.Size() + n += 1 + l + sovOracle(uint64(l)) + if len(m.Whitelist) > 0 { + for _, e := range m.Whitelist { + l = e.Size() + n += 1 + l + sovOracle(uint64(l)) + } + } + l = m.SlashFraction.Size() + n += 1 + l + sovOracle(uint64(l)) + if m.SlashWindow != 0 { + n += 1 + sovOracle(uint64(m.SlashWindow)) + } + l = m.MinValidPerWindow.Size() + n += 1 + l + sovOracle(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.TwapLookbackWindow) + n += 1 + l + sovOracle(uint64(l)) + if m.MinVoters != 0 { + n += 1 + sovOracle(uint64(m.MinVoters)) + } + l = m.ValidatorFeeRatio.Size() + n += 1 + l + sovOracle(uint64(l)) + if m.ExpirationBlocks != 0 { + n += 1 + sovOracle(uint64(m.ExpirationBlocks)) + } + return n +} + +func (m *AggregateExchangeRatePrevote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovOracle(uint64(l)) + } + l = len(m.Voter) + if l > 0 { + n += 1 + l + sovOracle(uint64(l)) + } + if m.SubmitBlock != 0 { + n += 1 + sovOracle(uint64(m.SubmitBlock)) + } + return n +} + +func (m *AggregateExchangeRateVote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ExchangeRateTuples) > 0 { + for _, e := range m.ExchangeRateTuples { + l = e.Size() + n += 1 + l + sovOracle(uint64(l)) + } + } + l = len(m.Voter) + if l > 0 { + n += 1 + l + sovOracle(uint64(l)) + } + return n +} + +func (m *ExchangeRateTuple) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Pair.Size() + n += 1 + l + sovOracle(uint64(l)) + l = m.ExchangeRate.Size() + n += 1 + l + sovOracle(uint64(l)) + return n +} + +func (m *DatedPrice) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ExchangeRate.Size() + n += 1 + l + sovOracle(uint64(l)) + if m.CreatedBlock != 0 { + n += 1 + sovOracle(uint64(m.CreatedBlock)) + } + return n +} + +func (m *Rewards) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != 0 { + n += 1 + sovOracle(uint64(m.Id)) + } + if m.VotePeriods != 0 { + n += 1 + sovOracle(uint64(m.VotePeriods)) + } + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovOracle(uint64(l)) + } + } + return n +} + +func sovOracle(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozOracle(x uint64) (n int) { + return sovOracle(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotePeriod", wireType) + } + m.VotePeriod = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotePeriod |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteThreshold", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.VoteThreshold.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RewardBand", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RewardBand.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Whitelist", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_archway_network_archway_x_common_asset.Pair + m.Whitelist = append(m.Whitelist, v) + if err := m.Whitelist[len(m.Whitelist)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashFraction", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SlashFraction.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashWindow", wireType) + } + m.SlashWindow = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SlashWindow |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinValidPerWindow", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinValidPerWindow.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TwapLookbackWindow", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.TwapLookbackWindow, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinVoters", wireType) + } + m.MinVoters = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinVoters |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorFeeRatio", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ValidatorFeeRatio.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpirationBlocks", wireType) + } + m.ExpirationBlocks = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpirationBlocks |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AggregateExchangeRatePrevote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AggregateExchangeRatePrevote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AggregateExchangeRatePrevote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SubmitBlock", wireType) + } + m.SubmitBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SubmitBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AggregateExchangeRateVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AggregateExchangeRateVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AggregateExchangeRateVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRateTuples", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExchangeRateTuples = append(m.ExchangeRateTuples, ExchangeRateTuple{}) + if err := m.ExchangeRateTuples[len(m.ExchangeRateTuples)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExchangeRateTuple) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExchangeRateTuple: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExchangeRateTuple: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pair", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Pair.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ExchangeRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DatedPrice) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DatedPrice: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DatedPrice: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ExchangeRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CreatedBlock", wireType) + } + m.CreatedBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CreatedBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Rewards) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Rewards: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Rewards: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + m.Id = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Id |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VotePeriods", wireType) + } + m.VotePeriods = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VotePeriods |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowOracle + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthOracle + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthOracle + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipOracle(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthOracle + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipOracle(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowOracle + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowOracle + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowOracle + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthOracle + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupOracle + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthOracle + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthOracle = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowOracle = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupOracle = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go new file mode 100644 index 00000000..f97e26e1 --- /dev/null +++ b/x/oracle/types/params.go @@ -0,0 +1,116 @@ +package types + +import ( + "fmt" + time "time" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/denoms" +) + +// Parameter keys +var ( + KeyVotePeriod = []byte("VotePeriod") + KeyVoteThreshold = []byte("VoteThreshold") + KeyMinVoters = []byte("MinVoters") + KeyRewardBand = []byte("RewardBand") + KeyWhitelist = []byte("Whitelist") + KeySlashFraction = []byte("SlashFraction") + KeySlashWindow = []byte("SlashWindow") + KeyMinValidPerWindow = []byte("MinValidPerWindow") + KeyTwapLookbackWindow = []byte("TwapLookbackWindow") + KeyValidatorFeeRatio = []byte("ValidatorFeeRatio") +) + +// Default parameter values +// Assumes block times are 2s +const ( + DefaultVotePeriod = 5 // vote every 1 minute + DefaultSlashWindow = 3600 // 2 hours + DefaultMinVoters = 4 // minimum of 4 voters for a pair to become valid + DefaultExpirationBlocks = 900 // 30 minutes +) + +// Default parameter values +var ( + DefaultVoteThreshold = math.LegacyOneDec().Quo(math.LegacyNewDec(3)) // 33.33% + DefaultRewardBand = math.LegacyNewDecWithPrec(2, 2) // 2% (-1, 1) + DefaultWhitelist = []asset.Pair{ + // paired against the US fiat dollar + asset.Registry.Pair(denoms.BTC, denoms.USD), + asset.Registry.Pair(denoms.ETH, denoms.USD), + asset.Registry.Pair(denoms.ATOM, denoms.USD), + asset.Registry.Pair(denoms.BNB, denoms.USD), + asset.Registry.Pair(denoms.USDC, denoms.USD), + asset.Registry.Pair(denoms.USDT, denoms.USD), + // asset.Registry.Pair(denoms.OSMO, denoms.USD), + // asset.Registry.Pair(denoms.AVAX, denoms.USD), + // asset.Registry.Pair(denoms.SOL, denoms.USD), + // asset.Registry.Pair(denoms.ADA, denoms.USD), + } + DefaultSlashFraction = math.LegacyNewDecWithPrec(5, 3) // 0.5% + DefaultMinValidPerWindow = math.LegacyNewDecWithPrec(69, 2) // 69% + DefaultTwapLookbackWindow = time.Duration(15 * time.Minute) // 15 minutes + DefaultValidatorFeeRatio = math.LegacyNewDecWithPrec(5, 2) // 0.05% +) + +// DefaultParams creates default oracle module parameters +func DefaultParams() Params { + return Params{ + VotePeriod: DefaultVotePeriod, + VoteThreshold: DefaultVoteThreshold, + MinVoters: DefaultMinVoters, + ExpirationBlocks: DefaultExpirationBlocks, + RewardBand: DefaultRewardBand, + Whitelist: DefaultWhitelist, + SlashFraction: DefaultSlashFraction, + SlashWindow: DefaultSlashWindow, + MinValidPerWindow: DefaultMinValidPerWindow, + TwapLookbackWindow: DefaultTwapLookbackWindow, + ValidatorFeeRatio: DefaultValidatorFeeRatio, + } +} + +// Validate performs basic validation on oracle parameters. +func (p Params) Validate() error { + if p.VotePeriod == 0 { + return fmt.Errorf("oracle parameter VotePeriod must be > 0, is %d", p.VotePeriod) + } + + if p.VoteThreshold.LTE(math.LegacyNewDecWithPrec(33, 2)) { + return fmt.Errorf("oracle parameter VoteThreshold must be greater than 33 percent") + } + + if p.MinVoters <= 0 { + return fmt.Errorf("oracle parameter MinVoters must be greater than 0") + } + + if p.RewardBand.GT(math.LegacyOneDec()) || p.RewardBand.IsNegative() { + return fmt.Errorf("oracle parameter RewardBand must be between [0, 1]") + } + + if p.SlashFraction.GT(math.LegacyOneDec()) || p.SlashFraction.IsNegative() { + return fmt.Errorf("oracle parameter SlashFraction must be between [0, 1]") + } + + if p.SlashWindow < p.VotePeriod { + return fmt.Errorf("oracle parameter SlashWindow must be greater than or equal with VotePeriod") + } + + if p.MinValidPerWindow.GT(math.LegacyOneDec()) || p.MinValidPerWindow.IsNegative() { + return fmt.Errorf("oracle parameter MinValidPerWindow must be between [0, 1]") + } + + if p.ValidatorFeeRatio.GT(math.LegacyOneDec()) || p.ValidatorFeeRatio.IsNegative() { + return fmt.Errorf("oracle parameter ValidatorFeeRatio must be between [0, 1]") + } + + for _, pair := range p.Whitelist { + if err := pair.Validate(); err != nil { + return fmt.Errorf("oracle parameter Whitelist Pair invalid format: %w", err) + } + } + return nil +} diff --git a/x/oracle/types/params_test.go b/x/oracle/types/params_test.go new file mode 100644 index 00000000..884307a9 --- /dev/null +++ b/x/oracle/types/params_test.go @@ -0,0 +1,76 @@ +package types_test + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/oracle/types" +) + +func TestParamsEqual(t *testing.T) { + p1 := types.DefaultParams() + err := p1.Validate() + require.NoError(t, err) + + // minus vote period + p1.VotePeriod = 0 + err = p1.Validate() + require.Error(t, err) + + p1.MinVoters = 0 + err = p1.Validate() + require.Error(t, err) + + // small vote threshold + p2 := types.DefaultParams() + p2.VoteThreshold = math.LegacyZeroDec() + err = p2.Validate() + require.Error(t, err) + + // negative reward band + p3 := types.DefaultParams() + p3.RewardBand = math.LegacyNewDecWithPrec(-1, 2) + err = p3.Validate() + require.Error(t, err) + + // negative slash fraction + p4 := types.DefaultParams() + p4.SlashFraction = math.LegacyNewDec(-1) + err = p4.Validate() + require.Error(t, err) + + // negative min valid per window + p5 := types.DefaultParams() + p5.MinValidPerWindow = math.LegacyNewDec(-1) + err = p5.Validate() + require.Error(t, err) + + // small slash window + p6 := types.DefaultParams() + p6.SlashWindow = 0 + err = p6.Validate() + require.Error(t, err) + + // empty name + p10 := types.DefaultParams() + p10.Whitelist[0] = "" + err = p10.Validate() + require.Error(t, err) + + // oracle fee ratio > 1 + p12 := types.DefaultParams() + p12.ValidatorFeeRatio = math.LegacyNewDec(2) + err = p12.Validate() + require.Error(t, err) + + // oracle fee ratio < 0 + p13 := types.DefaultParams() + p13.ValidatorFeeRatio = math.LegacyNewDec(-1) + err = p13.Validate() + require.Error(t, err) + + p11 := types.DefaultParams() + require.NotNil(t, p11.String()) +} diff --git a/x/oracle/types/query.pb.go b/x/oracle/types/query.pb.go new file mode 100644 index 00000000..568c385f --- /dev/null +++ b/x/oracle/types/query.pb.go @@ -0,0 +1,4210 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/query.proto + +package types + +import ( + context "context" + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + github_com_archway_network_archway_x_common_asset "github.com/archway-network/archway/x/common/asset" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryExchangeRateRequest is the request type for the Query/ExchangeRate RPC +// method. +type QueryExchangeRateRequest struct { + // pair defines the pair to query for. + Pair github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,1,opt,name=pair,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"pair"` +} + +func (m *QueryExchangeRateRequest) Reset() { *m = QueryExchangeRateRequest{} } +func (m *QueryExchangeRateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryExchangeRateRequest) ProtoMessage() {} +func (*QueryExchangeRateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{0} +} +func (m *QueryExchangeRateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryExchangeRateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryExchangeRateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryExchangeRateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryExchangeRateRequest.Merge(m, src) +} +func (m *QueryExchangeRateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryExchangeRateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryExchangeRateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryExchangeRateRequest proto.InternalMessageInfo + +// QueryExchangeRateResponse is response type for the +// Query/ExchangeRate RPC method. +type QueryExchangeRateResponse struct { + // exchange_rate defines the exchange rate of assets voted by validators + ExchangeRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=exchange_rate,json=exchangeRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"exchange_rate"` +} + +func (m *QueryExchangeRateResponse) Reset() { *m = QueryExchangeRateResponse{} } +func (m *QueryExchangeRateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryExchangeRateResponse) ProtoMessage() {} +func (*QueryExchangeRateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{1} +} +func (m *QueryExchangeRateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryExchangeRateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryExchangeRateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryExchangeRateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryExchangeRateResponse.Merge(m, src) +} +func (m *QueryExchangeRateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryExchangeRateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryExchangeRateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryExchangeRateResponse proto.InternalMessageInfo + +// QueryExchangeRatesRequest is the request type for the Query/ExchangeRates RPC +// method. +type QueryExchangeRatesRequest struct { +} + +func (m *QueryExchangeRatesRequest) Reset() { *m = QueryExchangeRatesRequest{} } +func (m *QueryExchangeRatesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryExchangeRatesRequest) ProtoMessage() {} +func (*QueryExchangeRatesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{2} +} +func (m *QueryExchangeRatesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryExchangeRatesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryExchangeRatesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryExchangeRatesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryExchangeRatesRequest.Merge(m, src) +} +func (m *QueryExchangeRatesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryExchangeRatesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryExchangeRatesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryExchangeRatesRequest proto.InternalMessageInfo + +// QueryExchangeRatesResponse is response type for the +// Query/ExchangeRates RPC method. +type QueryExchangeRatesResponse struct { + // exchange_rates defines a list of the exchange rate for all whitelisted + // pairs. + ExchangeRates ExchangeRateTuples `protobuf:"bytes,1,rep,name=exchange_rates,json=exchangeRates,proto3,castrepeated=ExchangeRateTuples" json:"exchange_rates"` +} + +func (m *QueryExchangeRatesResponse) Reset() { *m = QueryExchangeRatesResponse{} } +func (m *QueryExchangeRatesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryExchangeRatesResponse) ProtoMessage() {} +func (*QueryExchangeRatesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{3} +} +func (m *QueryExchangeRatesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryExchangeRatesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryExchangeRatesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryExchangeRatesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryExchangeRatesResponse.Merge(m, src) +} +func (m *QueryExchangeRatesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryExchangeRatesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryExchangeRatesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryExchangeRatesResponse proto.InternalMessageInfo + +func (m *QueryExchangeRatesResponse) GetExchangeRates() ExchangeRateTuples { + if m != nil { + return m.ExchangeRates + } + return nil +} + +// QueryActivesRequest is the request type for the Query/Actives RPC method. +type QueryActivesRequest struct { +} + +func (m *QueryActivesRequest) Reset() { *m = QueryActivesRequest{} } +func (m *QueryActivesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryActivesRequest) ProtoMessage() {} +func (*QueryActivesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{4} +} +func (m *QueryActivesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryActivesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryActivesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryActivesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActivesRequest.Merge(m, src) +} +func (m *QueryActivesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryActivesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActivesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryActivesRequest proto.InternalMessageInfo + +// QueryActivesResponse is response type for the +// Query/Actives RPC method. +type QueryActivesResponse struct { + // actives defines a list of the pair which oracle prices agreed upon. + Actives []github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,1,rep,name=actives,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"actives"` +} + +func (m *QueryActivesResponse) Reset() { *m = QueryActivesResponse{} } +func (m *QueryActivesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryActivesResponse) ProtoMessage() {} +func (*QueryActivesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{5} +} +func (m *QueryActivesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryActivesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryActivesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryActivesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryActivesResponse.Merge(m, src) +} +func (m *QueryActivesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryActivesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryActivesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryActivesResponse proto.InternalMessageInfo + +// QueryVoteTargetsRequest is the request type for the Query/VoteTargets RPC +// method. +type QueryVoteTargetsRequest struct { +} + +func (m *QueryVoteTargetsRequest) Reset() { *m = QueryVoteTargetsRequest{} } +func (m *QueryVoteTargetsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryVoteTargetsRequest) ProtoMessage() {} +func (*QueryVoteTargetsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{6} +} +func (m *QueryVoteTargetsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVoteTargetsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVoteTargetsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVoteTargetsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVoteTargetsRequest.Merge(m, src) +} +func (m *QueryVoteTargetsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryVoteTargetsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVoteTargetsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVoteTargetsRequest proto.InternalMessageInfo + +// QueryVoteTargetsResponse is response type for the +// Query/VoteTargets RPC method. +type QueryVoteTargetsResponse struct { + // vote_targets defines a list of the pairs in which everyone + // should vote in the current vote period. + VoteTargets []github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,1,rep,name=vote_targets,json=voteTargets,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"vote_targets"` +} + +func (m *QueryVoteTargetsResponse) Reset() { *m = QueryVoteTargetsResponse{} } +func (m *QueryVoteTargetsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryVoteTargetsResponse) ProtoMessage() {} +func (*QueryVoteTargetsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{7} +} +func (m *QueryVoteTargetsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVoteTargetsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVoteTargetsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVoteTargetsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVoteTargetsResponse.Merge(m, src) +} +func (m *QueryVoteTargetsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryVoteTargetsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVoteTargetsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVoteTargetsResponse proto.InternalMessageInfo + +// QueryFeederDelegationRequest is the request type for the +// Query/FeederDelegation RPC method. +type QueryFeederDelegationRequest struct { + // validator defines the validator address to query for. + ValidatorAddr string `protobuf:"bytes,1,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` +} + +func (m *QueryFeederDelegationRequest) Reset() { *m = QueryFeederDelegationRequest{} } +func (m *QueryFeederDelegationRequest) String() string { return proto.CompactTextString(m) } +func (*QueryFeederDelegationRequest) ProtoMessage() {} +func (*QueryFeederDelegationRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{8} +} +func (m *QueryFeederDelegationRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryFeederDelegationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryFeederDelegationRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryFeederDelegationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFeederDelegationRequest.Merge(m, src) +} +func (m *QueryFeederDelegationRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryFeederDelegationRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFeederDelegationRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryFeederDelegationRequest proto.InternalMessageInfo + +// QueryFeederDelegationResponse is response type for the +// Query/FeederDelegation RPC method. +type QueryFeederDelegationResponse struct { + // feeder_addr defines the feeder delegation of a validator + FeederAddr string `protobuf:"bytes,1,opt,name=feeder_addr,json=feederAddr,proto3" json:"feeder_addr,omitempty"` +} + +func (m *QueryFeederDelegationResponse) Reset() { *m = QueryFeederDelegationResponse{} } +func (m *QueryFeederDelegationResponse) String() string { return proto.CompactTextString(m) } +func (*QueryFeederDelegationResponse) ProtoMessage() {} +func (*QueryFeederDelegationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{9} +} +func (m *QueryFeederDelegationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryFeederDelegationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryFeederDelegationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryFeederDelegationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryFeederDelegationResponse.Merge(m, src) +} +func (m *QueryFeederDelegationResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryFeederDelegationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryFeederDelegationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryFeederDelegationResponse proto.InternalMessageInfo + +func (m *QueryFeederDelegationResponse) GetFeederAddr() string { + if m != nil { + return m.FeederAddr + } + return "" +} + +// QueryMissCounterRequest is the request type for the Query/MissCounter RPC +// method. +type QueryMissCounterRequest struct { + // validator defines the validator address to query for. + ValidatorAddr string `protobuf:"bytes,1,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` +} + +func (m *QueryMissCounterRequest) Reset() { *m = QueryMissCounterRequest{} } +func (m *QueryMissCounterRequest) String() string { return proto.CompactTextString(m) } +func (*QueryMissCounterRequest) ProtoMessage() {} +func (*QueryMissCounterRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{10} +} +func (m *QueryMissCounterRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMissCounterRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMissCounterRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMissCounterRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMissCounterRequest.Merge(m, src) +} +func (m *QueryMissCounterRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryMissCounterRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMissCounterRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMissCounterRequest proto.InternalMessageInfo + +// QueryMissCounterResponse is response type for the +// Query/MissCounter RPC method. +type QueryMissCounterResponse struct { + // miss_counter defines the oracle miss counter of a validator + MissCounter uint64 `protobuf:"varint,1,opt,name=miss_counter,json=missCounter,proto3" json:"miss_counter,omitempty"` +} + +func (m *QueryMissCounterResponse) Reset() { *m = QueryMissCounterResponse{} } +func (m *QueryMissCounterResponse) String() string { return proto.CompactTextString(m) } +func (*QueryMissCounterResponse) ProtoMessage() {} +func (*QueryMissCounterResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{11} +} +func (m *QueryMissCounterResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryMissCounterResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryMissCounterResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryMissCounterResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryMissCounterResponse.Merge(m, src) +} +func (m *QueryMissCounterResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryMissCounterResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryMissCounterResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryMissCounterResponse proto.InternalMessageInfo + +func (m *QueryMissCounterResponse) GetMissCounter() uint64 { + if m != nil { + return m.MissCounter + } + return 0 +} + +// QueryAggregatePrevoteRequest is the request type for the +// Query/AggregatePrevote RPC method. +type QueryAggregatePrevoteRequest struct { + // validator defines the validator address to query for. + ValidatorAddr string `protobuf:"bytes,1,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` +} + +func (m *QueryAggregatePrevoteRequest) Reset() { *m = QueryAggregatePrevoteRequest{} } +func (m *QueryAggregatePrevoteRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAggregatePrevoteRequest) ProtoMessage() {} +func (*QueryAggregatePrevoteRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{12} +} +func (m *QueryAggregatePrevoteRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregatePrevoteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregatePrevoteRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregatePrevoteRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregatePrevoteRequest.Merge(m, src) +} +func (m *QueryAggregatePrevoteRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregatePrevoteRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregatePrevoteRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregatePrevoteRequest proto.InternalMessageInfo + +// QueryAggregatePrevoteResponse is response type for the +// Query/AggregatePrevote RPC method. +type QueryAggregatePrevoteResponse struct { + // aggregate_prevote defines oracle aggregate prevote submitted by a validator + // in the current vote period + AggregatePrevote AggregateExchangeRatePrevote `protobuf:"bytes,1,opt,name=aggregate_prevote,json=aggregatePrevote,proto3" json:"aggregate_prevote"` +} + +func (m *QueryAggregatePrevoteResponse) Reset() { *m = QueryAggregatePrevoteResponse{} } +func (m *QueryAggregatePrevoteResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAggregatePrevoteResponse) ProtoMessage() {} +func (*QueryAggregatePrevoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{13} +} +func (m *QueryAggregatePrevoteResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregatePrevoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregatePrevoteResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregatePrevoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregatePrevoteResponse.Merge(m, src) +} +func (m *QueryAggregatePrevoteResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregatePrevoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregatePrevoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregatePrevoteResponse proto.InternalMessageInfo + +func (m *QueryAggregatePrevoteResponse) GetAggregatePrevote() AggregateExchangeRatePrevote { + if m != nil { + return m.AggregatePrevote + } + return AggregateExchangeRatePrevote{} +} + +// QueryAggregatePrevotesRequest is the request type for the +// Query/AggregatePrevotes RPC method. +type QueryAggregatePrevotesRequest struct { +} + +func (m *QueryAggregatePrevotesRequest) Reset() { *m = QueryAggregatePrevotesRequest{} } +func (m *QueryAggregatePrevotesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAggregatePrevotesRequest) ProtoMessage() {} +func (*QueryAggregatePrevotesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{14} +} +func (m *QueryAggregatePrevotesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregatePrevotesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregatePrevotesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregatePrevotesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregatePrevotesRequest.Merge(m, src) +} +func (m *QueryAggregatePrevotesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregatePrevotesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregatePrevotesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregatePrevotesRequest proto.InternalMessageInfo + +// QueryAggregatePrevotesResponse is response type for the +// Query/AggregatePrevotes RPC method. +type QueryAggregatePrevotesResponse struct { + // aggregate_prevotes defines all oracle aggregate prevotes submitted in the + // current vote period + AggregatePrevotes []AggregateExchangeRatePrevote `protobuf:"bytes,1,rep,name=aggregate_prevotes,json=aggregatePrevotes,proto3" json:"aggregate_prevotes"` +} + +func (m *QueryAggregatePrevotesResponse) Reset() { *m = QueryAggregatePrevotesResponse{} } +func (m *QueryAggregatePrevotesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAggregatePrevotesResponse) ProtoMessage() {} +func (*QueryAggregatePrevotesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{15} +} +func (m *QueryAggregatePrevotesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregatePrevotesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregatePrevotesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregatePrevotesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregatePrevotesResponse.Merge(m, src) +} +func (m *QueryAggregatePrevotesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregatePrevotesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregatePrevotesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregatePrevotesResponse proto.InternalMessageInfo + +func (m *QueryAggregatePrevotesResponse) GetAggregatePrevotes() []AggregateExchangeRatePrevote { + if m != nil { + return m.AggregatePrevotes + } + return nil +} + +// QueryAggregateVoteRequest is the request type for the Query/AggregateVote RPC +// method. +type QueryAggregateVoteRequest struct { + // validator defines the validator address to query for. + ValidatorAddr string `protobuf:"bytes,1,opt,name=validator_addr,json=validatorAddr,proto3" json:"validator_addr,omitempty"` +} + +func (m *QueryAggregateVoteRequest) Reset() { *m = QueryAggregateVoteRequest{} } +func (m *QueryAggregateVoteRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAggregateVoteRequest) ProtoMessage() {} +func (*QueryAggregateVoteRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{16} +} +func (m *QueryAggregateVoteRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregateVoteRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregateVoteRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregateVoteRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregateVoteRequest.Merge(m, src) +} +func (m *QueryAggregateVoteRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregateVoteRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregateVoteRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregateVoteRequest proto.InternalMessageInfo + +// QueryAggregateVoteResponse is response type for the +// Query/AggregateVote RPC method. +type QueryAggregateVoteResponse struct { + // aggregate_vote defines oracle aggregate vote submitted by a validator in + // the current vote period + AggregateVote AggregateExchangeRateVote `protobuf:"bytes,1,opt,name=aggregate_vote,json=aggregateVote,proto3" json:"aggregate_vote"` +} + +func (m *QueryAggregateVoteResponse) Reset() { *m = QueryAggregateVoteResponse{} } +func (m *QueryAggregateVoteResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAggregateVoteResponse) ProtoMessage() {} +func (*QueryAggregateVoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{17} +} +func (m *QueryAggregateVoteResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregateVoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregateVoteResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregateVoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregateVoteResponse.Merge(m, src) +} +func (m *QueryAggregateVoteResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregateVoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregateVoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregateVoteResponse proto.InternalMessageInfo + +func (m *QueryAggregateVoteResponse) GetAggregateVote() AggregateExchangeRateVote { + if m != nil { + return m.AggregateVote + } + return AggregateExchangeRateVote{} +} + +// QueryAggregateVotesRequest is the request type for the Query/AggregateVotes +// RPC method. +type QueryAggregateVotesRequest struct { +} + +func (m *QueryAggregateVotesRequest) Reset() { *m = QueryAggregateVotesRequest{} } +func (m *QueryAggregateVotesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAggregateVotesRequest) ProtoMessage() {} +func (*QueryAggregateVotesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{18} +} +func (m *QueryAggregateVotesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregateVotesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregateVotesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregateVotesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregateVotesRequest.Merge(m, src) +} +func (m *QueryAggregateVotesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregateVotesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregateVotesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregateVotesRequest proto.InternalMessageInfo + +// QueryAggregateVotesResponse is response type for the +// Query/AggregateVotes RPC method. +type QueryAggregateVotesResponse struct { + // aggregate_votes defines all oracle aggregate votes submitted in the current + // vote period + AggregateVotes []AggregateExchangeRateVote `protobuf:"bytes,1,rep,name=aggregate_votes,json=aggregateVotes,proto3" json:"aggregate_votes"` +} + +func (m *QueryAggregateVotesResponse) Reset() { *m = QueryAggregateVotesResponse{} } +func (m *QueryAggregateVotesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAggregateVotesResponse) ProtoMessage() {} +func (*QueryAggregateVotesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{19} +} +func (m *QueryAggregateVotesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAggregateVotesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAggregateVotesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryAggregateVotesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAggregateVotesResponse.Merge(m, src) +} +func (m *QueryAggregateVotesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAggregateVotesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAggregateVotesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAggregateVotesResponse proto.InternalMessageInfo + +func (m *QueryAggregateVotesResponse) GetAggregateVotes() []AggregateExchangeRateVote { + if m != nil { + return m.AggregateVotes + } + return nil +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{20} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ba0ccf1ab668bdde, []int{21} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*QueryExchangeRateRequest)(nil), "archway.oracle.v1.QueryExchangeRateRequest") + proto.RegisterType((*QueryExchangeRateResponse)(nil), "archway.oracle.v1.QueryExchangeRateResponse") + proto.RegisterType((*QueryExchangeRatesRequest)(nil), "archway.oracle.v1.QueryExchangeRatesRequest") + proto.RegisterType((*QueryExchangeRatesResponse)(nil), "archway.oracle.v1.QueryExchangeRatesResponse") + proto.RegisterType((*QueryActivesRequest)(nil), "archway.oracle.v1.QueryActivesRequest") + proto.RegisterType((*QueryActivesResponse)(nil), "archway.oracle.v1.QueryActivesResponse") + proto.RegisterType((*QueryVoteTargetsRequest)(nil), "archway.oracle.v1.QueryVoteTargetsRequest") + proto.RegisterType((*QueryVoteTargetsResponse)(nil), "archway.oracle.v1.QueryVoteTargetsResponse") + proto.RegisterType((*QueryFeederDelegationRequest)(nil), "archway.oracle.v1.QueryFeederDelegationRequest") + proto.RegisterType((*QueryFeederDelegationResponse)(nil), "archway.oracle.v1.QueryFeederDelegationResponse") + proto.RegisterType((*QueryMissCounterRequest)(nil), "archway.oracle.v1.QueryMissCounterRequest") + proto.RegisterType((*QueryMissCounterResponse)(nil), "archway.oracle.v1.QueryMissCounterResponse") + proto.RegisterType((*QueryAggregatePrevoteRequest)(nil), "archway.oracle.v1.QueryAggregatePrevoteRequest") + proto.RegisterType((*QueryAggregatePrevoteResponse)(nil), "archway.oracle.v1.QueryAggregatePrevoteResponse") + proto.RegisterType((*QueryAggregatePrevotesRequest)(nil), "archway.oracle.v1.QueryAggregatePrevotesRequest") + proto.RegisterType((*QueryAggregatePrevotesResponse)(nil), "archway.oracle.v1.QueryAggregatePrevotesResponse") + proto.RegisterType((*QueryAggregateVoteRequest)(nil), "archway.oracle.v1.QueryAggregateVoteRequest") + proto.RegisterType((*QueryAggregateVoteResponse)(nil), "archway.oracle.v1.QueryAggregateVoteResponse") + proto.RegisterType((*QueryAggregateVotesRequest)(nil), "archway.oracle.v1.QueryAggregateVotesRequest") + proto.RegisterType((*QueryAggregateVotesResponse)(nil), "archway.oracle.v1.QueryAggregateVotesResponse") + proto.RegisterType((*QueryParamsRequest)(nil), "archway.oracle.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "archway.oracle.v1.QueryParamsResponse") +} + +func init() { proto.RegisterFile("archway/oracle/v1/query.proto", fileDescriptor_ba0ccf1ab668bdde) } + +var fileDescriptor_ba0ccf1ab668bdde = []byte{ + // 1115 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x97, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x3d, 0x10, 0x52, 0x18, 0xc7, 0x6e, 0x32, 0x04, 0x91, 0x6c, 0x52, 0x3b, 0x2c, 0x24, + 0xad, 0xda, 0x64, 0x17, 0x9b, 0x42, 0x51, 0x44, 0x51, 0x63, 0x52, 0x40, 0xa5, 0x85, 0x60, 0x55, + 0x11, 0x3f, 0x0e, 0xd6, 0x78, 0x3d, 0x6c, 0x56, 0xb5, 0x3d, 0xdb, 0x9d, 0xb1, 0x93, 0x80, 0x2a, + 0x24, 0x10, 0x08, 0x6e, 0x48, 0x5c, 0x10, 0xa7, 0xc2, 0x09, 0xc1, 0x1f, 0x00, 0xdc, 0x39, 0xf4, + 0x58, 0x89, 0x0b, 0xe2, 0x50, 0x50, 0x82, 0x10, 0x7f, 0x06, 0xda, 0xd9, 0xf1, 0x7a, 0xd7, 0xbb, + 0x43, 0xb6, 0x2e, 0xbd, 0x25, 0xf3, 0xde, 0xbe, 0xf7, 0x79, 0xdf, 0x99, 0x9d, 0xaf, 0x17, 0x9e, + 0xc0, 0x9e, 0xb5, 0xb3, 0x8b, 0xf7, 0x4d, 0xea, 0x61, 0xab, 0x4d, 0xcc, 0x7e, 0xc5, 0xbc, 0xde, + 0x23, 0xde, 0xbe, 0xe1, 0x7a, 0x94, 0x53, 0x34, 0x23, 0xc3, 0x46, 0x10, 0x36, 0xfa, 0x15, 0x6d, + 0xd6, 0xa6, 0x36, 0x15, 0x51, 0xd3, 0xff, 0x2b, 0x48, 0xd4, 0x16, 0x6d, 0x4a, 0xed, 0x36, 0x31, + 0xb1, 0xeb, 0x98, 0xb8, 0xdb, 0xa5, 0x1c, 0x73, 0x87, 0x76, 0x99, 0x8c, 0x96, 0x92, 0x5d, 0x64, + 0x41, 0x19, 0xb7, 0x28, 0xeb, 0x50, 0x66, 0x36, 0x31, 0xf3, 0x83, 0x4d, 0xc2, 0x71, 0xc5, 0xb4, + 0xa8, 0xd3, 0x0d, 0xe2, 0xfa, 0x1e, 0x9c, 0x7b, 0xd3, 0xa7, 0xba, 0xb8, 0x67, 0xed, 0xe0, 0xae, + 0x4d, 0xea, 0x98, 0x93, 0x3a, 0xb9, 0xde, 0x23, 0x8c, 0xa3, 0x3a, 0x9c, 0x70, 0xb1, 0xe3, 0xcd, + 0x81, 0x25, 0x70, 0xea, 0x91, 0xda, 0x8b, 0xb7, 0xee, 0x94, 0x73, 0xbf, 0xdf, 0x29, 0x3f, 0x67, + 0x3b, 0x7c, 0xa7, 0xd7, 0x34, 0x2c, 0xda, 0x31, 0x65, 0xf3, 0xb5, 0x2e, 0xe1, 0xbb, 0xd4, 0xbb, + 0x36, 0xf8, 0xdf, 0xdc, 0x33, 0x2d, 0xda, 0xe9, 0xd0, 0xae, 0x89, 0x19, 0x23, 0xdc, 0xd8, 0xc2, + 0x8e, 0x57, 0x17, 0xb5, 0xd6, 0x1f, 0xfe, 0xec, 0x66, 0x39, 0xf7, 0xcf, 0xcd, 0x72, 0x4e, 0x27, + 0x70, 0x3e, 0xa5, 0x33, 0x73, 0x69, 0x97, 0x11, 0xf4, 0x2a, 0x2c, 0x10, 0xb9, 0xde, 0xf0, 0x30, + 0x27, 0x92, 0xe1, 0x49, 0xc9, 0xb0, 0x10, 0x4c, 0xc5, 0x5a, 0xd7, 0x0c, 0x87, 0x9a, 0x1d, 0xcc, + 0x77, 0x8c, 0xcb, 0xc4, 0xc6, 0xd6, 0xfe, 0x26, 0xb1, 0xea, 0x53, 0x24, 0x52, 0x51, 0x5f, 0x48, + 0x69, 0xc3, 0xe4, 0x84, 0xfa, 0x27, 0x00, 0x6a, 0x69, 0x51, 0x49, 0x61, 0xc3, 0x62, 0x8c, 0x82, + 0xcd, 0x81, 0xa5, 0x07, 0x4f, 0xe5, 0xab, 0x4f, 0x19, 0x89, 0xcd, 0x33, 0xa2, 0x15, 0xae, 0xf6, + 0xdc, 0x36, 0xa9, 0x69, 0x3e, 0xec, 0xf7, 0x7f, 0x94, 0x51, 0x22, 0xc4, 0xea, 0x85, 0x28, 0x23, + 0xd3, 0x1f, 0x83, 0x8f, 0x0a, 0x8c, 0x0d, 0x8b, 0x3b, 0xfd, 0x21, 0x9e, 0x0b, 0x67, 0xe3, 0xcb, + 0x92, 0xeb, 0x2d, 0x78, 0x0c, 0x07, 0x4b, 0x02, 0xe8, 0xde, 0xf7, 0x66, 0x50, 0x4e, 0x9f, 0x87, + 0x8f, 0x8b, 0x8e, 0xdb, 0x94, 0x93, 0xab, 0xd8, 0xb3, 0x09, 0x0f, 0x61, 0x6e, 0xc8, 0x93, 0x12, + 0x0b, 0x49, 0x20, 0x0c, 0xa7, 0xfa, 0x94, 0x93, 0x06, 0x0f, 0xd6, 0xff, 0x27, 0xaa, 0x7c, 0x7f, + 0xd8, 0x4a, 0x7f, 0x03, 0x2e, 0x8a, 0xf6, 0x2f, 0x13, 0xd2, 0x22, 0xde, 0x26, 0x69, 0x13, 0x5b, + 0xbc, 0x08, 0x83, 0xc3, 0xba, 0x0c, 0x8b, 0x7d, 0xdc, 0x76, 0x5a, 0x98, 0x53, 0xaf, 0x81, 0x5b, + 0x2d, 0x79, 0x6c, 0xeb, 0x85, 0x70, 0x75, 0xa3, 0xd5, 0x8a, 0x9e, 0xbf, 0x0b, 0xf0, 0x84, 0xa2, + 0xa0, 0x1c, 0xaa, 0x0c, 0xf3, 0xef, 0x89, 0x58, 0xb4, 0x1c, 0x0c, 0x96, 0xfc, 0x5a, 0xfa, 0x25, + 0x29, 0xd6, 0x15, 0x87, 0xb1, 0x97, 0x68, 0xaf, 0xcb, 0x89, 0x37, 0x36, 0xcd, 0x79, 0xa9, 0x6e, + 0xac, 0x96, 0x04, 0x79, 0x02, 0x4e, 0x75, 0x1c, 0xc6, 0x1a, 0x56, 0xb0, 0x2e, 0x4a, 0x4d, 0xd4, + 0xf3, 0x9d, 0x61, 0x6a, 0xa8, 0xce, 0x86, 0x6d, 0x7b, 0xfe, 0x1c, 0x64, 0xcb, 0x23, 0xbe, 0x7a, + 0x63, 0xf3, 0x7c, 0x0c, 0xa4, 0x3c, 0xc9, 0x8a, 0x92, 0xaa, 0x09, 0x67, 0xf0, 0x20, 0xd6, 0x70, + 0x83, 0xa0, 0xa8, 0x9a, 0xaf, 0x9a, 0x29, 0xef, 0x47, 0x58, 0x27, 0xfa, 0x36, 0xc8, 0x9a, 0xb5, + 0x09, 0xff, 0xa4, 0xd4, 0xa7, 0xf1, 0x48, 0x2f, 0xbd, 0xac, 0x80, 0x08, 0x0f, 0xe5, 0xa7, 0x00, + 0x96, 0x54, 0x19, 0x92, 0xb3, 0x05, 0x51, 0x82, 0x73, 0xf0, 0x22, 0x8f, 0x09, 0x3a, 0x33, 0x0a, + 0xca, 0xf4, 0xcb, 0xf2, 0x9a, 0x09, 0x9f, 0xde, 0xbe, 0x17, 0xf5, 0x77, 0xe5, 0xb5, 0x34, 0x52, + 0x4d, 0x4e, 0xf4, 0x36, 0x2c, 0x0e, 0x27, 0x8a, 0xc8, 0xbe, 0x9a, 0x75, 0x9a, 0xed, 0xe1, 0x28, + 0x05, 0x1c, 0x6d, 0xa1, 0x2f, 0xa6, 0x35, 0x0e, 0xd5, 0x7e, 0x1f, 0x2e, 0xa4, 0x46, 0x25, 0xd7, + 0xbb, 0xf0, 0x78, 0x9c, 0x6b, 0x20, 0xf3, 0x38, 0x60, 0xc5, 0x18, 0x18, 0xd3, 0x67, 0x21, 0x12, + 0xbd, 0xb7, 0xb0, 0x87, 0x3b, 0x21, 0xd1, 0xeb, 0xf2, 0xe2, 0x1c, 0xac, 0x4a, 0x92, 0x73, 0x70, + 0xd2, 0x15, 0x2b, 0x52, 0x99, 0xf9, 0x14, 0x80, 0xe0, 0x11, 0xd9, 0x4d, 0xa6, 0x57, 0xff, 0x3e, + 0x0e, 0x1f, 0x12, 0x05, 0xd1, 0x57, 0x00, 0x4e, 0x45, 0xd1, 0xd0, 0x99, 0x94, 0x1a, 0x2a, 0xeb, + 0xd4, 0x56, 0xb3, 0x25, 0x07, 0xb8, 0xfa, 0xda, 0x47, 0xbf, 0xfe, 0xf5, 0xe5, 0x03, 0x27, 0xd1, + 0xb2, 0x99, 0x70, 0xf3, 0xc0, 0xb0, 0x63, 0x2e, 0x84, 0xbe, 0x01, 0x70, 0x3a, 0xe6, 0x29, 0xbb, + 0xd8, 0xbd, 0x9f, 0x78, 0x55, 0x81, 0xb7, 0x8a, 0x4e, 0x67, 0xc2, 0x6b, 0x70, 0x1f, 0xe7, 0x5b, + 0x00, 0x0b, 0x31, 0x53, 0x45, 0x99, 0x7a, 0x0e, 0x36, 0x56, 0x5b, 0xcb, 0x98, 0x2d, 0x11, 0xcf, + 0x0a, 0x44, 0x03, 0xad, 0xaa, 0x10, 0xfd, 0x1f, 0x1f, 0x2c, 0x0e, 0xca, 0xd0, 0xe7, 0x00, 0x1e, + 0x93, 0xde, 0x8a, 0x56, 0x54, 0x0d, 0xe3, 0x9e, 0xac, 0x9d, 0x3c, 0x32, 0x2f, 0xeb, 0xa6, 0x06, + 0x48, 0xd2, 0x79, 0xd1, 0xd7, 0x00, 0xe6, 0x23, 0xd6, 0x8a, 0x4e, 0xab, 0xfa, 0x24, 0xad, 0x59, + 0x3b, 0x93, 0x29, 0x37, 0xeb, 0x6e, 0x06, 0x5c, 0x51, 0x3f, 0x47, 0x3f, 0x03, 0x38, 0x3d, 0xea, + 0x93, 0xc8, 0x54, 0x75, 0x55, 0x58, 0xb4, 0xf6, 0x74, 0xf6, 0x07, 0x24, 0x6b, 0x4d, 0xb0, 0xbe, + 0x80, 0xd6, 0x55, 0xac, 0xe1, 0x05, 0xca, 0xcc, 0x0f, 0xe2, 0x57, 0xec, 0x0d, 0x33, 0x70, 0x6a, + 0xf4, 0x1d, 0x80, 0xf9, 0x88, 0xab, 0xaa, 0x85, 0x4d, 0xda, 0xb8, 0x5a, 0xd8, 0x14, 0x9b, 0xd6, + 0x2f, 0x08, 0xd8, 0x75, 0xf4, 0xfc, 0x38, 0xb0, 0xbe, 0x99, 0xa3, 0x5f, 0x00, 0x9c, 0x1e, 0x35, + 0x32, 0xb5, 0xcc, 0x0a, 0xaf, 0x57, 0xcb, 0xac, 0xb2, 0x72, 0xfd, 0x8a, 0x20, 0x7f, 0x05, 0x5d, + 0x1c, 0x87, 0x3c, 0x61, 0xae, 0xe8, 0x47, 0x00, 0x67, 0x12, 0x7e, 0x8c, 0x32, 0x63, 0x85, 0xc7, + 0xba, 0x72, 0x17, 0x4f, 0xc8, 0x49, 0xce, 0x8b, 0x49, 0xce, 0xa1, 0x67, 0x33, 0x4c, 0x92, 0xfc, + 0x55, 0x80, 0x7e, 0x02, 0xb0, 0x10, 0x33, 0x37, 0xf5, 0xad, 0x95, 0x66, 0xf4, 0xea, 0x5b, 0x2b, + 0xd5, 0xc8, 0xf5, 0x4b, 0x82, 0x76, 0x13, 0xd5, 0xfe, 0x83, 0xb6, 0xe5, 0x1c, 0xa9, 0xbb, 0x10, + 0xfd, 0x07, 0x00, 0x8b, 0x71, 0x5f, 0x46, 0xd9, 0x68, 0x42, 0xb9, 0x8d, 0xac, 0xe9, 0x92, 0x7e, + 0x5d, 0xd0, 0x9f, 0x45, 0xd5, 0xbb, 0xd2, 0x3a, 0x10, 0xfa, 0x43, 0x38, 0x19, 0xf8, 0x2f, 0x5a, + 0x56, 0x75, 0x8d, 0x19, 0xbd, 0xb6, 0x72, 0x54, 0x9a, 0x84, 0x5a, 0x11, 0x50, 0x4b, 0xa8, 0xa4, + 0xbe, 0xdd, 0x84, 0xed, 0xbf, 0x76, 0xeb, 0xa0, 0x04, 0x6e, 0x1f, 0x94, 0xc0, 0x9f, 0x07, 0x25, + 0xf0, 0xc5, 0x61, 0x29, 0x77, 0xfb, 0xb0, 0x94, 0xfb, 0xed, 0xb0, 0x94, 0x7b, 0xa7, 0x92, 0xe9, + 0x6b, 0x45, 0x56, 0xe5, 0xfb, 0x2e, 0x61, 0xcd, 0x49, 0xf1, 0x2d, 0xfd, 0xcc, 0xbf, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x67, 0x1b, 0xe1, 0x93, 0xf3, 0x0f, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // ExchangeRate returns exchange rate of a pair + ExchangeRate(ctx context.Context, in *QueryExchangeRateRequest, opts ...grpc.CallOption) (*QueryExchangeRateResponse, error) + // ExchangeRateTwap returns twap exchange rate of a pair + ExchangeRateTwap(ctx context.Context, in *QueryExchangeRateRequest, opts ...grpc.CallOption) (*QueryExchangeRateResponse, error) + // ExchangeRates returns exchange rates of all pairs + ExchangeRates(ctx context.Context, in *QueryExchangeRatesRequest, opts ...grpc.CallOption) (*QueryExchangeRatesResponse, error) + // Actives returns all active pairs + Actives(ctx context.Context, in *QueryActivesRequest, opts ...grpc.CallOption) (*QueryActivesResponse, error) + // VoteTargets returns all vote target for pairs + VoteTargets(ctx context.Context, in *QueryVoteTargetsRequest, opts ...grpc.CallOption) (*QueryVoteTargetsResponse, error) + // FeederDelegation returns feeder delegation of a validator + FeederDelegation(ctx context.Context, in *QueryFeederDelegationRequest, opts ...grpc.CallOption) (*QueryFeederDelegationResponse, error) + // MissCounter returns oracle miss counter of a validator + MissCounter(ctx context.Context, in *QueryMissCounterRequest, opts ...grpc.CallOption) (*QueryMissCounterResponse, error) + // AggregatePrevote returns an aggregate prevote of a validator + AggregatePrevote(ctx context.Context, in *QueryAggregatePrevoteRequest, opts ...grpc.CallOption) (*QueryAggregatePrevoteResponse, error) + // AggregatePrevotes returns aggregate prevotes of all validators + AggregatePrevotes(ctx context.Context, in *QueryAggregatePrevotesRequest, opts ...grpc.CallOption) (*QueryAggregatePrevotesResponse, error) + // AggregateVote returns an aggregate vote of a validator + AggregateVote(ctx context.Context, in *QueryAggregateVoteRequest, opts ...grpc.CallOption) (*QueryAggregateVoteResponse, error) + // AggregateVotes returns aggregate votes of all validators + AggregateVotes(ctx context.Context, in *QueryAggregateVotesRequest, opts ...grpc.CallOption) (*QueryAggregateVotesResponse, error) + // Params queries all parameters. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) ExchangeRate(ctx context.Context, in *QueryExchangeRateRequest, opts ...grpc.CallOption) (*QueryExchangeRateResponse, error) { + out := new(QueryExchangeRateResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/ExchangeRate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ExchangeRateTwap(ctx context.Context, in *QueryExchangeRateRequest, opts ...grpc.CallOption) (*QueryExchangeRateResponse, error) { + out := new(QueryExchangeRateResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/ExchangeRateTwap", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ExchangeRates(ctx context.Context, in *QueryExchangeRatesRequest, opts ...grpc.CallOption) (*QueryExchangeRatesResponse, error) { + out := new(QueryExchangeRatesResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/ExchangeRates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Actives(ctx context.Context, in *QueryActivesRequest, opts ...grpc.CallOption) (*QueryActivesResponse, error) { + out := new(QueryActivesResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/Actives", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) VoteTargets(ctx context.Context, in *QueryVoteTargetsRequest, opts ...grpc.CallOption) (*QueryVoteTargetsResponse, error) { + out := new(QueryVoteTargetsResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/VoteTargets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) FeederDelegation(ctx context.Context, in *QueryFeederDelegationRequest, opts ...grpc.CallOption) (*QueryFeederDelegationResponse, error) { + out := new(QueryFeederDelegationResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/FeederDelegation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) MissCounter(ctx context.Context, in *QueryMissCounterRequest, opts ...grpc.CallOption) (*QueryMissCounterResponse, error) { + out := new(QueryMissCounterResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/MissCounter", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) AggregatePrevote(ctx context.Context, in *QueryAggregatePrevoteRequest, opts ...grpc.CallOption) (*QueryAggregatePrevoteResponse, error) { + out := new(QueryAggregatePrevoteResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/AggregatePrevote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) AggregatePrevotes(ctx context.Context, in *QueryAggregatePrevotesRequest, opts ...grpc.CallOption) (*QueryAggregatePrevotesResponse, error) { + out := new(QueryAggregatePrevotesResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/AggregatePrevotes", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) AggregateVote(ctx context.Context, in *QueryAggregateVoteRequest, opts ...grpc.CallOption) (*QueryAggregateVoteResponse, error) { + out := new(QueryAggregateVoteResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/AggregateVote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) AggregateVotes(ctx context.Context, in *QueryAggregateVotesRequest, opts ...grpc.CallOption) (*QueryAggregateVotesResponse, error) { + out := new(QueryAggregateVotesResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/AggregateVotes", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // ExchangeRate returns exchange rate of a pair + ExchangeRate(context.Context, *QueryExchangeRateRequest) (*QueryExchangeRateResponse, error) + // ExchangeRateTwap returns twap exchange rate of a pair + ExchangeRateTwap(context.Context, *QueryExchangeRateRequest) (*QueryExchangeRateResponse, error) + // ExchangeRates returns exchange rates of all pairs + ExchangeRates(context.Context, *QueryExchangeRatesRequest) (*QueryExchangeRatesResponse, error) + // Actives returns all active pairs + Actives(context.Context, *QueryActivesRequest) (*QueryActivesResponse, error) + // VoteTargets returns all vote target for pairs + VoteTargets(context.Context, *QueryVoteTargetsRequest) (*QueryVoteTargetsResponse, error) + // FeederDelegation returns feeder delegation of a validator + FeederDelegation(context.Context, *QueryFeederDelegationRequest) (*QueryFeederDelegationResponse, error) + // MissCounter returns oracle miss counter of a validator + MissCounter(context.Context, *QueryMissCounterRequest) (*QueryMissCounterResponse, error) + // AggregatePrevote returns an aggregate prevote of a validator + AggregatePrevote(context.Context, *QueryAggregatePrevoteRequest) (*QueryAggregatePrevoteResponse, error) + // AggregatePrevotes returns aggregate prevotes of all validators + AggregatePrevotes(context.Context, *QueryAggregatePrevotesRequest) (*QueryAggregatePrevotesResponse, error) + // AggregateVote returns an aggregate vote of a validator + AggregateVote(context.Context, *QueryAggregateVoteRequest) (*QueryAggregateVoteResponse, error) + // AggregateVotes returns aggregate votes of all validators + AggregateVotes(context.Context, *QueryAggregateVotesRequest) (*QueryAggregateVotesResponse, error) + // Params queries all parameters. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) ExchangeRate(ctx context.Context, req *QueryExchangeRateRequest) (*QueryExchangeRateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExchangeRate not implemented") +} +func (*UnimplementedQueryServer) ExchangeRateTwap(ctx context.Context, req *QueryExchangeRateRequest) (*QueryExchangeRateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExchangeRateTwap not implemented") +} +func (*UnimplementedQueryServer) ExchangeRates(ctx context.Context, req *QueryExchangeRatesRequest) (*QueryExchangeRatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExchangeRates not implemented") +} +func (*UnimplementedQueryServer) Actives(ctx context.Context, req *QueryActivesRequest) (*QueryActivesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Actives not implemented") +} +func (*UnimplementedQueryServer) VoteTargets(ctx context.Context, req *QueryVoteTargetsRequest) (*QueryVoteTargetsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VoteTargets not implemented") +} +func (*UnimplementedQueryServer) FeederDelegation(ctx context.Context, req *QueryFeederDelegationRequest) (*QueryFeederDelegationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FeederDelegation not implemented") +} +func (*UnimplementedQueryServer) MissCounter(ctx context.Context, req *QueryMissCounterRequest) (*QueryMissCounterResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MissCounter not implemented") +} +func (*UnimplementedQueryServer) AggregatePrevote(ctx context.Context, req *QueryAggregatePrevoteRequest) (*QueryAggregatePrevoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregatePrevote not implemented") +} +func (*UnimplementedQueryServer) AggregatePrevotes(ctx context.Context, req *QueryAggregatePrevotesRequest) (*QueryAggregatePrevotesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregatePrevotes not implemented") +} +func (*UnimplementedQueryServer) AggregateVote(ctx context.Context, req *QueryAggregateVoteRequest) (*QueryAggregateVoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregateVote not implemented") +} +func (*UnimplementedQueryServer) AggregateVotes(ctx context.Context, req *QueryAggregateVotesRequest) (*QueryAggregateVotesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregateVotes not implemented") +} +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_ExchangeRate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryExchangeRateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ExchangeRate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/ExchangeRate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ExchangeRate(ctx, req.(*QueryExchangeRateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ExchangeRateTwap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryExchangeRateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ExchangeRateTwap(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/ExchangeRateTwap", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ExchangeRateTwap(ctx, req.(*QueryExchangeRateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ExchangeRates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryExchangeRatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ExchangeRates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/ExchangeRates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ExchangeRates(ctx, req.(*QueryExchangeRatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Actives_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryActivesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Actives(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/Actives", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Actives(ctx, req.(*QueryActivesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_VoteTargets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryVoteTargetsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).VoteTargets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/VoteTargets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).VoteTargets(ctx, req.(*QueryVoteTargetsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_FeederDelegation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryFeederDelegationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).FeederDelegation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/FeederDelegation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).FeederDelegation(ctx, req.(*QueryFeederDelegationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_MissCounter_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryMissCounterRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).MissCounter(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/MissCounter", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).MissCounter(ctx, req.(*QueryMissCounterRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_AggregatePrevote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAggregatePrevoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).AggregatePrevote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/AggregatePrevote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).AggregatePrevote(ctx, req.(*QueryAggregatePrevoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_AggregatePrevotes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAggregatePrevotesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).AggregatePrevotes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/AggregatePrevotes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).AggregatePrevotes(ctx, req.(*QueryAggregatePrevotesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_AggregateVote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAggregateVoteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).AggregateVote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/AggregateVote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).AggregateVote(ctx, req.(*QueryAggregateVoteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_AggregateVotes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAggregateVotesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).AggregateVotes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/AggregateVotes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).AggregateVotes(ctx, req.(*QueryAggregateVotesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "archway.oracle.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ExchangeRate", + Handler: _Query_ExchangeRate_Handler, + }, + { + MethodName: "ExchangeRateTwap", + Handler: _Query_ExchangeRateTwap_Handler, + }, + { + MethodName: "ExchangeRates", + Handler: _Query_ExchangeRates_Handler, + }, + { + MethodName: "Actives", + Handler: _Query_Actives_Handler, + }, + { + MethodName: "VoteTargets", + Handler: _Query_VoteTargets_Handler, + }, + { + MethodName: "FeederDelegation", + Handler: _Query_FeederDelegation_Handler, + }, + { + MethodName: "MissCounter", + Handler: _Query_MissCounter_Handler, + }, + { + MethodName: "AggregatePrevote", + Handler: _Query_AggregatePrevote_Handler, + }, + { + MethodName: "AggregatePrevotes", + Handler: _Query_AggregatePrevotes_Handler, + }, + { + MethodName: "AggregateVote", + Handler: _Query_AggregateVote_Handler, + }, + { + MethodName: "AggregateVotes", + Handler: _Query_AggregateVotes_Handler, + }, + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "archway/oracle/v1/query.proto", +} + +func (m *QueryExchangeRateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryExchangeRateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryExchangeRateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Pair.Size() + i -= size + if _, err := m.Pair.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryExchangeRateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryExchangeRateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryExchangeRateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.ExchangeRate.Size() + i -= size + if _, err := m.ExchangeRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryExchangeRatesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryExchangeRatesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryExchangeRatesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryExchangeRatesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryExchangeRatesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryExchangeRatesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ExchangeRates) > 0 { + for iNdEx := len(m.ExchangeRates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ExchangeRates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryActivesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryActivesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryActivesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryActivesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryActivesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryActivesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Actives) > 0 { + for iNdEx := len(m.Actives) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.Actives[iNdEx].Size() + i -= size + if _, err := m.Actives[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryVoteTargetsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVoteTargetsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVoteTargetsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryVoteTargetsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVoteTargetsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVoteTargetsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.VoteTargets) > 0 { + for iNdEx := len(m.VoteTargets) - 1; iNdEx >= 0; iNdEx-- { + { + size := m.VoteTargets[iNdEx].Size() + i -= size + if _, err := m.VoteTargets[iNdEx].MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryFeederDelegationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryFeederDelegationRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryFeederDelegationRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddr) > 0 { + i -= len(m.ValidatorAddr) + copy(dAtA[i:], m.ValidatorAddr) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValidatorAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryFeederDelegationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryFeederDelegationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryFeederDelegationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.FeederAddr) > 0 { + i -= len(m.FeederAddr) + copy(dAtA[i:], m.FeederAddr) + i = encodeVarintQuery(dAtA, i, uint64(len(m.FeederAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryMissCounterRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMissCounterRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMissCounterRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddr) > 0 { + i -= len(m.ValidatorAddr) + copy(dAtA[i:], m.ValidatorAddr) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValidatorAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryMissCounterResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryMissCounterResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryMissCounterResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MissCounter != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.MissCounter)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryAggregatePrevoteRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregatePrevoteRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregatePrevoteRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddr) > 0 { + i -= len(m.ValidatorAddr) + copy(dAtA[i:], m.ValidatorAddr) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValidatorAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryAggregatePrevoteResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregatePrevoteResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregatePrevoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.AggregatePrevote.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryAggregatePrevotesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregatePrevotesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregatePrevotesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryAggregatePrevotesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregatePrevotesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregatePrevotesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AggregatePrevotes) > 0 { + for iNdEx := len(m.AggregatePrevotes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AggregatePrevotes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryAggregateVoteRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregateVoteRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregateVoteRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ValidatorAddr) > 0 { + i -= len(m.ValidatorAddr) + copy(dAtA[i:], m.ValidatorAddr) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ValidatorAddr))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryAggregateVoteResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregateVoteResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregateVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.AggregateVote.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryAggregateVotesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregateVotesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregateVotesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryAggregateVotesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryAggregateVotesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAggregateVotesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AggregateVotes) > 0 { + for iNdEx := len(m.AggregateVotes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AggregateVotes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryExchangeRateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Pair.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryExchangeRateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ExchangeRate.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryExchangeRatesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryExchangeRatesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ExchangeRates) > 0 { + for _, e := range m.ExchangeRates { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryActivesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryActivesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Actives) > 0 { + for _, e := range m.Actives { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryVoteTargetsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryVoteTargetsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.VoteTargets) > 0 { + for _, e := range m.VoteTargets { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryFeederDelegationRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValidatorAddr) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryFeederDelegationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.FeederAddr) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryMissCounterRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValidatorAddr) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryMissCounterResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MissCounter != 0 { + n += 1 + sovQuery(uint64(m.MissCounter)) + } + return n +} + +func (m *QueryAggregatePrevoteRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValidatorAddr) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryAggregatePrevoteResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.AggregatePrevote.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryAggregatePrevotesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryAggregatePrevotesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AggregatePrevotes) > 0 { + for _, e := range m.AggregatePrevotes { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryAggregateVoteRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ValidatorAddr) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryAggregateVoteResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.AggregateVote.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryAggregateVotesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryAggregateVotesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AggregateVotes) > 0 { + for _, e := range m.AggregateVotes { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryExchangeRateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryExchangeRateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryExchangeRateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pair", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Pair.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryExchangeRateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryExchangeRateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryExchangeRateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ExchangeRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryExchangeRatesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryExchangeRatesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryExchangeRatesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryExchangeRatesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryExchangeRatesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryExchangeRatesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExchangeRates = append(m.ExchangeRates, ExchangeRateTuple{}) + if err := m.ExchangeRates[len(m.ExchangeRates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryActivesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryActivesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryActivesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryActivesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryActivesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryActivesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Actives", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_archway_network_archway_x_common_asset.Pair + m.Actives = append(m.Actives, v) + if err := m.Actives[len(m.Actives)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryVoteTargetsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVoteTargetsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVoteTargetsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryVoteTargetsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVoteTargetsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVoteTargetsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VoteTargets", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_archway_network_archway_x_common_asset.Pair + m.VoteTargets = append(m.VoteTargets, v) + if err := m.VoteTargets[len(m.VoteTargets)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryFeederDelegationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryFeederDelegationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryFeederDelegationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryFeederDelegationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryFeederDelegationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryFeederDelegationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FeederAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FeederAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMissCounterRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMissCounterRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMissCounterRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryMissCounterResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryMissCounterResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryMissCounterResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MissCounter", wireType) + } + m.MissCounter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MissCounter |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregatePrevoteRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregatePrevoteRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregatePrevoteRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregatePrevoteResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregatePrevoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregatePrevoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregatePrevote", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AggregatePrevote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregatePrevotesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregatePrevotesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregatePrevotesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregatePrevotesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregatePrevotesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregatePrevotesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregatePrevotes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AggregatePrevotes = append(m.AggregatePrevotes, AggregateExchangeRatePrevote{}) + if err := m.AggregatePrevotes[len(m.AggregatePrevotes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregateVoteRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregateVoteRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregateVoteRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregateVoteResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregateVoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregateVoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregateVote", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AggregateVote.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregateVotesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregateVotesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregateVotesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAggregateVotesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryAggregateVotesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAggregateVotesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AggregateVotes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AggregateVotes = append(m.AggregateVotes, AggregateExchangeRateVote{}) + if err := m.AggregateVotes[len(m.AggregateVotes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/query.pb.gw.go b/x/oracle/types/query.pb.gw.go new file mode 100644 index 00000000..bfa0d6d9 --- /dev/null +++ b/x/oracle/types/query.pb.gw.go @@ -0,0 +1,1048 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: archway/oracle/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Query_ExchangeRate_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_ExchangeRate_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRateRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ExchangeRate_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ExchangeRate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ExchangeRate_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRateRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ExchangeRate_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ExchangeRate(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ExchangeRateTwap_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_ExchangeRateTwap_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRateRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ExchangeRateTwap_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ExchangeRateTwap(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ExchangeRateTwap_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRateRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ExchangeRateTwap_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ExchangeRateTwap(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ExchangeRates_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRatesRequest + var metadata runtime.ServerMetadata + + msg, err := client.ExchangeRates(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ExchangeRates_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryExchangeRatesRequest + var metadata runtime.ServerMetadata + + msg, err := server.ExchangeRates(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Actives_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActivesRequest + var metadata runtime.ServerMetadata + + msg, err := client.Actives(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Actives_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryActivesRequest + var metadata runtime.ServerMetadata + + msg, err := server.Actives(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_VoteTargets_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVoteTargetsRequest + var metadata runtime.ServerMetadata + + msg, err := client.VoteTargets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_VoteTargets_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVoteTargetsRequest + var metadata runtime.ServerMetadata + + msg, err := server.VoteTargets(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_FeederDelegation_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFeederDelegationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := client.FeederDelegation(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_FeederDelegation_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryFeederDelegationRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := server.FeederDelegation(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_MissCounter_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMissCounterRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := client.MissCounter(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_MissCounter_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryMissCounterRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := server.MissCounter(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_AggregatePrevote_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregatePrevoteRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := client.AggregatePrevote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_AggregatePrevote_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregatePrevoteRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := server.AggregatePrevote(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_AggregatePrevotes_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregatePrevotesRequest + var metadata runtime.ServerMetadata + + msg, err := client.AggregatePrevotes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_AggregatePrevotes_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregatePrevotesRequest + var metadata runtime.ServerMetadata + + msg, err := server.AggregatePrevotes(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_AggregateVote_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregateVoteRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := client.AggregateVote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_AggregateVote_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregateVoteRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["validator_addr"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "validator_addr") + } + + protoReq.ValidatorAddr, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "validator_addr", err) + } + + msg, err := server.AggregateVote(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_AggregateVotes_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregateVotesRequest + var metadata runtime.ServerMetadata + + msg, err := client.AggregateVotes(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_AggregateVotes_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAggregateVotesRequest + var metadata runtime.ServerMetadata + + msg, err := server.AggregateVotes(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_ExchangeRate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ExchangeRate_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ExchangeRateTwap_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ExchangeRateTwap_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRateTwap_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ExchangeRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ExchangeRates_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Actives_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Actives_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Actives_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_VoteTargets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_VoteTargets_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VoteTargets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_FeederDelegation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_FeederDelegation_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_FeederDelegation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_MissCounter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_MissCounter_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MissCounter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregatePrevote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_AggregatePrevote_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregatePrevote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregatePrevotes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_AggregatePrevotes_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregatePrevotes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregateVote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_AggregateVote_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregateVote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregateVotes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_AggregateVotes_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregateVotes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_ExchangeRate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ExchangeRate_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ExchangeRateTwap_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ExchangeRateTwap_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRateTwap_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ExchangeRates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ExchangeRates_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ExchangeRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Actives_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Actives_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Actives_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_VoteTargets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_VoteTargets_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VoteTargets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_FeederDelegation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_FeederDelegation_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_FeederDelegation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_MissCounter_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_MissCounter_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_MissCounter_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregatePrevote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_AggregatePrevote_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregatePrevote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregatePrevotes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_AggregatePrevotes_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregatePrevotes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregateVote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_AggregateVote_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregateVote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_AggregateVotes_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_AggregateVotes_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_AggregateVotes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_ExchangeRate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"archway", "oracle", "v1beta1", "exchange_rate"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ExchangeRateTwap_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"archway", "oracle", "v1beta1", "exchange_rate_twap"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ExchangeRates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"archway", "oracle", "v1beta1", "pairs", "exchange_rates"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Actives_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"archway", "oracle", "v1beta1", "pairs", "actives"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_VoteTargets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"archway", "oracle", "v1beta1", "pairs", "vote_targets"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_FeederDelegation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"archway", "oracle", "v1beta1", "validators", "validator_addr", "feeder"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_MissCounter_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"archway", "oracle", "v1beta1", "validators", "validator_addr", "miss"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_AggregatePrevote_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"archway", "oracle", "v1beta1", "validators", "validator_addr", "aggregate_prevote"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_AggregatePrevotes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"archway", "oracle", "v1beta1", "validators", "aggregate_prevotes"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_AggregateVote_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"archway", "oracle", "v1beta1", "valdiators", "validator_addr", "aggregate_vote"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_AggregateVotes_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"archway", "oracle", "v1beta1", "validators", "aggregate_votes"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"archway", "oracle", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_ExchangeRate_0 = runtime.ForwardResponseMessage + + forward_Query_ExchangeRateTwap_0 = runtime.ForwardResponseMessage + + forward_Query_ExchangeRates_0 = runtime.ForwardResponseMessage + + forward_Query_Actives_0 = runtime.ForwardResponseMessage + + forward_Query_VoteTargets_0 = runtime.ForwardResponseMessage + + forward_Query_FeederDelegation_0 = runtime.ForwardResponseMessage + + forward_Query_MissCounter_0 = runtime.ForwardResponseMessage + + forward_Query_AggregatePrevote_0 = runtime.ForwardResponseMessage + + forward_Query_AggregatePrevotes_0 = runtime.ForwardResponseMessage + + forward_Query_AggregateVote_0 = runtime.ForwardResponseMessage + + forward_Query_AggregateVotes_0 = runtime.ForwardResponseMessage + + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/x/oracle/types/state.pb.go b/x/oracle/types/state.pb.go new file mode 100644 index 00000000..f10a78b0 --- /dev/null +++ b/x/oracle/types/state.pb.go @@ -0,0 +1,410 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/state.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + github_com_archway_network_archway_x_common_asset "github.com/archway-network/archway/x/common/asset" + _ "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// a snapshot of the prices at a given point in time +type PriceSnapshot struct { + Pair github_com_archway_network_archway_x_common_asset.Pair `protobuf:"bytes,1,opt,name=pair,proto3,customtype=github.com/archway-network/archway/x/common/asset.Pair" json:"pair" yaml:"pair"` + Price cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=price,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"price"` + // milliseconds since unix epoch + TimestampMs int64 `protobuf:"varint,3,opt,name=timestamp_ms,json=timestampMs,proto3" json:"timestamp_ms,omitempty"` +} + +func (m *PriceSnapshot) Reset() { *m = PriceSnapshot{} } +func (m *PriceSnapshot) String() string { return proto.CompactTextString(m) } +func (*PriceSnapshot) ProtoMessage() {} +func (*PriceSnapshot) Descriptor() ([]byte, []int) { + return fileDescriptor_f86ab5cb19be0ee7, []int{0} +} +func (m *PriceSnapshot) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PriceSnapshot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PriceSnapshot.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PriceSnapshot) XXX_Merge(src proto.Message) { + xxx_messageInfo_PriceSnapshot.Merge(m, src) +} +func (m *PriceSnapshot) XXX_Size() int { + return m.Size() +} +func (m *PriceSnapshot) XXX_DiscardUnknown() { + xxx_messageInfo_PriceSnapshot.DiscardUnknown(m) +} + +var xxx_messageInfo_PriceSnapshot proto.InternalMessageInfo + +func (m *PriceSnapshot) GetTimestampMs() int64 { + if m != nil { + return m.TimestampMs + } + return 0 +} + +func init() { + proto.RegisterType((*PriceSnapshot)(nil), "archway.oracle.v1.PriceSnapshot") +} + +func init() { proto.RegisterFile("archway/oracle/v1/state.proto", fileDescriptor_f86ab5cb19be0ee7) } + +var fileDescriptor_f86ab5cb19be0ee7 = []byte{ + // 330 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0x31, 0x4e, 0xc3, 0x30, + 0x14, 0x86, 0x13, 0x0a, 0x48, 0xa4, 0x30, 0x10, 0x31, 0x54, 0x05, 0xd2, 0x52, 0x96, 0x2e, 0xc4, + 0x8a, 0x90, 0x90, 0x60, 0xac, 0xd8, 0xa0, 0x52, 0x55, 0x36, 0x16, 0xf4, 0x62, 0xac, 0xc4, 0x6a, + 0x9d, 0x17, 0xd9, 0x8f, 0x96, 0xdc, 0x82, 0x63, 0x75, 0xec, 0x88, 0x18, 0x2a, 0xd4, 0xde, 0x80, + 0x13, 0xa0, 0x24, 0x86, 0x85, 0x85, 0xcd, 0xf6, 0x67, 0xff, 0x9f, 0xdf, 0xef, 0x9d, 0x82, 0xe6, + 0xe9, 0x1c, 0x0a, 0x86, 0x1a, 0xf8, 0x54, 0xb0, 0x59, 0xc4, 0x0c, 0x01, 0x89, 0x30, 0xd7, 0x48, + 0xe8, 0x1f, 0x5a, 0x1c, 0xd6, 0x38, 0x9c, 0x45, 0xed, 0xa3, 0x04, 0x13, 0xac, 0x28, 0x2b, 0x57, + 0xf5, 0xc5, 0xf6, 0x49, 0x82, 0x98, 0x4c, 0x05, 0x83, 0x5c, 0x32, 0xc8, 0x32, 0x24, 0x20, 0x89, + 0x99, 0xb1, 0x34, 0xf8, 0x6b, 0xb1, 0x81, 0x96, 0x73, 0x34, 0x0a, 0x0d, 0x8b, 0xc1, 0x94, 0x30, + 0x16, 0x04, 0x11, 0xe3, 0x28, 0xb3, 0x9a, 0xf7, 0x96, 0xae, 0x77, 0x30, 0xd2, 0x92, 0x8b, 0x87, + 0x0c, 0x72, 0x93, 0x22, 0xf9, 0xe0, 0x6d, 0xe7, 0x20, 0x75, 0xcb, 0xed, 0xba, 0xfd, 0xbd, 0xc1, + 0x70, 0xb1, 0xea, 0x38, 0x1f, 0xab, 0xce, 0x55, 0x22, 0x29, 0x7d, 0x89, 0x43, 0x8e, 0x8a, 0x59, + 0xe5, 0x45, 0x26, 0x68, 0x8e, 0x7a, 0xf2, 0xb3, 0x67, 0xaf, 0x8c, 0xa3, 0x52, 0x98, 0x31, 0x30, + 0x46, 0x50, 0x38, 0x02, 0xa9, 0xbf, 0x56, 0x9d, 0x66, 0x01, 0x6a, 0x7a, 0xd3, 0x2b, 0x33, 0x7b, + 0xe3, 0x2a, 0xda, 0xbf, 0xf6, 0x76, 0xf2, 0xd2, 0xd9, 0xda, 0xaa, 0x1c, 0xe7, 0xd6, 0x71, 0x5c, + 0xff, 0xd5, 0x3c, 0x4f, 0x42, 0x89, 0x4c, 0x01, 0xa5, 0xe1, 0xbd, 0x48, 0x80, 0x17, 0xb7, 0x82, + 0x8f, 0xeb, 0x17, 0xfe, 0x99, 0xb7, 0x4f, 0x52, 0x09, 0x43, 0xa0, 0xf2, 0x27, 0x65, 0x5a, 0x8d, + 0xae, 0xdb, 0x6f, 0x8c, 0x9b, 0xbf, 0x67, 0x43, 0x33, 0xb8, 0x5b, 0xac, 0x03, 0x77, 0xb9, 0x0e, + 0xdc, 0xcf, 0x75, 0xe0, 0xbe, 0x6d, 0x02, 0x67, 0xb9, 0x09, 0x9c, 0xf7, 0x4d, 0xe0, 0x3c, 0x46, + 0xff, 0x1a, 0xc2, 0x36, 0x49, 0x45, 0x2e, 0x4c, 0xbc, 0x5b, 0xd5, 0x74, 0xf9, 0x1d, 0x00, 0x00, + 0xff, 0xff, 0x0d, 0x42, 0x44, 0x68, 0xce, 0x01, 0x00, 0x00, +} + +func (m *PriceSnapshot) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PriceSnapshot) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PriceSnapshot) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TimestampMs != 0 { + i = encodeVarintState(dAtA, i, uint64(m.TimestampMs)) + i-- + dAtA[i] = 0x18 + } + { + size := m.Price.Size() + i -= size + if _, err := m.Price.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintState(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.Pair.Size() + i -= size + if _, err := m.Pair.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintState(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintState(dAtA []byte, offset int, v uint64) int { + offset -= sovState(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *PriceSnapshot) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Pair.Size() + n += 1 + l + sovState(uint64(l)) + l = m.Price.Size() + n += 1 + l + sovState(uint64(l)) + if m.TimestampMs != 0 { + n += 1 + sovState(uint64(m.TimestampMs)) + } + return n +} + +func sovState(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozState(x uint64) (n int) { + return sovState(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *PriceSnapshot) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowState + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PriceSnapshot: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PriceSnapshot: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pair", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowState + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthState + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthState + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Pair.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Price", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowState + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthState + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthState + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Price.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimestampMs", wireType) + } + m.TimestampMs = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowState + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimestampMs |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipState(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthState + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipState(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowState + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowState + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowState + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthState + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupState + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthState + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthState = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowState = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupState = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/test_utils.go b/x/oracle/types/test_utils.go new file mode 100644 index 00000000..7204342f --- /dev/null +++ b/x/oracle/types/test_utils.go @@ -0,0 +1,181 @@ +// nolint +package types + +import ( + "context" + "errors" + "math" + "math/rand" + "time" + + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cometbft/cometbft/crypto/secp256k1" + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" +) + +// OracleDecPrecision nolint +const OracleDecPrecision = 8 + +// GenerateRandomTestCase nolint +func GenerateRandomTestCase() (rates []float64, valAddrs []sdk.ValAddress, stakingKeeper DummyStakingKeeper) { + valAddrs = []sdk.ValAddress{} + mockValidators := []MockValidator{} + + base := math.Pow10(OracleDecPrecision) + + rand.Seed(int64(time.Now().Nanosecond())) + numInputs := 10 + (rand.Int() % 100) + for i := 0; i < numInputs; i++ { + rate := float64(int64(rand.Float64()*base)) / base + rates = append(rates, rate) + + pubKey := secp256k1.GenPrivKey().PubKey() + valAddr := sdk.ValAddress(pubKey.Address()) + valAddrs = append(valAddrs, valAddr) + + power := rand.Int63()%1000 + 1 + mockValidator := NewMockValidator(valAddr, power) + mockValidators = append(mockValidators, mockValidator) + } + + stakingKeeper = NewDummyStakingKeeper(mockValidators) + + return +} + +var _ StakingKeeper = DummyStakingKeeper{} + +// DummyStakingKeeper dummy staking keeper to test votes +type DummyStakingKeeper struct { + validators []MockValidator +} + +// NewDummyStakingKeeper returns new DummyStakingKeeper instance +func NewDummyStakingKeeper(validators []MockValidator) DummyStakingKeeper { + return DummyStakingKeeper{ + validators: validators, + } +} + +// Validators nolint +func (sk DummyStakingKeeper) Validators() []MockValidator { + return sk.validators +} + +// Validator nolint +func (sk DummyStakingKeeper) Validator(ctx context.Context, address sdk.ValAddress) (stakingtypes.ValidatorI, error) { + for _, validator := range sk.validators { + op, err := sdk.ValAddressFromBech32(validator.GetOperator()) + if err != nil { + return nil, err + } + if op.Equals(address) { + return validator, nil + } + } + + return nil, errors.New("Validator not found") +} + +// TotalBondedTokens nolint +func (DummyStakingKeeper) TotalBondedTokens(_ context.Context) (sdkmath.Int, error) { + return sdkmath.ZeroInt(), nil +} + +// Slash nolint +func (DummyStakingKeeper) Slash(context.Context, sdk.ConsAddress, int64, int64, sdkmath.LegacyDec) (sdkmath.Int, error) { + return sdkmath.ZeroInt(), nil +} + +// ValidatorsPowerStoreIterator nolint +func (DummyStakingKeeper) ValidatorsPowerStoreIterator(ctx context.Context) (storetypes.Iterator, error) { + return storetypes.KVStoreReversePrefixIterator(nil, nil), nil +} + +// Jail nolint +func (DummyStakingKeeper) Jail(context.Context, sdk.ConsAddress) error { + return nil +} + +// GetLastValidatorPower nolint +func (sk DummyStakingKeeper) GetLastValidatorPower(ctx context.Context, operator sdk.ValAddress) (power int64) { + validator, err := sk.Validator(ctx, operator) + if err != nil { + panic(err) + } + + return validator.GetConsensusPower(sdk.DefaultPowerReduction) +} + +// MaxValidators returns the maximum amount of bonded validators +func (DummyStakingKeeper) MaxValidators(context.Context) (uint32, error) { + return 100, nil +} + +// PowerReduction - is the amount of staking tokens required for 1 unit of consensus-engine power +func (DummyStakingKeeper) PowerReduction(ctx context.Context) (res sdkmath.Int) { + res = sdk.DefaultPowerReduction + return +} + +// MockValidator nolint +type MockValidator struct { + power int64 + valOperAddr sdk.ValAddress +} + +var _ stakingtypes.ValidatorI = MockValidator{} + +func (MockValidator) IsJailed() bool { return false } +func (MockValidator) GetMoniker() string { return "" } +func (MockValidator) GetStatus() stakingtypes.BondStatus { return stakingtypes.Bonded } +func (MockValidator) IsBonded() bool { return true } +func (MockValidator) IsUnbonded() bool { return false } +func (MockValidator) IsUnbonding() bool { return false } +func (v MockValidator) GetOperator() string { return v.valOperAddr.String() } +func (MockValidator) ConsPubKey() (cryptotypes.PubKey, error) { return nil, nil } +func (MockValidator) TmConsPublicKey() (cmtprotocrypto.PublicKey, error) { + return cmtprotocrypto.PublicKey{}, nil +} +func (MockValidator) GetConsAddr() ([]byte, error) { return nil, nil } +func (v MockValidator) GetTokens() sdkmath.Int { + return sdk.TokensFromConsensusPower(v.power, sdk.DefaultPowerReduction) +} + +func (v MockValidator) GetBondedTokens() sdkmath.Int { + return sdk.TokensFromConsensusPower(v.power, sdk.DefaultPowerReduction) +} +func (v MockValidator) GetConsensusPower(powerReduction sdkmath.Int) int64 { return v.power } +func (v *MockValidator) SetConsensusPower(power int64) { v.power = power } +func (v MockValidator) GetCommission() sdkmath.LegacyDec { return sdkmath.LegacyZeroDec() } +func (v MockValidator) GetMinSelfDelegation() sdkmath.Int { return sdkmath.OneInt() } +func (v MockValidator) GetDelegatorShares() sdkmath.LegacyDec { return sdkmath.LegacyNewDec(v.power) } +func (v MockValidator) TokensFromShares(sdkmath.LegacyDec) sdkmath.LegacyDec { + return sdkmath.LegacyZeroDec() +} +func (v MockValidator) TokensFromSharesTruncated(sdkmath.LegacyDec) sdkmath.LegacyDec { + return sdkmath.LegacyZeroDec() +} +func (v MockValidator) TokensFromSharesRoundUp(sdkmath.LegacyDec) sdkmath.LegacyDec { + return sdkmath.LegacyZeroDec() +} +func (v MockValidator) SharesFromTokens(amt sdkmath.Int) (sdkmath.LegacyDec, error) { + return sdkmath.LegacyZeroDec(), nil +} + +func (v MockValidator) SharesFromTokensTruncated(amt sdkmath.Int) (sdkmath.LegacyDec, error) { + return sdkmath.LegacyZeroDec(), nil +} + +func NewMockValidator(valAddr sdk.ValAddress, power int64) MockValidator { + return MockValidator{ + power: power, + valOperAddr: valAddr, + } +} diff --git a/x/oracle/types/tx.pb.go b/x/oracle/types/tx.pb.go new file mode 100644 index 00000000..76d0ffa5 --- /dev/null +++ b/x/oracle/types/tx.pb.go @@ -0,0 +1,1478 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: archway/oracle/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgAggregateExchangeRatePrevote represents a message to submit +// aggregate exchange rate prevote. +type MsgAggregateExchangeRatePrevote struct { + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty" yaml:"hash"` + // Feeder is the Bech32 address of the price feeder. A validator may + // specify multiple price feeders by delegating them consent. The validator + // address is also a valid feeder by default. + Feeder string `protobuf:"bytes,2,opt,name=feeder,proto3" json:"feeder,omitempty" yaml:"feeder"` + // Validator is the Bech32 address to which the prevote will be credited. + Validator string `protobuf:"bytes,3,opt,name=validator,proto3" json:"validator,omitempty" yaml:"validator"` +} + +func (m *MsgAggregateExchangeRatePrevote) Reset() { *m = MsgAggregateExchangeRatePrevote{} } +func (m *MsgAggregateExchangeRatePrevote) String() string { return proto.CompactTextString(m) } +func (*MsgAggregateExchangeRatePrevote) ProtoMessage() {} +func (*MsgAggregateExchangeRatePrevote) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{0} +} +func (m *MsgAggregateExchangeRatePrevote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAggregateExchangeRatePrevote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAggregateExchangeRatePrevote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAggregateExchangeRatePrevote) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAggregateExchangeRatePrevote.Merge(m, src) +} +func (m *MsgAggregateExchangeRatePrevote) XXX_Size() int { + return m.Size() +} +func (m *MsgAggregateExchangeRatePrevote) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAggregateExchangeRatePrevote.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAggregateExchangeRatePrevote proto.InternalMessageInfo + +// MsgAggregateExchangeRatePrevoteResponse defines the +// Msg/AggregateExchangeRatePrevote response type. +type MsgAggregateExchangeRatePrevoteResponse struct { +} + +func (m *MsgAggregateExchangeRatePrevoteResponse) Reset() { + *m = MsgAggregateExchangeRatePrevoteResponse{} +} +func (m *MsgAggregateExchangeRatePrevoteResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAggregateExchangeRatePrevoteResponse) ProtoMessage() {} +func (*MsgAggregateExchangeRatePrevoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{1} +} +func (m *MsgAggregateExchangeRatePrevoteResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAggregateExchangeRatePrevoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAggregateExchangeRatePrevoteResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAggregateExchangeRatePrevoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAggregateExchangeRatePrevoteResponse.Merge(m, src) +} +func (m *MsgAggregateExchangeRatePrevoteResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAggregateExchangeRatePrevoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAggregateExchangeRatePrevoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAggregateExchangeRatePrevoteResponse proto.InternalMessageInfo + +// MsgAggregateExchangeRateVote represents a message to submit +// aggregate exchange rate vote. +type MsgAggregateExchangeRateVote struct { + Salt string `protobuf:"bytes,1,opt,name=salt,proto3" json:"salt,omitempty" yaml:"salt"` + ExchangeRates string `protobuf:"bytes,2,opt,name=exchange_rates,json=exchangeRates,proto3" json:"exchange_rates,omitempty" yaml:"exchange_rates"` + // Feeder is the Bech32 address of the price feeder. A validator may + // specify multiple price feeders by delegating them consent. The validator + // address is also a valid feeder by default. + Feeder string `protobuf:"bytes,3,opt,name=feeder,proto3" json:"feeder,omitempty" yaml:"feeder"` + // Validator is the Bech32 address to which the vote will be credited. + Validator string `protobuf:"bytes,4,opt,name=validator,proto3" json:"validator,omitempty" yaml:"validator"` +} + +func (m *MsgAggregateExchangeRateVote) Reset() { *m = MsgAggregateExchangeRateVote{} } +func (m *MsgAggregateExchangeRateVote) String() string { return proto.CompactTextString(m) } +func (*MsgAggregateExchangeRateVote) ProtoMessage() {} +func (*MsgAggregateExchangeRateVote) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{2} +} +func (m *MsgAggregateExchangeRateVote) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAggregateExchangeRateVote) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAggregateExchangeRateVote.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAggregateExchangeRateVote) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAggregateExchangeRateVote.Merge(m, src) +} +func (m *MsgAggregateExchangeRateVote) XXX_Size() int { + return m.Size() +} +func (m *MsgAggregateExchangeRateVote) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAggregateExchangeRateVote.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAggregateExchangeRateVote proto.InternalMessageInfo + +// MsgAggregateExchangeRateVoteResponse defines the +// Msg/AggregateExchangeRateVote response type. +type MsgAggregateExchangeRateVoteResponse struct { +} + +func (m *MsgAggregateExchangeRateVoteResponse) Reset() { *m = MsgAggregateExchangeRateVoteResponse{} } +func (m *MsgAggregateExchangeRateVoteResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAggregateExchangeRateVoteResponse) ProtoMessage() {} +func (*MsgAggregateExchangeRateVoteResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{3} +} +func (m *MsgAggregateExchangeRateVoteResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAggregateExchangeRateVoteResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAggregateExchangeRateVoteResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAggregateExchangeRateVoteResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAggregateExchangeRateVoteResponse.Merge(m, src) +} +func (m *MsgAggregateExchangeRateVoteResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAggregateExchangeRateVoteResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAggregateExchangeRateVoteResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAggregateExchangeRateVoteResponse proto.InternalMessageInfo + +// MsgDelegateFeedConsent represents a message to delegate oracle voting rights +// to another address. +type MsgDelegateFeedConsent struct { + Operator string `protobuf:"bytes,1,opt,name=operator,proto3" json:"operator,omitempty" yaml:"operator"` + Delegate string `protobuf:"bytes,2,opt,name=delegate,proto3" json:"delegate,omitempty" yaml:"delegate"` +} + +func (m *MsgDelegateFeedConsent) Reset() { *m = MsgDelegateFeedConsent{} } +func (m *MsgDelegateFeedConsent) String() string { return proto.CompactTextString(m) } +func (*MsgDelegateFeedConsent) ProtoMessage() {} +func (*MsgDelegateFeedConsent) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{4} +} +func (m *MsgDelegateFeedConsent) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDelegateFeedConsent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDelegateFeedConsent.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgDelegateFeedConsent) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDelegateFeedConsent.Merge(m, src) +} +func (m *MsgDelegateFeedConsent) XXX_Size() int { + return m.Size() +} +func (m *MsgDelegateFeedConsent) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDelegateFeedConsent.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDelegateFeedConsent proto.InternalMessageInfo + +// MsgDelegateFeedConsentResponse defines the Msg/DelegateFeedConsent response +// type. +type MsgDelegateFeedConsentResponse struct { +} + +func (m *MsgDelegateFeedConsentResponse) Reset() { *m = MsgDelegateFeedConsentResponse{} } +func (m *MsgDelegateFeedConsentResponse) String() string { return proto.CompactTextString(m) } +func (*MsgDelegateFeedConsentResponse) ProtoMessage() {} +func (*MsgDelegateFeedConsentResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_13ab59b50571d74b, []int{5} +} +func (m *MsgDelegateFeedConsentResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDelegateFeedConsentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDelegateFeedConsentResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgDelegateFeedConsentResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDelegateFeedConsentResponse.Merge(m, src) +} +func (m *MsgDelegateFeedConsentResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgDelegateFeedConsentResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDelegateFeedConsentResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDelegateFeedConsentResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgAggregateExchangeRatePrevote)(nil), "archway.oracle.v1.MsgAggregateExchangeRatePrevote") + proto.RegisterType((*MsgAggregateExchangeRatePrevoteResponse)(nil), "archway.oracle.v1.MsgAggregateExchangeRatePrevoteResponse") + proto.RegisterType((*MsgAggregateExchangeRateVote)(nil), "archway.oracle.v1.MsgAggregateExchangeRateVote") + proto.RegisterType((*MsgAggregateExchangeRateVoteResponse)(nil), "archway.oracle.v1.MsgAggregateExchangeRateVoteResponse") + proto.RegisterType((*MsgDelegateFeedConsent)(nil), "archway.oracle.v1.MsgDelegateFeedConsent") + proto.RegisterType((*MsgDelegateFeedConsentResponse)(nil), "archway.oracle.v1.MsgDelegateFeedConsentResponse") +} + +func init() { proto.RegisterFile("archway/oracle/v1/tx.proto", fileDescriptor_13ab59b50571d74b) } + +var fileDescriptor_13ab59b50571d74b = []byte{ + // 593 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x3f, 0x6f, 0x13, 0x41, + 0x10, 0xc5, 0xbd, 0xb1, 0x89, 0x92, 0x45, 0x21, 0xe4, 0x6c, 0x12, 0xfb, 0x64, 0xdd, 0x85, 0x05, + 0x11, 0x1c, 0x29, 0x77, 0xb2, 0x29, 0x90, 0x5c, 0x41, 0xf8, 0xd3, 0x20, 0x4b, 0xe8, 0x0a, 0x0a, + 0x1a, 0xb4, 0xb1, 0x87, 0xb5, 0xc5, 0xf9, 0xf6, 0x74, 0xbb, 0x38, 0x76, 0x87, 0x02, 0x05, 0x25, + 0x12, 0x2d, 0x45, 0x24, 0x3e, 0x00, 0xa9, 0xf9, 0x04, 0x94, 0x91, 0x68, 0xa8, 0x2c, 0x64, 0x23, + 0x85, 0xda, 0x1d, 0x1d, 0xba, 0xbf, 0x18, 0xdb, 0x49, 0xb0, 0xe8, 0xac, 0x7d, 0x6f, 0x66, 0x7e, + 0xf3, 0x34, 0x67, 0xac, 0x52, 0xaf, 0xde, 0xdc, 0xa7, 0x3d, 0x93, 0x7b, 0xb4, 0x6e, 0x83, 0xd9, + 0x29, 0x9b, 0xb2, 0x6b, 0xb8, 0x1e, 0x97, 0x5c, 0x59, 0x8b, 0x34, 0x23, 0xd4, 0x8c, 0x4e, 0x59, + 0xcd, 0x31, 0xce, 0x78, 0xa0, 0x9a, 0xfe, 0xaf, 0xd0, 0xa8, 0x16, 0x19, 0xe7, 0xcc, 0x06, 0x93, + 0xba, 0x2d, 0x93, 0x3a, 0x0e, 0x97, 0x54, 0xb6, 0xb8, 0x23, 0x22, 0x55, 0x9b, 0x1e, 0x11, 0x35, + 0x0c, 0xf5, 0x8d, 0x3a, 0x17, 0x6d, 0x2e, 0xcc, 0xb6, 0x60, 0xbe, 0xd6, 0x16, 0x2c, 0x14, 0xc8, + 0x67, 0x84, 0xf5, 0x9a, 0x60, 0x77, 0x19, 0xf3, 0x80, 0x51, 0x09, 0x0f, 0xba, 0xf5, 0x26, 0x75, + 0x18, 0x58, 0x54, 0xc2, 0x63, 0x0f, 0x3a, 0x5c, 0x82, 0x72, 0x0d, 0x67, 0x9a, 0x54, 0x34, 0xf3, + 0x68, 0x13, 0xdd, 0x5c, 0xde, 0x5d, 0x1d, 0xf5, 0xf5, 0x8b, 0x3d, 0xda, 0xb6, 0xab, 0xc4, 0x7f, + 0x25, 0x56, 0x20, 0x2a, 0x25, 0xbc, 0xf8, 0x1c, 0xa0, 0x01, 0x5e, 0x7e, 0x21, 0xb0, 0xad, 0x8d, + 0xfa, 0xfa, 0x4a, 0x68, 0x0b, 0xdf, 0x89, 0x15, 0x19, 0x94, 0x0a, 0x5e, 0xee, 0x50, 0xbb, 0xd5, + 0xa0, 0x92, 0x7b, 0xf9, 0x74, 0xe0, 0xce, 0x8d, 0xfa, 0xfa, 0xe5, 0xd0, 0x9d, 0x48, 0xc4, 0xfa, + 0x63, 0xab, 0x66, 0xdf, 0x1e, 0xea, 0xa9, 0x9f, 0x87, 0x7a, 0xea, 0xe0, 0xe4, 0x68, 0x3b, 0x6a, + 0x44, 0x4a, 0x78, 0xeb, 0x1c, 0x76, 0x0b, 0x84, 0xcb, 0x1d, 0x01, 0xe4, 0x17, 0xc2, 0xc5, 0xd3, + 0xbc, 0x4f, 0xa2, 0x25, 0x05, 0xb5, 0xe5, 0xf4, 0x92, 0xfe, 0x2b, 0xb1, 0x02, 0x51, 0xb9, 0x83, + 0x2f, 0x41, 0x54, 0xf8, 0xcc, 0xa3, 0x12, 0x44, 0xb4, 0x6c, 0x61, 0xd4, 0xd7, 0xaf, 0x84, 0xf6, + 0xbf, 0x75, 0x62, 0xad, 0xc0, 0xd8, 0x24, 0x31, 0x16, 0x53, 0x7a, 0xae, 0x98, 0x32, 0xff, 0x11, + 0xd3, 0x0d, 0x7c, 0xfd, 0xac, 0xd5, 0x93, 0x8c, 0xde, 0x20, 0xbc, 0x5e, 0x13, 0xec, 0x3e, 0xd8, + 0x81, 0xef, 0x21, 0x40, 0xe3, 0x9e, 0x2f, 0x38, 0x52, 0x31, 0xf1, 0x12, 0x77, 0xc1, 0x0b, 0x50, + 0xc2, 0x84, 0xb2, 0xa3, 0xbe, 0xbe, 0x1a, 0xa2, 0xc4, 0x0a, 0xb1, 0x12, 0x93, 0x5f, 0xd0, 0x88, + 0xfa, 0x44, 0x19, 0x8d, 0x15, 0xc4, 0x0a, 0xb1, 0x12, 0x53, 0x75, 0x29, 0x26, 0x27, 0x9b, 0x58, + 0x9b, 0x4d, 0x11, 0x83, 0x56, 0x5e, 0x67, 0x70, 0xba, 0x26, 0x98, 0xf2, 0x09, 0xe1, 0xe2, 0x99, + 0x97, 0x5b, 0x31, 0xa6, 0x3e, 0x2f, 0xe3, 0x9c, 0x8b, 0x51, 0xab, 0xf3, 0xd7, 0x24, 0x09, 0xea, + 0x07, 0x5f, 0x7f, 0xbc, 0x5f, 0x28, 0x90, 0x0d, 0x73, 0xe2, 0x7b, 0x74, 0x23, 0xa0, 0x8f, 0x08, + 0x17, 0x4e, 0xbf, 0x41, 0x73, 0x8e, 0xd1, 0x7e, 0x81, 0x7a, 0x7b, 0xce, 0x82, 0x04, 0xb4, 0x18, + 0x80, 0xae, 0x93, 0xdc, 0x24, 0x68, 0x40, 0xf9, 0x01, 0xe1, 0xec, 0xac, 0x2b, 0x28, 0xcd, 0x1e, + 0x37, 0xc3, 0xaa, 0x96, 0xff, 0xd9, 0x9a, 0x30, 0x6d, 0x05, 0x4c, 0x57, 0x89, 0x3e, 0xc9, 0x14, + 0x9e, 0xf1, 0x4e, 0x7c, 0x2a, 0xea, 0x85, 0x57, 0x27, 0x47, 0xdb, 0x68, 0xf7, 0xd1, 0x97, 0x81, + 0x86, 0x8e, 0x07, 0x1a, 0xfa, 0x3e, 0xd0, 0xd0, 0xbb, 0xa1, 0x96, 0x3a, 0x1e, 0x6a, 0xa9, 0x6f, + 0x43, 0x2d, 0xf5, 0xb4, 0xcc, 0x5a, 0xb2, 0xf9, 0x72, 0xcf, 0xa8, 0xf3, 0x76, 0xdc, 0x6b, 0xc7, + 0x01, 0xb9, 0xcf, 0xbd, 0x17, 0x49, 0xef, 0x6e, 0xdc, 0x5d, 0xf6, 0x5c, 0x10, 0x7b, 0x8b, 0xc1, + 0xdf, 0xe1, 0xad, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x07, 0x7a, 0x33, 0xf2, 0xac, 0x05, 0x00, + 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // AggregateExchangeRatePrevote defines a method for submitting + // aggregate exchange rate prevote + AggregateExchangeRatePrevote(ctx context.Context, in *MsgAggregateExchangeRatePrevote, opts ...grpc.CallOption) (*MsgAggregateExchangeRatePrevoteResponse, error) + // AggregateExchangeRateVote defines a method for submitting + // aggregate exchange rate vote + AggregateExchangeRateVote(ctx context.Context, in *MsgAggregateExchangeRateVote, opts ...grpc.CallOption) (*MsgAggregateExchangeRateVoteResponse, error) + // DelegateFeedConsent defines a method for delegating oracle voting rights + // to another address known as a price feeder. + // See https://github.com/NibiruChain/pricefeeder. + DelegateFeedConsent(ctx context.Context, in *MsgDelegateFeedConsent, opts ...grpc.CallOption) (*MsgDelegateFeedConsentResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) AggregateExchangeRatePrevote(ctx context.Context, in *MsgAggregateExchangeRatePrevote, opts ...grpc.CallOption) (*MsgAggregateExchangeRatePrevoteResponse, error) { + out := new(MsgAggregateExchangeRatePrevoteResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Msg/AggregateExchangeRatePrevote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) AggregateExchangeRateVote(ctx context.Context, in *MsgAggregateExchangeRateVote, opts ...grpc.CallOption) (*MsgAggregateExchangeRateVoteResponse, error) { + out := new(MsgAggregateExchangeRateVoteResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Msg/AggregateExchangeRateVote", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) DelegateFeedConsent(ctx context.Context, in *MsgDelegateFeedConsent, opts ...grpc.CallOption) (*MsgDelegateFeedConsentResponse, error) { + out := new(MsgDelegateFeedConsentResponse) + err := c.cc.Invoke(ctx, "/archway.oracle.v1.Msg/DelegateFeedConsent", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // AggregateExchangeRatePrevote defines a method for submitting + // aggregate exchange rate prevote + AggregateExchangeRatePrevote(context.Context, *MsgAggregateExchangeRatePrevote) (*MsgAggregateExchangeRatePrevoteResponse, error) + // AggregateExchangeRateVote defines a method for submitting + // aggregate exchange rate vote + AggregateExchangeRateVote(context.Context, *MsgAggregateExchangeRateVote) (*MsgAggregateExchangeRateVoteResponse, error) + // DelegateFeedConsent defines a method for delegating oracle voting rights + // to another address known as a price feeder. + // See https://github.com/NibiruChain/pricefeeder. + DelegateFeedConsent(context.Context, *MsgDelegateFeedConsent) (*MsgDelegateFeedConsentResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) AggregateExchangeRatePrevote(ctx context.Context, req *MsgAggregateExchangeRatePrevote) (*MsgAggregateExchangeRatePrevoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregateExchangeRatePrevote not implemented") +} +func (*UnimplementedMsgServer) AggregateExchangeRateVote(ctx context.Context, req *MsgAggregateExchangeRateVote) (*MsgAggregateExchangeRateVoteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AggregateExchangeRateVote not implemented") +} +func (*UnimplementedMsgServer) DelegateFeedConsent(ctx context.Context, req *MsgDelegateFeedConsent) (*MsgDelegateFeedConsentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DelegateFeedConsent not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_AggregateExchangeRatePrevote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAggregateExchangeRatePrevote) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AggregateExchangeRatePrevote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Msg/AggregateExchangeRatePrevote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AggregateExchangeRatePrevote(ctx, req.(*MsgAggregateExchangeRatePrevote)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_AggregateExchangeRateVote_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAggregateExchangeRateVote) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AggregateExchangeRateVote(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Msg/AggregateExchangeRateVote", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AggregateExchangeRateVote(ctx, req.(*MsgAggregateExchangeRateVote)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_DelegateFeedConsent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgDelegateFeedConsent) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).DelegateFeedConsent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/archway.oracle.v1.Msg/DelegateFeedConsent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).DelegateFeedConsent(ctx, req.(*MsgDelegateFeedConsent)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "archway.oracle.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AggregateExchangeRatePrevote", + Handler: _Msg_AggregateExchangeRatePrevote_Handler, + }, + { + MethodName: "AggregateExchangeRateVote", + Handler: _Msg_AggregateExchangeRateVote_Handler, + }, + { + MethodName: "DelegateFeedConsent", + Handler: _Msg_DelegateFeedConsent_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "archway/oracle/v1/tx.proto", +} + +func (m *MsgAggregateExchangeRatePrevote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAggregateExchangeRatePrevote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAggregateExchangeRatePrevote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0x1a + } + if len(m.Feeder) > 0 { + i -= len(m.Feeder) + copy(dAtA[i:], m.Feeder) + i = encodeVarintTx(dAtA, i, uint64(len(m.Feeder))) + i-- + dAtA[i] = 0x12 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintTx(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAggregateExchangeRatePrevoteResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAggregateExchangeRatePrevoteResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAggregateExchangeRatePrevoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgAggregateExchangeRateVote) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAggregateExchangeRateVote) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAggregateExchangeRateVote) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Validator) > 0 { + i -= len(m.Validator) + copy(dAtA[i:], m.Validator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Validator))) + i-- + dAtA[i] = 0x22 + } + if len(m.Feeder) > 0 { + i -= len(m.Feeder) + copy(dAtA[i:], m.Feeder) + i = encodeVarintTx(dAtA, i, uint64(len(m.Feeder))) + i-- + dAtA[i] = 0x1a + } + if len(m.ExchangeRates) > 0 { + i -= len(m.ExchangeRates) + copy(dAtA[i:], m.ExchangeRates) + i = encodeVarintTx(dAtA, i, uint64(len(m.ExchangeRates))) + i-- + dAtA[i] = 0x12 + } + if len(m.Salt) > 0 { + i -= len(m.Salt) + copy(dAtA[i:], m.Salt) + i = encodeVarintTx(dAtA, i, uint64(len(m.Salt))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAggregateExchangeRateVoteResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAggregateExchangeRateVoteResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAggregateExchangeRateVoteResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgDelegateFeedConsent) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgDelegateFeedConsent) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDelegateFeedConsent) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Delegate) > 0 { + i -= len(m.Delegate) + copy(dAtA[i:], m.Delegate) + i = encodeVarintTx(dAtA, i, uint64(len(m.Delegate))) + i-- + dAtA[i] = 0x12 + } + if len(m.Operator) > 0 { + i -= len(m.Operator) + copy(dAtA[i:], m.Operator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Operator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgDelegateFeedConsentResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgDelegateFeedConsentResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDelegateFeedConsentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgAggregateExchangeRatePrevote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Feeder) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAggregateExchangeRatePrevoteResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgAggregateExchangeRateVote) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Salt) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ExchangeRates) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Feeder) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAggregateExchangeRateVoteResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgDelegateFeedConsent) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Operator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Delegate) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgDelegateFeedConsentResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgAggregateExchangeRatePrevote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAggregateExchangeRatePrevote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAggregateExchangeRatePrevote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Feeder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Feeder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAggregateExchangeRatePrevoteResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAggregateExchangeRatePrevoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAggregateExchangeRatePrevoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAggregateExchangeRateVote) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAggregateExchangeRateVote: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAggregateExchangeRateVote: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Salt", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Salt = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExchangeRates", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExchangeRates = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Feeder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Feeder = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAggregateExchangeRateVoteResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAggregateExchangeRateVoteResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAggregateExchangeRateVoteResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDelegateFeedConsent) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgDelegateFeedConsent: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDelegateFeedConsent: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Delegate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Delegate = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDelegateFeedConsentResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgDelegateFeedConsentResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDelegateFeedConsentResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/tx.pb.gw.go b/x/oracle/types/tx.pb.gw.go new file mode 100644 index 00000000..0cab5f9f --- /dev/null +++ b/x/oracle/types/tx.pb.gw.go @@ -0,0 +1,337 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: archway/oracle/v1/tx.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Msg_AggregateExchangeRatePrevote_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_AggregateExchangeRatePrevote_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgAggregateExchangeRatePrevote + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_AggregateExchangeRatePrevote_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AggregateExchangeRatePrevote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_AggregateExchangeRatePrevote_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgAggregateExchangeRatePrevote + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_AggregateExchangeRatePrevote_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AggregateExchangeRatePrevote(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Msg_AggregateExchangeRateVote_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_AggregateExchangeRateVote_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgAggregateExchangeRateVote + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_AggregateExchangeRateVote_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.AggregateExchangeRateVote(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_AggregateExchangeRateVote_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgAggregateExchangeRateVote + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_AggregateExchangeRateVote_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.AggregateExchangeRateVote(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Msg_DelegateFeedConsent_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_DelegateFeedConsent_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgDelegateFeedConsent + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_DelegateFeedConsent_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DelegateFeedConsent(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_DelegateFeedConsent_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgDelegateFeedConsent + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_DelegateFeedConsent_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DelegateFeedConsent(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterMsgHandlerServer registers the http handlers for service Msg to "mux". +// UnaryRPC :call MsgServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterMsgHandlerFromEndpoint instead. +func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server MsgServer) error { + + mux.Handle("POST", pattern_Msg_AggregateExchangeRatePrevote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_AggregateExchangeRatePrevote_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_AggregateExchangeRatePrevote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_AggregateExchangeRateVote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_AggregateExchangeRateVote_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_AggregateExchangeRateVote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_DelegateFeedConsent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_DelegateFeedConsent_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_DelegateFeedConsent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterMsgHandlerFromEndpoint is same as RegisterMsgHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterMsgHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterMsgHandler(ctx, mux, conn) +} + +// RegisterMsgHandler registers the http handlers for service Msg to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterMsgHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterMsgHandlerClient(ctx, mux, NewMsgClient(conn)) +} + +// RegisterMsgHandlerClient registers the http handlers for service Msg +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "MsgClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "MsgClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "MsgClient" to call the correct interceptors. +func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client MsgClient) error { + + mux.Handle("POST", pattern_Msg_AggregateExchangeRatePrevote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_AggregateExchangeRatePrevote_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_AggregateExchangeRatePrevote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_AggregateExchangeRateVote_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_AggregateExchangeRateVote_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_AggregateExchangeRateVote_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Msg_DelegateFeedConsent_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_DelegateFeedConsent_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_DelegateFeedConsent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Msg_AggregateExchangeRatePrevote_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"archway", "oracle", "prevote"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Msg_AggregateExchangeRateVote_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"archway", "oracle", "vote"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Msg_DelegateFeedConsent_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"archway", "oracle", "feeder-delegate"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Msg_AggregateExchangeRatePrevote_0 = runtime.ForwardResponseMessage + + forward_Msg_AggregateExchangeRateVote_0 = runtime.ForwardResponseMessage + + forward_Msg_DelegateFeedConsent_0 = runtime.ForwardResponseMessage +) diff --git a/x/oracle/types/vote.go b/x/oracle/types/vote.go new file mode 100644 index 00000000..85c73b19 --- /dev/null +++ b/x/oracle/types/vote.go @@ -0,0 +1,157 @@ +package types + +import ( + "fmt" + "strings" + + "cosmossdk.io/math" + + "github.com/archway-network/archway/x/common/asset" + "github.com/archway-network/archway/x/common/set" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + ExchangeRateTuplesSeparator = "|" + ExchangeRateTupleStringPrefix = '(' + ExchangeRateTupleStringSuffix = ')' + ExchangeRateTuplePairRateSeparator = "," +) + +// NewAggregateExchangeRatePrevote returns AggregateExchangeRatePrevote object +func NewAggregateExchangeRatePrevote(hash AggregateVoteHash, voter sdk.ValAddress, submitBlock uint64) AggregateExchangeRatePrevote { + return AggregateExchangeRatePrevote{ + Hash: hash.String(), + Voter: voter.String(), + SubmitBlock: submitBlock, + } +} + +// NewAggregateExchangeRateVote creates a AggregateExchangeRateVote instance +func NewAggregateExchangeRateVote(exchangeRateTuples ExchangeRateTuples, voter sdk.ValAddress) AggregateExchangeRateVote { + return AggregateExchangeRateVote{ + ExchangeRateTuples: exchangeRateTuples, + Voter: voter.String(), + } +} + +// NewExchangeRateTuple creates a ExchangeRateTuple instance +func NewExchangeRateTuple(pair asset.Pair, exchangeRate math.LegacyDec) ExchangeRateTuple { + return ExchangeRateTuple{ + pair, + exchangeRate, + } +} + +// ToString converts the ExchangeRateTuple to the vote string. +func (m ExchangeRateTuple) ToString() (string, error) { + err := m.Pair.Validate() + if err != nil { + return "", err + } + + return fmt.Sprintf( + "%c%s%s%s%c", + ExchangeRateTupleStringPrefix, + m.Pair, + ExchangeRateTuplePairRateSeparator, + m.ExchangeRate.String(), + ExchangeRateTupleStringSuffix, + ), nil +} + +// NewExchangeRateTupleFromString populates ExchangeRateTuple from a string, fails if the string is of invalid format. +func NewExchangeRateTupleFromString(s string) (ExchangeRateTuple, error) { + // strip parentheses + if len(s) <= 2 { + return ExchangeRateTuple{}, fmt.Errorf("invalid string length: %v", len(s)) + } + + if s[0] != ExchangeRateTupleStringPrefix || s[len(s)-1] != ExchangeRateTupleStringSuffix { + return ExchangeRateTuple{}, fmt.Errorf("invalid ExchangeRateTuple delimiters, start is expected with '(', end with ')', got: %s", s) + } + + stripParentheses := s[1 : len(s)-1] + split := strings.Split(stripParentheses, ExchangeRateTuplePairRateSeparator) + if len(split) != 2 { + return ExchangeRateTuple{}, fmt.Errorf("invalid ExchangeRateTuple format") + } + + pair, err := asset.TryNewPair(split[0]) + if err != nil { + return ExchangeRateTuple{}, fmt.Errorf("invalid pair definition %s: %w", split[0], err) + } + + dec, err := math.LegacyNewDecFromStr(split[1]) + if err != nil { + return ExchangeRateTuple{}, fmt.Errorf("invalid decimal %s: %w", split[1], err) + } + + return ExchangeRateTuple{ + Pair: pair, + ExchangeRate: dec, + }, nil +} + +// ExchangeRateTuples - array of ExchangeRateTuple +type ExchangeRateTuples []ExchangeRateTuple + +func (tuples ExchangeRateTuples) ToMap() (exchangeRateMap map[asset.Pair]math.LegacyDec) { + exchangeRateMap = make(map[asset.Pair]math.LegacyDec) + for _, tuple := range tuples { + exchangeRateMap[tuple.Pair] = tuple.ExchangeRate + } + return exchangeRateMap +} + +func NewExchangeRateTuplesFromString(s string) (ExchangeRateTuples, error) { + stringTuples := strings.Split(s, ExchangeRateTuplesSeparator) + + tuples := make(ExchangeRateTuples, len(stringTuples)) + + duplicates := make(set.Set[asset.Pair], len(stringTuples)) + + for i, stringTuple := range stringTuples { + exchangeRate, err := NewExchangeRateTupleFromString(stringTuple) + if err != nil { + return []ExchangeRateTuple{}, fmt.Errorf("invalid ExchangeRateTuple at index %d: %w", i, err) + } + + // check duplicates + if _, ok := duplicates[exchangeRate.Pair]; ok { + return []ExchangeRateTuple{}, fmt.Errorf("found duplicate at index %d: %s", i, exchangeRate.Pair) + } else { + duplicates[exchangeRate.Pair] = struct{}{} + } + + // insert exchange rate into the tuple + tuples[i] = exchangeRate + } + + return tuples, nil +} + +func (tuples ExchangeRateTuples) ToString() (string, error) { + tuplesStringSlice := make([]string, len(tuples)) + for i, r := range tuples { + rStr, err := r.ToString() + if err != nil { + return "", fmt.Errorf("invalid ExchangeRateTuple at index %d: %w", i, err) + } + + tuplesStringSlice[i] = rStr + } + + return strings.Join(tuplesStringSlice, "|"), nil +} + +// ParseExchangeRateTuples ExchangeRateTuple parser +func ParseExchangeRateTuples(tuplesStr string) (ExchangeRateTuples, error) { + tuples, err := NewExchangeRateTuplesFromString(tuplesStr) + if err != nil { + return nil, err + } + + return tuples, nil +} diff --git a/x/oracle/types/vote_test.go b/x/oracle/types/vote_test.go new file mode 100644 index 00000000..04069016 --- /dev/null +++ b/x/oracle/types/vote_test.go @@ -0,0 +1,85 @@ +package types_test + +import ( + "testing" + + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + + "github.com/archway-network/archway/x/oracle/types" +) + +func TestExchangeRateTuples_ToString(t *testing.T) { + t.Run("inverse", func(t *testing.T) { + tuples := types.ExchangeRateTuples{ + { + Pair: "BTC:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("40000.00"), + }, + + { + Pair: "ETH:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("4000.00"), + }, + } + + tuplesStr, err := tuples.ToString() + require.NoError(t, err) + + parsedTuples, err := types.NewExchangeRateTuplesFromString(tuplesStr) + require.NoError(t, err) + + require.Equal(t, tuples, parsedTuples) + }) + + t.Run("check duplicates", func(t *testing.T) { + tuples := types.ExchangeRateTuples{ + { + Pair: "BTC:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("40000.00"), + }, + + { + Pair: "BTC:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("4000.00"), + }, + } + + tuplesStr, err := tuples.ToString() + require.NoError(t, err) + + _, err = types.NewExchangeRateTuplesFromString(tuplesStr) + require.ErrorContains(t, err, "found duplicate") + }) +} + +func TestExchangeRateTuple(t *testing.T) { + t.Run("inverse", func(t *testing.T) { + exchangeRate := types.ExchangeRateTuple{ + Pair: "BTC:USD", + ExchangeRate: math.LegacyMustNewDecFromStr("40000.00"), + } + exchangeRateStr, err := exchangeRate.ToString() + require.NoError(t, err) + + parsedExchangeRate, err := types.NewExchangeRateTupleFromString(exchangeRateStr) + require.NoError(t, err) + + require.Equal(t, exchangeRate, parsedExchangeRate) + }) + + t.Run("invalid size", func(t *testing.T) { + _, err := types.NewExchangeRateTupleFromString("00") + require.ErrorContains(t, err, "invalid string length") + }) + + t.Run("invalid delimiters", func(t *testing.T) { + _, err := types.NewExchangeRateTupleFromString("|1000.0,nibi:usd|") + require.ErrorContains(t, err, "invalid ExchangeRateTuple delimiters") + }) + + t.Run("invalid format", func(t *testing.T) { + _, err := types.NewExchangeRateTupleFromString("(1000.0,nibi:usd,1000.0)") + require.ErrorContains(t, err, "invalid ExchangeRateTuple format") + }) +} diff --git a/x/rewards/types/events.pb.go b/x/rewards/types/events.pb.go index 4ad8eb3e..7f38d842 100644 --- a/x/rewards/types/events.pb.go +++ b/x/rewards/types/events.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/rewards/v1/events.proto diff --git a/x/rewards/types/genesis.pb.go b/x/rewards/types/genesis.pb.go index d88ba053..21fe8bb6 100644 --- a/x/rewards/types/genesis.pb.go +++ b/x/rewards/types/genesis.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/rewards/v1/genesis.proto diff --git a/x/rewards/types/query.pb.go b/x/rewards/types/query.pb.go index f2f58a9f..f61c4cd2 100644 --- a/x/rewards/types/query.pb.go +++ b/x/rewards/types/query.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/rewards/v1/query.proto diff --git a/x/rewards/types/query.pb.gw.go b/x/rewards/types/query.pb.gw.go index cf4bea30..c2869094 100644 --- a/x/rewards/types/query.pb.gw.go +++ b/x/rewards/types/query.pb.gw.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. // source: archway/rewards/v1/query.proto diff --git a/x/rewards/types/rewards.pb.go b/x/rewards/types/rewards.pb.go index fbf6a709..b5c30b57 100644 --- a/x/rewards/types/rewards.pb.go +++ b/x/rewards/types/rewards.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/rewards/v1/rewards.proto diff --git a/x/rewards/types/tx.pb.go b/x/rewards/types/tx.pb.go index 62cfa71d..a66c37b0 100644 --- a/x/rewards/types/tx.pb.go +++ b/x/rewards/types/tx.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/rewards/v1/tx.proto diff --git a/x/tracking/types/genesis.pb.go b/x/tracking/types/genesis.pb.go index ae8b10b9..6b57e5f9 100644 --- a/x/tracking/types/genesis.pb.go +++ b/x/tracking/types/genesis.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/tracking/v1/genesis.proto diff --git a/x/tracking/types/query.pb.go b/x/tracking/types/query.pb.go index fa952624..9f83ae5b 100644 --- a/x/tracking/types/query.pb.go +++ b/x/tracking/types/query.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/tracking/v1/query.proto diff --git a/x/tracking/types/query.pb.gw.go b/x/tracking/types/query.pb.gw.go index 9e3d0c67..04c2314d 100644 --- a/x/tracking/types/query.pb.gw.go +++ b/x/tracking/types/query.pb.gw.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. // source: archway/tracking/v1/query.proto diff --git a/x/tracking/types/tracking.pb.go b/x/tracking/types/tracking.pb.go index 21976757..099528cb 100644 --- a/x/tracking/types/tracking.pb.go +++ b/x/tracking/types/tracking.pb.go @@ -1,4 +1,3 @@ -// DONTCOVER // Code generated by protoc-gen-gogo. DO NOT EDIT. // source: archway/tracking/v1/tracking.proto