diff --git a/Makefile b/Makefile index 9b63c6558..6d46f90be 100644 --- a/Makefile +++ b/Makefile @@ -347,7 +347,7 @@ docusaurus_start: check_npm check_node ## Start the Docusaurus server .PHONY: docs_update_gov_params_page docs_update_gov_params_page: ## Update the page in Docusaurus documenting all the governance parameters - go run tools/scripts/generate_docs_params.go + go run tools/scripts/docusaurus/generate_docs_params.go ###################### ### Ignite Helpers ### diff --git a/docusaurus/docs/develop/developer_guide/adding_params.md b/docusaurus/docs/develop/developer_guide/adding_params.md index 38f80ac63..37ff71d1f 100644 --- a/docusaurus/docs/develop/developer_guide/adding_params.md +++ b/docusaurus/docs/develop/developer_guide/adding_params.md @@ -56,88 +56,13 @@ message Params { } ``` -### 2 Update the Parameter E2E Tests - -Update the E2E test files (e.g., `update_params.feature` and `update_params_test.go`) -to include scenarios that test the new parameter. - -#### 2.1 Scenario Example - -```gherkin -# NB: If you are reading this and the proof module has parameters -# that are not being updated in this test, please update the test. -Scenario: An authorized user updates all "proof" module params - Given the user has the pocketd binary installed - And all "proof" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.proof.MsgUpdateParams" message exists - When the "pnf" account sends an authz exec message to update all "proof" module params - | name | value | type | - | new_parameter_name | 100 | int64 | - Then all "proof" module params should be updated -``` - -#### 2.2 Scenario Outline Example - -```gherkin -# NB: If you are reading this and any module has parameters that -# are not being updated in this test, please update the test. -Scenario Outline: An authorized user updates individual module params - Given the user has the pocketd binary installed - And all "" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "" message exists - When the "pnf" account sends an authz exec message to update "" the module param - | name | value | type | - | | | | - Then the "" module param "" should be updated - - Examples: - | module | message_type | param_name | param_value | param_type | - | proof | /poktroll.proof.MsgUpdateParam | new_parameter_name | 100 | int64 | -``` - -#### 2.3 Step Definition Helpers Example - -The related changes to the step definition, presented below via an example, -should be made in `e2e/tests/parse_params_test.go`. - -```go -func (s *suite) newProofMsgUpdateParams(params paramsMap) cosmostypes.Msg { - msgUpdateParams := &prooftypes.MsgUpdateParam{ - Params: &prooftypes.Params{}, - } - - for paramName, paramValue := range params { - switch paramName { - case prooftypes.ParamNewParameterName: - msgUpdateParams.Params.NewParameterName = uint64(paramValue.value.(int64)) - default: - s.Fatalf("unexpected %q type param name %q", paramValue.typeStr, paramName) - } - } +### 2 Update the Parameter Integration Tests - return msgUpdateParams -} -``` - -#### 2.4 Update switch statement to support new param - -The related changes to the step definition, presented below via an example, -should be made in `e2e/tests/parse_params_test.go`. - -```go -case prooftypes.ModuleName: - params := prooftypes.DefaultParams() - paramsMap := s.expectedModuleParams[moduleName] - - newParameter, ok := paramsMap[prooftypes.ParamNewParameterName] - if ok { - params.NewParameter = uint64(newParameter.value.(int64)) - } -``` +// TODO_DOCUMENT(@bryanchriswhite, #826) ### 3. Update the Default Parameter Values -In the corresponding Go file (e.g., `params.go`), define the default value, key, and parameter name for the +In the corresponding Go file (e.g., `x//types/params.go`), define the default value, key, and parameter name for the new parameter and include the default in the `NewParams` and `DefaultParams` functions. ```go diff --git a/docusaurus/docs/develop/developer_guide/test_suites.md b/docusaurus/docs/develop/developer_guide/test_suites.md new file mode 100644 index 000000000..bbe6a63df --- /dev/null +++ b/docusaurus/docs/develop/developer_guide/test_suites.md @@ -0,0 +1,6 @@ +--- +sidebar_position: 5 +title: Test Suites +--- + +// TODO_DOCUMENT(@bryanchriswhite) \ No newline at end of file diff --git a/docusaurus/docs/protocol/governance/params.md b/docusaurus/docs/protocol/governance/params.md index 30ca4da81..37400c7e4 100644 --- a/docusaurus/docs/protocol/governance/params.md +++ b/docusaurus/docs/protocol/governance/params.md @@ -13,6 +13,14 @@ DO NOT EDIT: this file was generated by make docs_update_gov_params_page - [Adding a new parameter](#adding-a-new-parameter) - [Parameters](#parameters) +## Access Control + +// TODO_DOCUMENT(@bryanchriswhite) tl;dr, authz. + +## Updating governance parameter values + +// TODO_DOCUMENT(@bryanchriswhite) + ## Updating this page ``` @@ -27,12 +35,9 @@ Please follow the instructions in [this guide](../../develop/developer_guide/add |Module | Field Type | Field Name |Comment | -|-------|------------|------------|--------| -| `application` | `uint64 ` | `max_delegated_gateways` | max_delegated_gateways defines the maximum number of gateways that a single application can delegate to. This is used to prevent performance issues in case the relay ring signature becomes too large. | +|-------|------------|------------|--------|| `application` | `uint64 ` | `max_delegated_gateways` | max_delegated_gateways defines the maximum number of gateways that a single application can delegate to. This is used to prevent performance issues in case the relay ring signature becomes too large. | | `proof ` | `bytes ` | `relay_difficulty_target_hash` | TODO_FOLLOWUP(@olshansk, #690): Either delete this or change it to be named "minimum" relay_difficulty_target_hash is the maximum value a relay hash must be less than to be volume/reward applicable. | | `proof ` | `float ` | `proof_request_probability` | proof_request_probability is the probability of a session requiring a proof if it's cost (i.e. compute unit consumption) is below the ProofRequirementThreshold. | -| `proof ` | `uint64 ` | `proof_requirement_threshold` | proof_requirement_threshold is the session cost (i.e. compute unit consumption) threshold which asserts that a session MUST have a corresponding proof when its cost is equal to or above the threshold. This is in contrast to the this requirement being determined probabilistically via ProofRequestProbability. TODO_MAINNET: Consider renaming this to `proof_requirement_threshold_compute_units`. | -| `service ` | `uint64 ` | `add_service_fee` | The amount of uPOKT required to add a new service. This will be deducted from the signer's account balance, and transferred to the pocket network foundation. | | `shared ` | `uint64 ` | `num_blocks_per_session` | num_blocks_per_session is the number of blocks between the session start & end heights. | | `shared ` | `uint64 ` | `grace_period_end_offset_blocks` | grace_period_end_offset_blocks is the number of blocks, after the session end height, during which the supplier can still service payable relays. Suppliers will need to recreate a claim for the previous session (if already created) to get paid for the additional relays. | | `shared ` | `uint64 ` | `claim_window_open_offset_blocks` | claim_window_open_offset_blocks is the number of blocks after the session grace period height, at which the claim window opens. | @@ -41,4 +46,4 @@ Please follow the instructions in [this guide](../../develop/developer_guide/add | `shared ` | `uint64 ` | `proof_window_close_offset_blocks` | proof_window_close_offset_blocks is the number of blocks after the proof window open height, at which the proof window closes. | | `shared ` | `uint64 ` | `supplier_unbonding_period_sessions` | supplier_unbonding_period_sessions is the number of sessions that a supplier must wait after unstaking before their staked assets are moved to their account balance. On-chain business logic requires, and ensures, that the corresponding block count of the unbonding period will exceed the end of any active claim & proof lifecycles. | | `shared ` | `uint64 ` | `application_unbonding_period_sessions` | application_unbonding_period_sessions is the number of sessions that an application must wait after unstaking before their staked assets are moved to their account balance. On-chain business logic requires, and ensures, that the corresponding block count of the application unbonding period will exceed the end of its corresponding proof window close height. | -| `tokenomics` | `uint64 ` | `compute_units_to_tokens_multiplier` | The amount of upokt that a compute unit should translate to when settling a session. | +| `shared ` | `uint64 ` | `compute_units_to_tokens_multiplier` | The amount of upokt that a compute unit should translate to when settling a session. DEV_NOTE: This used to be under x/tokenomics but has been moved here to avoid cyclic dependencies. | diff --git a/docusaurus/docs/protocol/upgrades/module_params.md b/docusaurus/docs/protocol/upgrades/module_params.md index 695e29026..690fabf26 100644 --- a/docusaurus/docs/protocol/upgrades/module_params.md +++ b/docusaurus/docs/protocol/upgrades/module_params.md @@ -13,6 +13,10 @@ Pocket Network utilizes an off-chain governance mechanism that enables the commu - [Examples](#examples) - [Block Size Change](#block-size-change) +## Access Control + +// TODO_DOCUMENT(@bryanchriswhite) tl;dr, authz. + ## Examples ### Block Size Change diff --git a/docusaurus/yarn.lock b/docusaurus/yarn.lock index 5c2dcf935..93cee387e 100644 --- a/docusaurus/yarn.lock +++ b/docusaurus/yarn.lock @@ -1810,10 +1810,15 @@ dependencies: "@types/mdx" "^2.0.0" -"@node-rs/jieba-darwin-arm64@1.10.0": +"@node-rs/jieba-linux-x64-gnu@1.10.0": version "1.10.0" - resolved "https://registry.npmjs.org/@node-rs/jieba-darwin-arm64/-/jieba-darwin-arm64-1.10.0.tgz" - integrity sha512-IhR5r+XxFcfhVsF93zQ3uCJy8ndotRntXzoW/JCyKqOahUo/ITQRT6vTKHKMyD9xNmjl222OZonBSo2+mlI2fQ== + resolved "https://registry.npmjs.org/@node-rs/jieba-linux-x64-gnu/-/jieba-linux-x64-gnu-1.10.0.tgz" + integrity sha512-rS5Shs8JITxJjFIjoIZ5a9O+GO21TJgKu03g2qwFE3QaN5ZOvXtz+/AqqyfT4GmmMhCujD83AGqfOGXDmItF9w== + +"@node-rs/jieba-linux-x64-musl@1.10.0": + version "1.10.0" + resolved "https://registry.npmjs.org/@node-rs/jieba-linux-x64-musl/-/jieba-linux-x64-musl-1.10.0.tgz" + integrity sha512-BvSiF2rR8Birh2oEVHcYwq0WGC1cegkEdddWsPrrSmpKmukJE2zyjcxaOOggq2apb8fIRsjyeeUh6X3R5AgjvA== "@node-rs/jieba@^1.6.0": version "1.10.0" @@ -4614,11 +4619,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" diff --git a/e2e/tests/stake_supplier_steps_test.go b/e2e/tests/stake_supplier_steps_test.go index 2b1e9e28e..96aa53538 100644 --- a/e2e/tests/stake_supplier_steps_test.go +++ b/e2e/tests/stake_supplier_steps_test.go @@ -4,11 +4,10 @@ package e2e import ( "reflect" - "strings" - "unicode" "github.com/stretchr/testify/require" + "github.com/pokt-network/poktroll/testutil/cases" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) @@ -61,7 +60,7 @@ func paramsAnyMapFromParamsStruct(paramStruct any) paramsAnyMap { for i := 0; i < paramsReflectValue.NumField(); i++ { fieldValue := paramsReflectValue.Field(i) fieldStruct := paramsReflectValue.Type().Field(i) - paramName := toSnakeCase(fieldStruct.Name) + paramName := cases.ToSnakeCase(fieldStruct.Name) fieldTypeName := fieldStruct.Type.Name() // TODO_IMPROVE: MsgUpdateParam currently only supports int64 and not uint64 value types. @@ -78,23 +77,3 @@ func paramsAnyMapFromParamsStruct(paramStruct any) paramsAnyMap { } return paramsMap } - -func toSnakeCase(str string) string { - var result strings.Builder - - for i, runeValue := range str { - if unicode.IsUpper(runeValue) { - // If it's not the first letter, add an underscore - if i > 0 { - result.WriteRune('_') - } - // Convert to lowercase - result.WriteRune(unicode.ToLower(runeValue)) - } else { - // Otherwise, just append the rune as-is - result.WriteRune(runeValue) - } - } - - return result.String() -} diff --git a/e2e/tests/update_params.feature b/e2e/tests/update_params.feature deleted file mode 100644 index a84cad537..000000000 --- a/e2e/tests/update_params.feature +++ /dev/null @@ -1,95 +0,0 @@ -Feature: Params Namespace - - # Why do we have this scenario? - # During development, if one of these tests fails along the way, we get into a - # state where LocalNet is inconsistent w/ expectations and needs to be restarted. - # Rather than using a `Background` set of commands that rerun on every scenario, - # we add one to prepare for everything downstream. - Scenario: All params are reset to their default values - Given the user has the pocketd binary installed - And an authz grant from the "gov" "module" account to the "pnf" "user" account for each module MsgUpdateParam message exists - Then all module params are reset to their default values - - # NB: If you are reading this and the proof module has parameters - # that are not being updated in this test, please update the test. - Scenario: An authorized user updates all "proof" module params - Given the user has the pocketd binary installed - And all "proof" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.proof.MsgUpdateParams" message exists - When the "pnf" account sends an authz exec message to update all "proof" module params - | name | value | type | - | relay_difficulty_target_hash | 00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff | bytes | - | proof_request_probability | 0.1 | float | - | proof_requirement_threshold | 100 | coin | - | proof_missing_penalty | 500 | coin | - | proof_submission_fee | 5000000 | coin | - Then all "proof" module params should be updated - - # NB: If you are reading this and the proof module has parameters - # that are not being updated in this test, please update the test. - Scenario: An authorized user updates all "shared" module params - Given the user has the pocketd binary installed - And all "shared" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.shared.MsgUpdateParams" message exists - When the "pnf" account sends an authz exec message to update all "shared" module params - | name | value | type | - | num_blocks_per_session | 5 | int64 | - | grace_period_end_offset_blocks | 2 | int64 | - | claim_window_open_offset_blocks | 2 | int64 | - | claim_window_close_offset_blocks | 3 | int64 | - | proof_window_open_offset_blocks | 1 | int64 | - | proof_window_close_offset_blocks | 5 | int64 | - | supplier_unbonding_period_sessions | 5 | int64 | - | application_unbonding_period_sessions | 5 | int64 | - | compute_units_to_tokens_multiplier | 666 | int64 | - Then all "shared" module params should be updated - - - # NB: If you are reading this and any module has parameters that - # are not being updated in this test, please update the test. - Scenario: An authorized user updates all "service" module params - Given the user has the pocketd binary installed - And all "service" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.service.MsgUpdateParams" message exists - When the "pnf" account sends an authz exec message to update all "service" module params - | name | value | type | - | add_service_fee | 1000000001 | coin | - Then all "service" module params should be updated - - # NB: If you are reading this and any module has parameters that - # are not being updated in this test, please update the test. - Scenario Outline: An authorized user updates individual module params - Given the user has the pocketd binary installed - And all "" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "" message exists - When the "pnf" account sends an authz exec message to update "" the module param - | name | value | type | - | | | | - Then the "" module param "" should be updated - - Examples: - | module | message_type | param_name | param_value | param_type | - | proof | /poktroll.proof.MsgUpdateParam | min_relay_difficulty_bits | 12 | int64 | - | proof | /poktroll.proof.MsgUpdateParam | proof_request_probability | 0.1 | float | - | proof | /poktroll.proof.MsgUpdateParam | proof_requirement_threshold | 100 | coin | - | proof | /poktroll.proof.MsgUpdateParam | proof_missing_penalty | 500 | coin | - | proof | /poktroll.proof.MsgUpdateParam | proof_submission_fee | 5000000 | coin | - | shared | /poktroll.shared.MsgUpdateParam | num_blocks_per_session | 9 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | grace_period_end_offset_blocks | 0 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | claim_window_open_offset_blocks | 2 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | claim_window_close_offset_blocks | 3 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | proof_window_open_offset_blocks | 1 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | proof_window_close_offset_blocks | 5 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | supplier_unbonding_period_sessions | 5 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | application_unbonding_period_sessions | 5 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | compute_units_to_tokens_multiplier | 68 | int64 | - | service | /poktroll.service.MsgUpdateParam | add_service_fee | 1000000001 | coin | - - Scenario: An unauthorized user cannot update individual module params - Given the user has the pocketd binary installed - And all "proof" module params are set to their default values - And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.proof.MsgUpdateParams" message exists - When the "unauthorized" account sends an authz exec message to update "proof" the module param - | name | value | type | - | proof_request_probability | 0.1 | float | - Then the "proof" module param "min_relay_difficulty_bits" should be set to its default value diff --git a/tests/integration/params/update_param_test.go b/tests/integration/params/update_param_test.go new file mode 100644 index 000000000..0f1ffccfd --- /dev/null +++ b/tests/integration/params/update_param_test.go @@ -0,0 +1,142 @@ +package params + +import ( + "fmt" + "reflect" + "testing" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/pokt-network/poktroll/testutil/integration/suites" +) + +// msgUpdateParamSuite is a test suite which exercises the MsgUpdateParam message +// for each poktroll module via authz, as would be done in a live network in order +// to update **individual** parameter values for a given module. +// NB: Not to be confused with MsgUpdateParams (plural), which updates all parameter +// values for a module. +type msgUpdateParamSuite struct { + suites.ParamsSuite + + unauthorizedAddr cosmostypes.AccAddress +} + +func TestUpdateParamSuite(t *testing.T) { + suite.Run(t, new(msgUpdateParamSuite)) +} + +func (s *msgUpdateParamSuite) SetupSubTest() { + // Create a fresh integration app for each test. + s.NewApp(s.T()) + + // Initialize the test accounts and create authz grants. + s.SetupTestAuthzAccounts() + s.SetupTestAuthzGrants() + + // Allocate an address for unauthorized user. + nextAcct, ok := s.GetApp().GetPreGeneratedAccounts().Next() + require.True(s.T(), ok, "insufficient pre-generated accounts available") + s.unauthorizedAddr = nextAcct.Address +} + +func (s *msgUpdateParamSuite) TestUnauthorizedMsgUpdateParamFails() { + for _, moduleName := range suites.MsgUpdateParamEnabledModuleNames { + moduleCfg := suites.ModuleParamConfigMap[moduleName] + + // Iterate over each field in the current module's MsgUpdateParam, for each + // field, send a new MsgUpdateParam which would update the corresponding param + // to that field's value. + validParamsValue := reflect.ValueOf(moduleCfg.ValidParams) + for fieldIdx := 0; fieldIdx < validParamsValue.NumField(); fieldIdx++ { + fieldValue := validParamsValue.Field(fieldIdx) + fieldName := validParamsValue.Type().Field(fieldIdx).Name + + testName := fmt.Sprintf("%s_%s", moduleName, fieldName) + s.T().Run(testName, func(t *testing.T) { + // Reset the app state in order to assert that each module + // param is updated correctly. + s.SetupSubTest() + + // Assert that the module's params are set to their default values. + s.RequireModuleHasDefaultParams(t, moduleName) + + updateResBz, err := s.RunUpdateParamAsSigner(t, + moduleName, + fieldName, + fieldValue.Interface(), + s.unauthorizedAddr, + ) + require.ErrorContains(t, err, authz.ErrNoAuthorizationFound.Error()) + require.Nil(t, updateResBz) + }) + } + } +} + +func (s *msgUpdateParamSuite) TestAuthorizedMsgUpdateParamSucceeds() { + for _, moduleName := range suites.MsgUpdateParamEnabledModuleNames { + moduleCfg := suites.ModuleParamConfigMap[moduleName] + + // Iterate over each field in the current module's MsgUpdateParam, for each + // field, send a new MsgUpdateParam which would update the corresponding param + // to that field's value. + validParamsValue := reflect.ValueOf(moduleCfg.ValidParams) + for fieldIdx := 0; fieldIdx < validParamsValue.NumField(); fieldIdx++ { + fieldExpectedValue := validParamsValue.Field(fieldIdx) + fieldName := validParamsValue.Type().Field(fieldIdx).Name + + testName := fmt.Sprintf("%s_%s", moduleName, fieldName) + s.T().Run(testName, func(t *testing.T) { + // Reset the app state in order to assert that each module + // param is updated correctly. + s.SetupSubTest() + + // Assert that the module's params are set to their default values. + s.RequireModuleHasDefaultParams(t, moduleName) + + updateResBz, err := s.RunUpdateParam(t, + moduleName, + fieldName, + fieldExpectedValue.Interface(), + ) + require.NoError(t, err) + require.NotNil(t, updateResBz) + + // TODO_INVESTIGATE(https://github.com/cosmos/cosmos-sdk/issues/21904): + // It seems like the response objects are encoded in an unexpected way. + // It's unclear whether this is the result of being executed via authz. + // Looking at the code, it seems like authz utilizes the sdk.Result#Data + // field of the result which is returned from the message handler. + // These result byte slices are accumulated for each message in the MsgExec and + // set on the MsgExecResponse#Results field. + // + // - https://github.com/cosmos/cosmos-sdk/blob/v0.50.9/x/authz/keeper/msg_server.go#L120 + // - https://github.com/cosmos/cosmos-sdk/blob/v0.50.9/x/authz/keeper/keeper.go#L166 + // - https://github.com/cosmos/cosmos-sdk/blob/v0.50.9/baseapp/msg_service_router.go#L55 + // - https://github.com/cosmos/cosmos-sdk/blob/v0.50.9/baseapp/msg_service_router.go#L198 + // - https://github.com/cosmos/cosmos-sdk/blob/v0.50.9/types/result.go#L213 + // + // I (@bryanchriswhite) would've expected the following to work, but it does not: + // + // updateResValue := reflect.New(reflect.TypeOf(moduleCfg.MsgUpdateParamResponse)) + // // NB: using proto.Unmarshal here because authz seems to use + // // proto.Marshal to serialize each message response. + // err = proto.Unmarshal(updateResBz, updateResValue.Interface().(cosmostypes.Msg)) + // require.NoError(t, err) + // updateResParamValue := updateResValue.Elem().FieldByName("Params").Elem().FieldByName(fieldName) + // require.Equal(t, fieldExpectedValue.Interface(), updateResParamValue.Interface()) + + // Query for the module's params. + params, err := s.QueryModuleParams(t, moduleName) + require.NoError(t, err) + + paramsValue := reflect.ValueOf(params) + paramValue := paramsValue.FieldByName(fieldName) + require.Equal(t, fieldExpectedValue.Interface(), paramValue.Interface()) + }) + } + } +} diff --git a/tests/integration/params/update_params_test.go b/tests/integration/params/update_params_test.go new file mode 100644 index 000000000..f8d803b50 --- /dev/null +++ b/tests/integration/params/update_params_test.go @@ -0,0 +1,105 @@ +package params + +import ( + "reflect" + "testing" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/pokt-network/poktroll/testutil/integration/suites" +) + +// msgUpdateParamsSuite is a test suite which exercises the MsgUpdateParams message +// for each poktroll module via authz, as would be done in a live network in order +// to update **all** parameter values for a given module. +// NB: Not to be confused with MsgUpdateParam (singular), which updates a single +// parameter value for a module. +type msgUpdateParamsSuite struct { + suites.ParamsSuite + + unauthorizedAddr cosmostypes.AccAddress +} + +// TestUpdateParamsSuite uses the ModuleParamConfig for each module to test the +// MsgUpdateParams message execution via authz on an integration app, as would be +// done in a live network in order to update module parameter values. It uses +// reflection to construct the messages and make assertions about the results to +// improve maintainability and reduce boilerplate. +func TestUpdateParamsSuite(t *testing.T) { + suite.Run(t, &msgUpdateParamsSuite{}) +} + +func (s *msgUpdateParamsSuite) SetupTest() { + // Create a fresh integration app for each test. + s.NewApp(s.T()) + + // Initialize the test accounts and create authz grants. + s.SetupTestAuthzAccounts() + s.SetupTestAuthzGrants() + + // Allocate an address for unauthorized user. + nextAcct, ok := s.GetApp().GetPreGeneratedAccounts().Next() + require.True(s.T(), ok, "insufficient pre-generated accounts available") + s.unauthorizedAddr = nextAcct.Address +} + +func (s *msgUpdateParamsSuite) TestUnauthorizedMsgUpdateParamsFails() { + for _, moduleName := range s.GetPoktrollModuleNames() { + moduleCfg := suites.ModuleParamConfigMap[moduleName] + + s.T().Run(moduleName, func(t *testing.T) { + // Assert that the module's params are set to their default values. + s.RequireModuleHasDefaultParams(t, moduleName) + + // Construct a new MsgUpdateParams and set its authority and params fields. + expectedParams := moduleCfg.ValidParams + msgUpdateParamsType := reflect.TypeOf(moduleCfg.ParamsMsgs.MsgUpdateParams) + msgUpdateParams := suites.NewMsgUpdateParams( + msgUpdateParamsType, + s.AuthorityAddr.String(), + expectedParams, + ) + updateRes, err := s.RunUpdateParamsAsSigner(t, msgUpdateParams, s.unauthorizedAddr) + require.ErrorContains(t, err, authz.ErrNoAuthorizationFound.Error()) + require.Nil(t, updateRes) + }) + } +} + +func (s *msgUpdateParamsSuite) TestAuthorizedMsgUpdateParamsSucceeds() { + for _, moduleName := range s.GetPoktrollModuleNames() { + moduleCfg := suites.ModuleParamConfigMap[moduleName] + + s.T().Run(moduleName, func(t *testing.T) { + // Assert that the module's params are set to their default values. + s.RequireModuleHasDefaultParams(t, moduleName) + + // Construct a new MsgUpdateParams and set its authority and params fields. + expectedParams := moduleCfg.ValidParams + msgUpdateParamsType := reflect.TypeOf(moduleCfg.ParamsMsgs.MsgUpdateParams) + msgUpdateParams := suites.NewMsgUpdateParams( + msgUpdateParamsType, + s.AuthorityAddr.String(), + expectedParams, + ) + // TODO_IMPROVE: add a Params field to the MsgUpdateParamsResponse + // and assert that it reflects the updated params. + _, err := s.RunUpdateParams(t, msgUpdateParams) + require.NoError(t, err) + + // Query for the module's params. + params, err := s.QueryModuleParams(t, moduleName) + require.NoError(t, err) + + // Assert that the module's params are updated. + require.True(t, + reflect.DeepEqual(expectedParams, params), + "expected:\n%+v\nto deeply equal:\n%+v", + expectedParams, params, + ) + }) + } +} diff --git a/tests/integration/tokenomics/relay_mining_integration_test.go b/tests/integration/tokenomics/relay_mining_integration_test.go index c7e32c0ef..b37ef1e17 100644 --- a/tests/integration/tokenomics/relay_mining_integration_test.go +++ b/tests/integration/tokenomics/relay_mining_integration_test.go @@ -11,6 +11,7 @@ import ( "github.com/pokt-network/poktroll/pkg/crypto/protocol" "github.com/pokt-network/poktroll/testutil/integration" + "github.com/pokt-network/poktroll/testutil/integration/suites" "github.com/pokt-network/poktroll/testutil/testrelayer" apptypes "github.com/pokt-network/poktroll/x/application/types" prooftypes "github.com/pokt-network/poktroll/x/proof/types" @@ -27,20 +28,14 @@ var ( ) type RelayMiningIntegrationTestSuite struct { - // TODO_BETA(#826): wait for integration app & suites refactor to be merged. - // Once suites.ParamsSuite is available, embed it here. In the meantime, we - // MUST embed suite.Suite to avoid compilation errors. - // - // suites.ParamsSuite - suite.Suite + suites.ParamsSuite } func (s *RelayMiningIntegrationTestSuite) SetupTest() { // Construct a fresh integration app for each test. - // TODO_BETA(#826): wait for integration app & suites refactor to be merged. - // s.NewApp(s.T()) - // s.SetupTestAccounts() - // s.SetupTestAuthzGrants() + s.NewApp(s.T()) + s.SetupTestAuthzAccounts() + s.SetupTestAuthzGrants() } func (s *RelayMiningIntegrationTestSuite) TestComputeNewDifficultyHash_RewardsReflectWorkCompleted() { @@ -183,6 +178,5 @@ func prepareRealClaim( } func TestRelayMiningIntegrationSuite(t *testing.T) { - t.Skip("TODO_BETA(#826): wait for integration app & suites refactor to be merged.") suite.Run(t, new(RelayMiningIntegrationTestSuite)) } diff --git a/testutil/cases/cases.go b/testutil/cases/cases.go new file mode 100644 index 000000000..012edf29a --- /dev/null +++ b/testutil/cases/cases.go @@ -0,0 +1,29 @@ +package cases + +import ( + "strings" + "unicode" +) + +// TODO_CONSIDERATION: Prefer using an external library (e.g. +// https://github.com/iancoleman/strcase) over implementing more cases. + +func ToSnakeCase(str string) string { + var result strings.Builder + + for i, runeValue := range str { + if unicode.IsUpper(runeValue) { + // If it's not the first letter, add an underscore + if i > 0 { + result.WriteRune('_') + } + // Convert to lowercase + result.WriteRune(unicode.ToLower(runeValue)) + } else { + // Otherwise, just append the rune as-is + result.WriteRune(runeValue) + } + } + + return result.String() +} diff --git a/testutil/integration/suites/authz.go b/testutil/integration/suites/authz.go new file mode 100644 index 000000000..a3363219e --- /dev/null +++ b/testutil/integration/suites/authz.go @@ -0,0 +1,108 @@ +package suites + +import ( + "fmt" + "testing" + "time" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" +) + +const ( + // poktrollMsgTypeFormat is the format for a poktroll module's message type. + // The first %s is the module name, and the second %s is the message name. + poktrollMsgTypeFormat = "/poktroll.%s.%s" +) + +var defaultAuthzGrantExpiration = time.Now().Add(time.Hour) + +// AuthzIntegrationSuite is an integration test suite that provides helper functions for +// running authz grant and exec messages. It is intended to be embedded in other integration +// test suites which are dependent on authz. +type AuthzIntegrationSuite struct { + BaseIntegrationSuite +} + +// RunAuthzGrantMsgForPoktrollModules creates an on-chain authz grant for the given +// granter and grantee addresses for the specified message name in each of the poktroll +// modules present in the integration app. +func (s *AuthzIntegrationSuite) RunAuthzGrantMsgForPoktrollModules( + t *testing.T, + granterAddr, granteeAddr cosmostypes.AccAddress, + msgName string, + moduleNames ...string, +) { + t.Helper() + + var foundModuleGrants = make(map[string]int) + for _, moduleName := range moduleNames { + msgType := fmtPoktrollMsgType(moduleName, msgName) + authorization := &authz.GenericAuthorization{Msg: msgType} + s.RunAuthzGrantMsg(t, granterAddr, granteeAddr, authorization) + + // Query for the created grant to assert that they were created. + authzQueryClient := authz.NewQueryClient(s.app.QueryHelper()) + queryGrantsReq := &authz.QueryGrantsRequest{ + Granter: granterAddr.String(), + Grantee: granteeAddr.String(), + MsgTypeUrl: msgType, + } + queryGrantsRes, err := authzQueryClient.Grants(s.app.GetSdkCtx(), queryGrantsReq) + require.NoError(t, err) + require.NotNil(t, queryGrantsRes) + + // Count the number of grants found for each module. + for range queryGrantsRes.GetGrants() { + foundModuleGrants[moduleName]++ + } + } + + // Assert that only one grant was created for each module. + for _, foundTimes := range foundModuleGrants { + require.Equal(t, 1, foundTimes) + } +} + +// RunAuthzGrantMsg creates an on-chain authz grant from the given granter to the +// grantee addresses for the authorization object provided. +func (s *AuthzIntegrationSuite) RunAuthzGrantMsg( + t *testing.T, + granterAddr, + granteeAddr cosmostypes.AccAddress, + authorization authz.Authorization, +) { + t.Helper() + + grantMsg, err := authz.NewMsgGrant(granterAddr, granteeAddr, authorization, &defaultAuthzGrantExpiration) + require.NoError(t, err) + + grantResAny, err := s.app.RunMsg(s.T(), grantMsg) + require.NoError(t, err) + require.NotNil(t, grantResAny) +} + +// RunAuthzExecMsg executes the given messag(es) using authz. It assumes that an +// authorization exists for which signerAdder is the grantee. +func (s *AuthzIntegrationSuite) RunAuthzExecMsg( + t *testing.T, + signerAddr cosmostypes.AccAddress, + msgs ...cosmostypes.Msg, +) (msgRespsBz [][]byte, err error) { + t.Helper() + + execMsg := authz.NewMsgExec(signerAddr, msgs) + execResAny, err := s.GetApp().RunMsg(s.T(), &execMsg) + if err != nil { + return nil, err + } + + require.NotNil(t, execResAny) + return execResAny.(*authz.MsgExecResponse).Results, nil +} + +// fmtPoktrollMsgType returns the formatted message type for a poktroll module. +func fmtPoktrollMsgType(moduleName, msgName string) string { + return fmt.Sprintf(poktrollMsgTypeFormat, moduleName, msgName) +} diff --git a/testutil/integration/suites/param_configs.go b/testutil/integration/suites/param_configs.go new file mode 100644 index 000000000..2e95a7c41 --- /dev/null +++ b/testutil/integration/suites/param_configs.go @@ -0,0 +1,164 @@ +package suites + +import ( + "encoding/hex" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + + "github.com/pokt-network/poktroll/app/volatile" + apptypes "github.com/pokt-network/poktroll/x/application/types" + gatewaytypes "github.com/pokt-network/poktroll/x/gateway/types" + prooftypes "github.com/pokt-network/poktroll/x/proof/types" + servicetypes "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" + suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" + tokenomicstypes "github.com/pokt-network/poktroll/x/tokenomics/types" +) + +var ( + ValidAddServiceFeeCoin = cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 1000000001) + ValidProofMissingPenaltyCoin = cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 500) + ValidProofSubmissionFeeCoin = cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 5000000) + ValidProofRequirementThresholdCoin = cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 100) + ValidRelayDifficultyTargetHash, _ = hex.DecodeString("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + + SharedModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: sharedtypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: sharedtypes.MsgUpdateParamsResponse{}, + MsgUpdateParam: sharedtypes.MsgUpdateParam{}, + MsgUpdateParamResponse: sharedtypes.MsgUpdateParamResponse{}, + QueryParamsRequest: sharedtypes.QueryParamsRequest{}, + QueryParamsResponse: sharedtypes.QueryParamsResponse{}, + }, + ParamTypes: map[ParamType]any{ + ParamTypeUint64: sharedtypes.MsgUpdateParam_AsInt64{}, + ParamTypeInt64: sharedtypes.MsgUpdateParam_AsInt64{}, + ParamTypeString: sharedtypes.MsgUpdateParam_AsString{}, + ParamTypeBytes: sharedtypes.MsgUpdateParam_AsBytes{}, + }, + ValidParams: sharedtypes.Params{ + NumBlocksPerSession: 12, + GracePeriodEndOffsetBlocks: 0, + ClaimWindowOpenOffsetBlocks: 2, + ClaimWindowCloseOffsetBlocks: 3, + ProofWindowOpenOffsetBlocks: 1, + ProofWindowCloseOffsetBlocks: 3, + SupplierUnbondingPeriodSessions: 9, + ApplicationUnbondingPeriodSessions: 9, + ComputeUnitsToTokensMultiplier: 420, + }, + DefaultParams: sharedtypes.DefaultParams(), + NewParamClientFn: sharedtypes.NewQueryClient, + } + + SessionModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: sessiontypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: sessiontypes.MsgUpdateParamsResponse{}, + QueryParamsRequest: sessiontypes.QueryParamsRequest{}, + QueryParamsResponse: sessiontypes.QueryParamsResponse{}, + }, + ValidParams: sessiontypes.Params{}, + DefaultParams: sessiontypes.DefaultParams(), + NewParamClientFn: sessiontypes.NewQueryClient, + } + + ServiceModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: servicetypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: servicetypes.MsgUpdateParamsResponse{}, + MsgUpdateParam: servicetypes.MsgUpdateParam{}, + MsgUpdateParamResponse: servicetypes.MsgUpdateParamResponse{}, + QueryParamsRequest: servicetypes.QueryParamsRequest{}, + QueryParamsResponse: servicetypes.QueryParamsResponse{}, + }, + ValidParams: servicetypes.Params{ + AddServiceFee: &ValidAddServiceFeeCoin, + }, + ParamTypes: map[ParamType]any{ + ParamTypeCoin: servicetypes.MsgUpdateParam_AsCoin{}, + }, + DefaultParams: servicetypes.DefaultParams(), + NewParamClientFn: servicetypes.NewQueryClient, + } + + ApplicationModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: apptypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: apptypes.MsgUpdateParamsResponse{}, + QueryParamsRequest: apptypes.QueryParamsRequest{}, + QueryParamsResponse: apptypes.QueryParamsResponse{}, + }, + ValidParams: apptypes.Params{ + MaxDelegatedGateways: 999, + }, + DefaultParams: apptypes.DefaultParams(), + NewParamClientFn: apptypes.NewQueryClient, + } + + GatewayModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: gatewaytypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: gatewaytypes.MsgUpdateParamsResponse{}, + QueryParamsRequest: gatewaytypes.QueryParamsRequest{}, + QueryParamsResponse: gatewaytypes.QueryParamsResponse{}, + }, + ValidParams: gatewaytypes.Params{}, + DefaultParams: gatewaytypes.DefaultParams(), + NewParamClientFn: gatewaytypes.NewQueryClient, + } + + SupplierModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: suppliertypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: suppliertypes.MsgUpdateParamsResponse{}, + QueryParamsRequest: suppliertypes.QueryParamsRequest{}, + QueryParamsResponse: suppliertypes.QueryParamsResponse{}, + }, + ValidParams: suppliertypes.Params{}, + DefaultParams: suppliertypes.DefaultParams(), + NewParamClientFn: suppliertypes.NewQueryClient, + } + + ProofModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: prooftypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: prooftypes.MsgUpdateParamsResponse{}, + MsgUpdateParam: prooftypes.MsgUpdateParam{}, + MsgUpdateParamResponse: prooftypes.MsgUpdateParamResponse{}, + QueryParamsRequest: prooftypes.QueryParamsRequest{}, + QueryParamsResponse: prooftypes.QueryParamsResponse{}, + }, + ValidParams: prooftypes.Params{ + RelayDifficultyTargetHash: ValidRelayDifficultyTargetHash, + ProofRequestProbability: 0.1, + ProofRequirementThreshold: &ValidProofRequirementThresholdCoin, + ProofMissingPenalty: &ValidProofMissingPenaltyCoin, + ProofSubmissionFee: &ValidProofSubmissionFeeCoin, + }, + ParamTypes: map[ParamType]any{ + ParamTypeUint64: prooftypes.MsgUpdateParam_AsInt64{}, + ParamTypeInt64: prooftypes.MsgUpdateParam_AsInt64{}, + ParamTypeString: prooftypes.MsgUpdateParam_AsString{}, + ParamTypeBytes: prooftypes.MsgUpdateParam_AsBytes{}, + ParamTypeFloat32: prooftypes.MsgUpdateParam_AsFloat{}, + ParamTypeCoin: prooftypes.MsgUpdateParam_AsCoin{}, + }, + DefaultParams: prooftypes.DefaultParams(), + NewParamClientFn: prooftypes.NewQueryClient, + } + + TokenomicsModuleParamConfig = ModuleParamConfig{ + ParamsMsgs: ModuleParamsMessages{ + MsgUpdateParams: tokenomicstypes.MsgUpdateParams{}, + MsgUpdateParamsResponse: tokenomicstypes.MsgUpdateParamsResponse{}, + QueryParamsRequest: tokenomicstypes.QueryParamsRequest{}, + QueryParamsResponse: tokenomicstypes.QueryParamsResponse{}, + }, + ValidParams: tokenomicstypes.Params{}, + DefaultParams: tokenomicstypes.DefaultParams(), + NewParamClientFn: tokenomicstypes.NewQueryClient, + } +) diff --git a/testutil/integration/suites/update_params.go b/testutil/integration/suites/update_params.go new file mode 100644 index 000000000..243c3f3a2 --- /dev/null +++ b/testutil/integration/suites/update_params.go @@ -0,0 +1,345 @@ +package suites + +import ( + "reflect" + "testing" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/stretchr/testify/require" + + "github.com/pokt-network/poktroll/testutil/cases" + apptypes "github.com/pokt-network/poktroll/x/application/types" + gatewaytypes "github.com/pokt-network/poktroll/x/gateway/types" + prooftypes "github.com/pokt-network/poktroll/x/proof/types" + servicetypes "github.com/pokt-network/poktroll/x/service/types" + sessiontypes "github.com/pokt-network/poktroll/x/session/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" + suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" + tokenomicstypes "github.com/pokt-network/poktroll/x/tokenomics/types" +) + +// ParamType is a type alias for a module parameter type. It is the string that +// is returned when calling reflect.Value#Type()#Name() on a module parameter. +type ParamType = string + +const ( + ParamTypeInt64 ParamType = "int64" + ParamTypeUint64 ParamType = "uint64" + ParamTypeFloat32 ParamType = "float32" + ParamTypeString ParamType = "string" + ParamTypeBytes ParamType = "uint8" + ParamTypeCoin ParamType = "Coin" +) + +const ( + MsgUpdateParamsName = "MsgUpdateParams" + MsgUpdateParamName = "MsgUpdateParam" +) + +// ModuleParamConfig holds type information about a module's parameters update +// message(s) along with default and valid non-default values and a query constructor +// function for the module. It is used by ParamsSuite to construct and send +// parameter update messages and assert on their results. +type ModuleParamConfig struct { + ParamsMsgs ModuleParamsMessages + // ParamTypes is a map of parameter types to their respective MsgUpdateParam_As* + // types which satisfy the oneof for the MsgUpdateParam#AsType field. Each AsType + // type which the module supports should be included in this map. + ParamTypes map[ParamType]any + // ValidParams is a set of parameters which are expected to be valid when used + // together AND when used individually, where the reamining parameters are set + // to their default values. + ValidParams any + DefaultParams any + NewParamClientFn any +} + +// ModuleParamsMessages holds a reference to each of the params-related message +// types for a given module. The values are only used for their type information +// which is obtained via reflection. The values are not used for their actual +// message contents and MAY be the zero value. +// If MsgUpdateParam is omitted (i.e. nil), ParamsSuite will assume that +// this module does not support individual parameter updates (i.e. MsgUpdateParam). +// In this case, MsgUpdateParamResponse SHOULD also be omitted. +type ModuleParamsMessages struct { + MsgUpdateParams any + MsgUpdateParamsResponse any + MsgUpdateParam any + MsgUpdateParamResponse any + QueryParamsRequest any + QueryParamsResponse any +} + +var ( + // MsgUpdateParamEnabledModuleNames is a list of module names which support + // individual parameter updates (i.e. MsgUpdateParam). It is initialized in + // init(). + MsgUpdateParamEnabledModuleNames []string + + // ModuleParamConfigMap is a map of module names to their respective parameter + // configurations. It is used by the ParamsSuite, mostly via reflection, + // to construct and send parameter update messages and assert on their results. + ModuleParamConfigMap = map[string]ModuleParamConfig{ + sharedtypes.ModuleName: SharedModuleParamConfig, + sessiontypes.ModuleName: SessionModuleParamConfig, + servicetypes.ModuleName: ServiceModuleParamConfig, + apptypes.ModuleName: ApplicationModuleParamConfig, + gatewaytypes.ModuleName: GatewayModuleParamConfig, + suppliertypes.ModuleName: SupplierModuleParamConfig, + prooftypes.ModuleName: ProofModuleParamConfig, + tokenomicstypes.ModuleName: TokenomicsModuleParamConfig, + } + + _ IntegrationSuite = (*ParamsSuite)(nil) +) + +func init() { + for moduleName, moduleParamCfg := range ModuleParamConfigMap { + if moduleParamCfg.ParamsMsgs.MsgUpdateParam != nil { + MsgUpdateParamEnabledModuleNames = append(MsgUpdateParamEnabledModuleNames, moduleName) + } + } +} + +// ParamsSuite is an integration test suite that provides helper functions for +// querying module parameters and running parameter update messages. It is +// intended to be embedded in other integration test suites which are dependent +// on parameter queries or updates. +type ParamsSuite struct { + AuthzIntegrationSuite + + // AuthorityAddr is the cosmos account address of the authority for the integration + // app. It is used as the **granter** of authz grants for parameter update messages. + // In practice, is an address sourced by an on-chain string and no one has the private key. + AuthorityAddr cosmostypes.AccAddress + // AuthorizedAddr is the cosmos account address which is the **grantee** of authz + // grants for parameter update messages. + // In practice, it is the address of the foundation or the DAO. + AuthorizedAddr cosmostypes.AccAddress +} + +// NewMsgUpdateParams constructs a new concrete pointer of msgUpdateParams type +// with the given param values set on it. It is returned as a cosmostypes.Msg. +func NewMsgUpdateParams( + msgUpdateParamsType reflect.Type, + authorityBech32 string, + params any, +) cosmostypes.Msg { + msgUpdateParamsValue := reflect.New(msgUpdateParamsType) + msgUpdateParamsValue.Elem(). + FieldByName("Authority"). + SetString(authorityBech32) + msgUpdateParamsValue.Elem(). + FieldByName("Params"). + Set(reflect.ValueOf(params)) + + return msgUpdateParamsValue.Interface().(cosmostypes.Msg) +} + +// SetupTestAuthzAccounts sets AuthorityAddr for the suite by getting the authority +// from the integration app. It also assigns a new pre-generated identity to be used +// as the AuthorizedAddr for the suite. It is expected to be called after s.NewApp() +// as it depends on the integration app and its pre-generated account iterator. +func (s *ParamsSuite) SetupTestAuthzAccounts() { + // Set the authority, authorized, and unauthorized addresses. + s.AuthorityAddr = cosmostypes.MustAccAddressFromBech32(s.GetApp().GetAuthority()) + + nextAcct, ok := s.GetApp().GetPreGeneratedAccounts().Next() + require.True(s.T(), ok, "insufficient pre-generated accounts available") + s.AuthorizedAddr = nextAcct.Address +} + +// SetupTestAuthzGrants creates on-chain authz grants for the MsgUpdateUpdateParam and +// MsgUpdateParams message for each module. It is expected to be called after s.NewApp() +// as it depends on the authority and authorized addresses having been set. +func (s *ParamsSuite) SetupTestAuthzGrants() { + // Create authz grants for all poktroll modules' MsgUpdateParams messages. + s.RunAuthzGrantMsgForPoktrollModules(s.T(), + s.AuthorityAddr, + s.AuthorizedAddr, + MsgUpdateParamsName, + s.GetPoktrollModuleNames()..., + ) + + // Create authz grants for all poktroll modules' MsgUpdateParam messages. + s.RunAuthzGrantMsgForPoktrollModules(s.T(), + s.AuthorityAddr, + s.AuthorizedAddr, + MsgUpdateParamName, + // NB: only modules with params are expected to support MsgUpdateParam. + MsgUpdateParamEnabledModuleNames..., + ) +} + +// RunUpdateParams runs the given MsgUpdateParams message via an authz exec as the +// AuthorizedAddr and returns the response bytes and error. It is expected to be called +// after s.SetupTestAuthzGrants() as it depends on an on-chain authz grant to AuthorizedAddr +// for MsgUpdateParams for the given module. +func (s *ParamsSuite) RunUpdateParams( + t *testing.T, + msgUpdateParams cosmostypes.Msg, +) (msgResponseBz []byte, err error) { + t.Helper() + + return s.RunUpdateParamsAsSigner(t, msgUpdateParams, s.AuthorizedAddr) +} + +// RunUpdateParamsAsSigner runs the given MsgUpdateParams message via an authz exec +// as signerAddr and returns the response bytes and error. It depends on an on-chain +// authz grant to signerAddr for MsgUpdateParams for the given module. +func (s *ParamsSuite) RunUpdateParamsAsSigner( + t *testing.T, + msgUpdateParams cosmostypes.Msg, + signerAddr cosmostypes.AccAddress, +) (msgResponseBz []byte, err error) { + t.Helper() + + // Send an authz MsgExec from an unauthorized address. + execMsg := authz.NewMsgExec(s.AuthorizedAddr, []cosmostypes.Msg{msgUpdateParams}) + msgRespsBz, err := s.RunAuthzExecMsg(t, signerAddr, &execMsg) + if err != nil { + return nil, err + } + + require.Equal(t, 1, len(msgRespsBz), "expected exactly 1 message response") + return msgRespsBz[0], err +} + +// RunUpdateParam constructs and runs an MsgUpdateParam message via an authz exec +// as the AuthorizedAddr for the given module, parameter name, and value. It returns +// the response bytes and error. It is expected to be called after s.SetupTestAuthzGrants() +// as it depends on an on-chain authz grant to AuthorizedAddr for MsgUpdateParam for the given module. +func (s *ParamsSuite) RunUpdateParam( + t *testing.T, + moduleName string, + paramName string, + paramValue any, +) (msgResponseBz []byte, err error) { + t.Helper() + + return s.RunUpdateParamAsSigner(t, + moduleName, + paramName, + paramValue, + s.AuthorizedAddr, + ) +} + +// RunUpdateParamAsSigner constructs and runs an MsgUpdateParam message via an authz exec +// as the given signerAddr for the given module, parameter name, and value. It returns +// the response bytes and error. It depends on an on-chain authz grant to signerAddr for +// MsgUpdateParam for the given module. +func (s *ParamsSuite) RunUpdateParamAsSigner( + t *testing.T, + moduleName string, + paramName string, + paramValue any, + signerAddr cosmostypes.AccAddress, +) (msgResponseBz []byte, err error) { + t.Helper() + + moduleCfg := ModuleParamConfigMap[moduleName] + + paramReflectValue := reflect.ValueOf(paramValue) + paramType := paramReflectValue.Type().Name() + switch paramReflectValue.Kind() { + case reflect.Pointer: + paramType = paramReflectValue.Elem().Type().Name() + case reflect.Slice: + paramType = paramReflectValue.Type().Elem().Name() + } + + msgIface := moduleCfg.ParamsMsgs.MsgUpdateParam + msgValue := reflect.ValueOf(msgIface) + msgType := msgValue.Type() + + // Copy the message and set the authority field. + msgUpdateParamValue := reflect.New(msgType) + msgUpdateParamValue.Elem(). + FieldByName("Authority"). + SetString(s.AuthorityAddr.String()) + + msgUpdateParamValue.Elem().FieldByName("Name").SetString(cases.ToSnakeCase(paramName)) + + msgAsTypeStruct := moduleCfg.ParamTypes[paramType] + msgAsTypeType := reflect.TypeOf(msgAsTypeStruct) + msgAsTypeValue := reflect.New(msgAsTypeType) + switch paramType { + case ParamTypeUint64: + // NB: MsgUpdateParam doesn't currently support uint64 param type. + msgAsTypeValue.Elem().FieldByName("AsInt64").SetInt(int64(paramReflectValue.Interface().(uint64))) + case ParamTypeInt64: + msgAsTypeValue.Elem().FieldByName("AsInt64").Set(paramReflectValue) + case ParamTypeFloat32: + msgAsTypeValue.Elem().FieldByName("AsFloat").Set(paramReflectValue) + case ParamTypeString: + msgAsTypeValue.Elem().FieldByName("AsString").Set(paramReflectValue) + case ParamTypeBytes: + msgAsTypeValue.Elem().FieldByName("AsBytes").Set(paramReflectValue) + case ParamTypeCoin: + msgAsTypeValue.Elem().FieldByName("AsCoin").Set(paramReflectValue) + default: + t.Fatalf("ERROR: unknown field type %q", paramType) + } + + msgUpdateParamValue.Elem().FieldByName("AsType").Set(msgAsTypeValue) + + msgUpdateParam := msgUpdateParamValue.Interface().(cosmostypes.Msg) + + // Send an authz MsgExec from the authority address. + execMsg := authz.NewMsgExec(signerAddr, []cosmostypes.Msg{msgUpdateParam}) + msgRespsBz, err := s.RunAuthzExecMsg(t, signerAddr, &execMsg) + if err != nil { + return nil, err + } + + require.Equal(t, 1, len(msgRespsBz), "expected exactly 1 message response") + return msgRespsBz[0], err +} + +// RequireModuleHasDefaultParams asserts that the given module's parameters are set +// to their default values. +func (s *ParamsSuite) RequireModuleHasDefaultParams(t *testing.T, moduleName string) { + t.Helper() + + params, err := s.QueryModuleParams(t, moduleName) + require.NoError(t, err) + + moduleCfg := ModuleParamConfigMap[moduleName] + require.EqualValues(t, moduleCfg.DefaultParams, params) +} + +// QueryModuleParams queries the given module's parameters and returns them. It is +// expected to be called after s.NewApp() as it depends on the app's query helper. +func (s *ParamsSuite) QueryModuleParams(t *testing.T, moduleName string) (params any, err error) { + t.Helper() + + moduleCfg := ModuleParamConfigMap[moduleName] + + // Construct a new param client. + newParamClientFn := reflect.ValueOf(moduleCfg.NewParamClientFn) + newParamClientFnArgs := []reflect.Value{ + reflect.ValueOf(s.GetApp().QueryHelper()), + } + paramClient := newParamClientFn.Call(newParamClientFnArgs)[0] + + // Query for the module's params. + paramsQueryReqValue := reflect.New(reflect.TypeOf(moduleCfg.ParamsMsgs.QueryParamsRequest)) + callParamsArgs := []reflect.Value{ + reflect.ValueOf(s.GetApp().GetSdkCtx()), + paramsQueryReqValue, + } + callParamsReturnValues := paramClient.MethodByName("Params").Call(callParamsArgs) + paramsResParamsValue := callParamsReturnValues[0] + paramResErrValue := callParamsReturnValues[1].Interface() + + isErr := false + err, isErr = paramResErrValue.(error) + if !isErr { + require.Nil(t, callParamsReturnValues[1].Interface()) + } + + paramsValue := paramsResParamsValue.Elem().FieldByName("Params") + return paramsValue.Interface(), err +} diff --git a/tools/scripts/generate_docs_params.go b/tools/scripts/docusaurus/generate_docs_params.go similarity index 85% rename from tools/scripts/generate_docs_params.go rename to tools/scripts/docusaurus/generate_docs_params.go index f7b02888e..1c11dbee3 100644 --- a/tools/scripts/generate_docs_params.go +++ b/tools/scripts/docusaurus/generate_docs_params.go @@ -7,6 +7,9 @@ import ( "path/filepath" "regexp" "strings" + + "github.com/pokt-network/poktroll/pkg/polylog" + _ "github.com/pokt-network/poktroll/pkg/polylog/polyzero" ) type ProtoField struct { @@ -26,37 +29,16 @@ const ( destinationFile = "docusaurus/docs/protocol/governance/params.md" ) -const paramsDocsTemplate = `--- -title: Pocket Network Governance Params -sidebar_position: 1 ---- - -# Governance Parameters - -:::warning -DO NOT EDIT: this file was generated by make docs_update_gov_params_page -::: - -- [Updating this page](#updating-this-page) -- [Adding a new parameter](#adding-a-new-parameter) -- [Parameters](#parameters) - -## Updating this page - -%s -make docs_update_gov_params_page -%s - -## Adding a new parameter +var paramsDocsTemplateStr string -Please follow the instructions in [this guide](../../develop/developer_guide/adding_params.md) to add a new parameter. - -## Parameters - - -|Module | Field Type | Field Name |Comment | -|-------|------------|------------|--------| -` +func init() { + paramsTempalteFile, err := os.ReadFile("./tools/scripts/docusaurus/params_template.md") + if err != nil { + polylog.DefaultContextLogger.Error().Err(err).Send() + os.Exit(1) + } + paramsDocsTemplateStr = string(paramsTempalteFile) +} // writeContentToFile writes the given content to the specified file. func writeContentToFile(file_path, content string) error { @@ -184,7 +166,7 @@ func main() { } // This is necessary because multiline strings in golang do not support embedded backticks. - template := fmt.Sprintf(paramsDocsTemplate, "```", "```") + template := fmt.Sprintf(paramsDocsTemplateStr, "```", "```") docs, err := prepareGovernanceParamsDocs(protoFilePaths, template) if err != nil { diff --git a/tools/scripts/docusaurus/params_template.md b/tools/scripts/docusaurus/params_template.md new file mode 100644 index 000000000..11a0294a8 --- /dev/null +++ b/tools/scripts/docusaurus/params_template.md @@ -0,0 +1,38 @@ +--- +title: Pocket Network Governance Params +sidebar_position: 1 +--- + +# Governance Parameters + +:::warning +DO NOT EDIT: this file was generated by make docs_update_gov_params_page +::: + +- [Updating this page](#updating-this-page) +- [Adding a new parameter](#adding-a-new-parameter) +- [Parameters](#parameters) + +## Access Control + +// TODO_DOCUMENT(@bryanchriswhite) tl;dr, authz. + +## Updating governance parameter values + +// TODO_DOCUMENT(@bryanchriswhite) + +## Updating this page + +%s +make docs_update_gov_params_page +%s + +## Adding a new parameter + +Please follow the instructions in [this guide](../../develop/developer_guide/adding_params.md) to add a new parameter. + +## Parameters + + +|Module | Field Type | Field Name |Comment | +|-------|------------|------------|--------| \ No newline at end of file