diff --git a/changelog.md b/changelog.md index 7dddd856df..9a5a68c0ee 100644 --- a/changelog.md +++ b/changelog.md @@ -15,8 +15,10 @@ * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure * [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient * [1831](https://github.com/zeta-chain/node/pull/1831) - removing unnecessary pointers in context structure +* [1864](https://github.com/zeta-chain/node/pull/1864) - prevent panic in param management * [1848](https://github.com/zeta-chain/node/issues/1848) - create a method to observe deposits to tss address in one evm block * [1885](https://github.com/zeta-chain/node/pull/1885) - change important metrics on port 8123 to be prometheus compatible +* [1863](https://github.com/zeta-chain/node/pull/1863) - remove duplicate ValidateChainParams function ### Features @@ -31,6 +33,7 @@ * [1791](https://github.com/zeta-chain/node/pull/1791) - add e2e tests for feature of restricted address * [1787](https://github.com/zeta-chain/node/pull/1787) - add unit tests for cross-chain evm hooks and e2e test failed withdraw to BTC legacy address * [1840](https://github.com/zeta-chain/node/pull/1840) - fix code coverage test failures ignored in CI +* [1870](https://github.com/zeta-chain/node/pull/1870) - enable emissions pool in local e2e testing * [1868](https://github.com/zeta-chain/node/pull/1868) - run e2e btc tests locally * [1851](https://github.com/zeta-chain/node/pull/1851) - rename usdt to erc20 in e2e tests * [1872](https://github.com/zeta-chain/node/pull/1872) - remove usage of RPC in unit test diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 3dd40d92c3..6aef91156a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -44,71 +44,19 @@ func NewLocalCmd() *cobra.Command { Short: "Run Local E2E tests", Run: localE2ETest, } - cmd.Flags().Bool( - flagContractsDeployed, - false, - "set to to true if running tests again with existing state", - ) - cmd.Flags().Int64( - flagWaitForHeight, - 0, - "block height for tests to begin, ex. --wait-for 100", - ) - cmd.Flags().String( - FlagConfigFile, - "", - "config file to use for the tests", - ) - cmd.Flags().String( - flagConfigOut, - "", - "config file to write the deployed contracts from the setup", - ) - cmd.Flags().Bool( - flagVerbose, - false, - "set to true to enable verbose logging", - ) - cmd.Flags().Bool( - flagTestAdmin, - false, - "set to true to run admin tests", - ) - cmd.Flags().Bool( - flagTestPerformance, - false, - "set to true to run performance tests", - ) - cmd.Flags().Bool( - flagTestCustom, - false, - "set to true to run custom tests", - ) - cmd.Flags().Bool( - flagSkipRegular, - false, - "set to true to skip regular tests", - ) - cmd.Flags().Bool( - flagLight, - false, - "run the most basic regular tests, useful for quick checks", - ) - cmd.Flags().Bool( - flagSetupOnly, - false, - "set to true to only setup the networks", - ) - cmd.Flags().Bool( - flagSkipSetup, - false, - "set to true to skip setup", - ) - cmd.Flags().Bool( - flagSkipBitcoinSetup, - false, - "set to true to skip bitcoin wallet setup", - ) + cmd.Flags().Bool(flagContractsDeployed, false, "set to to true if running tests again with existing state") + cmd.Flags().Int64(flagWaitForHeight, 0, "block height for tests to begin, ex. --wait-for 100") + cmd.Flags().String(FlagConfigFile, "", "config file to use for the tests") + cmd.Flags().Bool(flagVerbose, false, "set to true to enable verbose logging") + cmd.Flags().Bool(flagTestAdmin, false, "set to true to run admin tests") + cmd.Flags().Bool(flagTestPerformance, false, "set to true to run performance tests") + cmd.Flags().Bool(flagTestCustom, false, "set to true to run custom tests") + cmd.Flags().Bool(flagSkipRegular, false, "set to true to skip regular tests") + cmd.Flags().Bool(flagLight, false, "run the most basic regular tests, useful for quick checks") + cmd.Flags().Bool(flagSetupOnly, false, "set to true to only setup the networks") + cmd.Flags().String(flagConfigOut, "", "config file to write the deployed contracts from the setup") + cmd.Flags().Bool(flagSkipSetup, false, "set to true to skip setup") + cmd.Flags().Bool(flagSkipBitcoinSetup, false, "set to true to skip bitcoin wallet setup") return cmd } @@ -242,7 +190,16 @@ func localE2ETest(cmd *cobra.Command, _ []string) { startTime := time.Now() deployerRunner.SetupEVM(contractsDeployed) deployerRunner.SetZEVMContracts() + + // NOTE: this method return an error so we handle it and panic if it occurs unlike other method that panics directly + // TODO: all methods should return errors instead of panicking and this current function should also return an error + // https://github.com/zeta-chain/node/issues/1500 + if err := deployerRunner.FundEmissionsPool(); err != nil { + panic(err) + } + deployerRunner.MintERC20OnEvm(10000) + logger.Print("✅ setup completed in %s", time.Since(startTime)) } @@ -346,4 +303,17 @@ func localE2ETest(cmd *cobra.Command, _ []string) { } logger.Print("✅ e2e tests completed in %s", time.Since(testStartTime).String()) + + // print and validate report + networkReport, err := deployerRunner.GenerateNetworkReport() + if err != nil { + logger.Print("❌ failed to generate network report %v", err) + } + deployerRunner.PrintNetworkReport(networkReport) + if err := networkReport.Validate(); err != nil { + logger.Print("❌ network report validation failed %v", err) + os.Exit(1) + } + + os.Exit(0) } diff --git a/e2e/runner/e2etest.go b/e2e/runner/e2etest.go new file mode 100644 index 0000000000..499d45144d --- /dev/null +++ b/e2e/runner/e2etest.go @@ -0,0 +1,105 @@ +package runner + +import "fmt" + +// E2ETestFunc is a function representing a E2E test +// It takes a E2ERunner as an argument +type E2ETestFunc func(*E2ERunner, []string) + +// E2ETest represents a E2E test with a name, args, description and test func +type E2ETest struct { + Name string + Description string + Args []string + ArgsDefinition []ArgDefinition + E2ETest E2ETestFunc +} + +// NewE2ETest creates a new instance of E2ETest with specified parameters. +func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { + return E2ETest{ + Name: name, + Description: description, + ArgsDefinition: argsDefinition, + E2ETest: e2eTestFunc, + Args: []string{}, + } +} + +// ArgDefinition defines a structure for holding an argument's description along with it's default value. +type ArgDefinition struct { + Description string + DefaultValue string +} + +// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. +func (e E2ETest) DefaultArgs() []string { + defaultArgs := make([]string, len(e.ArgsDefinition)) + for i, spec := range e.ArgsDefinition { + defaultArgs[i] = spec.DefaultValue + } + return defaultArgs +} + +// ArgsDescription returns a string representing the arguments description in a readable format. +func (e E2ETest) ArgsDescription() string { + argsDescription := "" + for _, def := range e.ArgsDefinition { + argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) + if argsDescription != "" { + argsDescription += ", " + } + argsDescription += argDesc + } + return argsDescription +} + +// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. +type E2ETestRunConfig struct { + Name string + Args []string +} + +// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments +func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { + tests := []E2ETestRunConfig{} + for _, testName := range testNames { + tests = append(tests, E2ETestRunConfig{ + Name: testName, + Args: []string{}, + }) + } + return runner.GetE2ETestsToRunByConfig(availableTests, tests) +} + +// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments +func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { + tests := []E2ETest{} + for _, testSpec := range testConfigs { + e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) + if !found { + return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) + } + e2eTestToRun := NewE2ETest( + e2eTest.Name, + e2eTest.Description, + e2eTest.ArgsDefinition, + e2eTest.E2ETest, + ) + // update e2e test args + e2eTestToRun.Args = testSpec.Args + tests = append(tests, e2eTestToRun) + } + + return tests, nil +} + +// findE2ETest finds a e2e test by name +func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { + for _, test := range e2eTests { + if test.Name == e2eTestName { + return test, true + } + } + return E2ETest{}, false +} diff --git a/e2e/runner/report.go b/e2e/runner/report.go index a502c442dd..e798eadd4b 100644 --- a/e2e/runner/report.go +++ b/e2e/runner/report.go @@ -5,9 +5,16 @@ import ( "strings" "text/tabwriter" "time" + + sdkmath "cosmossdk.io/math" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/e2e/txserver" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) -// TestReport is a struct that contains the test report +// TestReport is a struct that contains the report for a specific e2e test +// It can be generated with the RunE2ETestsIntoReport method type TestReport struct { Name string Success bool @@ -53,3 +60,70 @@ func (runner *E2ERunner) PrintTestReports(tr TestReports) { } runner.Logger.PrintNoPrefix(table) } + +// NetworkReport is a struct that contains the report for the network used after running e2e tests +// This report has been initialized to check the emissions pool balance and if the pool is decreasing +// TODO: add more complete data and validation to the network +// https://github.com/zeta-chain/node/issues/1873 +type NetworkReport struct { + EmissionsPoolBalance sdkmath.Int + Height uint64 + CctxCount int +} + +// Validate validates the network report +// This method is used to validate the network after running e2e tests +// It checks the emissions pool balance and if the pool is decreasing +func (nr NetworkReport) Validate() error { + if nr.EmissionsPoolBalance.GTE(sdkmath.NewIntFromBigInt(EmissionsPoolFunding)) { + return fmt.Errorf( + "emissions pool balance is not decreasing, expected less than %s, got %s", + EmissionsPoolFunding, + nr.EmissionsPoolBalance, + ) + } + return nil +} + +// GenerateNetworkReport generates a report for the network used after running e2e tests +func (runner *E2ERunner) GenerateNetworkReport() (NetworkReport, error) { + // get the emissions pool balance + balanceRes, err := runner.BankClient.Balance(runner.Ctx, &banktypes.QueryBalanceRequest{ + Address: txserver.EmissionsPoolAddress, + Denom: config.BaseDenom, + }) + if err != nil { + return NetworkReport{}, err + } + emissionsPoolBalance := balanceRes.Balance + + // fetch the height and number of cctxs, this gives a better idea on the activity of the network + + // get the block height + blockRes, err := runner.ZEVMClient.BlockNumber(runner.Ctx) + if err != nil { + return NetworkReport{}, err + } + + // get the number of cctxs + cctxsRes, err := runner.CctxClient.CctxAll(runner.Ctx, &crosschaintypes.QueryAllCctxRequest{}) + if err != nil { + return NetworkReport{}, err + } + cctxCount := len(cctxsRes.CrossChainTx) + + return NetworkReport{ + EmissionsPoolBalance: emissionsPoolBalance.Amount, + Height: blockRes, + CctxCount: cctxCount, + }, nil +} + +// PrintNetworkReport prints the network report +func (runner *E2ERunner) PrintNetworkReport(nr NetworkReport) { + runner.Logger.Print(" ---📈 Network Report ---") + runner.Logger.Print("Block Height: %d", nr.Height) + runner.Logger.Print("CCTX Processed: %d", nr.CctxCount) + runner.Logger.Print("Emissions Pool Balance: %sZETA", nr.EmissionsPoolBalance.Quo(sdkmath.NewIntFromUint64(1e18))) + +} diff --git a/e2e/runner/run.go b/e2e/runner/run.go new file mode 100644 index 0000000000..f24ff9d9f8 --- /dev/null +++ b/e2e/runner/run.go @@ -0,0 +1,94 @@ +package runner + +import ( + "fmt" + "runtime" + "time" +) + +// RunE2ETests runs a list of e2e tests +func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { + for _, e2eTest := range e2eTests { + if err := runner.RunE2ETest(e2eTest, true); err != nil { + return err + } + } + return nil +} + +// RunE2ETest runs a e2e test +func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { + // return an error on panic + // https://github.com/zeta-chain/node/issues/1500 + defer func() { + if r := recover(); r != nil { + // print stack trace + stack := make([]byte, 4096) + n := runtime.Stack(stack, false) + err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) + } + }() + + startTime := time.Now() + runner.Logger.Print("⏳running - %s", e2eTest.Description) + + // run e2e test, if args are not provided, use default args + args := e2eTest.Args + if len(args) == 0 { + args = e2eTest.DefaultArgs() + } + e2eTest.E2ETest(runner, args) + + //check supplies + if checkAccounting { + if err := runner.CheckZRC20ReserveAndSupply(); err != nil { + return err + } + } + + runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) + + return err +} + +// RunE2ETestsIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report +// The function doesn't return an error, it returns a report with the error +func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { + // go through all tests + reports := make(TestReports, 0, len(e2eTests)) + for _, test := range e2eTests { + // get info before test + balancesBefore, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeBefore := time.Now() + + // run test + testErr := runner.RunE2ETest(test, false) + if testErr != nil { + runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) + } + + // wait 5 sec to make sure we get updated balances + time.Sleep(5 * time.Second) + + // get info after test + balancesAfter, err := runner.GetAccountBalances(true) + if err != nil { + return nil, err + } + timeAfter := time.Now() + + // create report + report := TestReport{ + Name: test.Name, + Success: testErr == nil, + Time: timeAfter.Sub(timeBefore), + GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), + } + reports = append(reports, report) + } + + return reports, nil +} diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 06b123c12f..22f549b296 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -2,16 +2,9 @@ package runner import ( "context" - "fmt" - "runtime" "sync" "time" - "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" - "github.com/zeta-chain/zetacore/e2e/contracts/erc20" - "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - "github.com/zeta-chain/zetacore/e2e/txserver" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcutil" @@ -29,6 +22,10 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" + "github.com/zeta-chain/zetacore/e2e/contracts/erc20" + "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" + "github.com/zeta-chain/zetacore/e2e/txserver" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -159,195 +156,6 @@ func NewE2ERunner( } } -// E2ETestFunc is a function representing a E2E test -// It takes a E2ERunner as an argument -type E2ETestFunc func(*E2ERunner, []string) - -// E2ETest represents a E2E test with a name, args, description and test func -type E2ETest struct { - Name string - Description string - Args []string - ArgsDefinition []ArgDefinition - E2ETest E2ETestFunc -} - -// NewE2ETest creates a new instance of E2ETest with specified parameters. -func NewE2ETest(name, description string, argsDefinition []ArgDefinition, e2eTestFunc E2ETestFunc) E2ETest { - return E2ETest{ - Name: name, - Description: description, - ArgsDefinition: argsDefinition, - E2ETest: e2eTestFunc, - Args: []string{}, - } -} - -// ArgDefinition defines a structure for holding an argument's description along with it's default value. -type ArgDefinition struct { - Description string - DefaultValue string -} - -// DefaultArgs extracts and returns array of default arguments from the ArgsDefinition. -func (e E2ETest) DefaultArgs() []string { - defaultArgs := make([]string, len(e.ArgsDefinition)) - for i, spec := range e.ArgsDefinition { - defaultArgs[i] = spec.DefaultValue - } - return defaultArgs -} - -// ArgsDescription returns a string representing the arguments description in a readable format. -func (e E2ETest) ArgsDescription() string { - argsDescription := "" - for _, def := range e.ArgsDefinition { - argDesc := fmt.Sprintf("%s (%s)", def.Description, def.DefaultValue) - if argsDescription != "" { - argsDescription += ", " - } - argsDescription += argDesc - } - return argsDescription -} - -// E2ETestRunConfig defines the basic configuration for initiating an E2E test, including its name and optional runtime arguments. -type E2ETestRunConfig struct { - Name string - Args []string -} - -// GetE2ETestsToRunByName prepares a list of E2ETests to run based on given test names without arguments -func (runner *E2ERunner) GetE2ETestsToRunByName(availableTests []E2ETest, testNames ...string) ([]E2ETest, error) { - tests := []E2ETestRunConfig{} - for _, testName := range testNames { - tests = append(tests, E2ETestRunConfig{ - Name: testName, - Args: []string{}, - }) - } - return runner.GetE2ETestsToRunByConfig(availableTests, tests) -} - -// GetE2ETestsToRunByConfig prepares a list of E2ETests to run based on provided test names and their corresponding arguments -func (runner *E2ERunner) GetE2ETestsToRunByConfig(availableTests []E2ETest, testConfigs []E2ETestRunConfig) ([]E2ETest, error) { - tests := []E2ETest{} - for _, testSpec := range testConfigs { - e2eTest, found := findE2ETestByName(availableTests, testSpec.Name) - if !found { - return nil, fmt.Errorf("e2e test %s not found", testSpec.Name) - } - e2eTestToRun := NewE2ETest( - e2eTest.Name, - e2eTest.Description, - e2eTest.ArgsDefinition, - e2eTest.E2ETest, - ) - // update e2e test args - e2eTestToRun.Args = testSpec.Args - tests = append(tests, e2eTestToRun) - } - - return tests, nil -} - -// RunE2ETests runs a list of e2e tests -func (runner *E2ERunner) RunE2ETests(e2eTests []E2ETest) (err error) { - for _, e2eTest := range e2eTests { - if err := runner.RunE2ETest(e2eTest, true); err != nil { - return err - } - } - return nil -} - -// RunE2ETestsFromNamesIntoReport runs a list of e2e tests by name in a list of e2e tests and returns a report -// The function doesn't return an error, it returns a report with the error -func (runner *E2ERunner) RunE2ETestsIntoReport(e2eTests []E2ETest) (TestReports, error) { - // go through all tests - reports := make(TestReports, 0, len(e2eTests)) - for _, test := range e2eTests { - // get info before test - balancesBefore, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeBefore := time.Now() - - // run test - testErr := runner.RunE2ETest(test, false) - if testErr != nil { - runner.Logger.Print("test %s failed: %s", test.Name, testErr.Error()) - } - - // wait 5 sec to make sure we get updated balances - time.Sleep(5 * time.Second) - - // get info after test - balancesAfter, err := runner.GetAccountBalances(true) - if err != nil { - return nil, err - } - timeAfter := time.Now() - - // create report - report := TestReport{ - Name: test.Name, - Success: testErr == nil, - Time: timeAfter.Sub(timeBefore), - GasSpent: GetAccountBalancesDiff(balancesBefore, balancesAfter), - } - reports = append(reports, report) - } - - return reports, nil -} - -// RunE2ETest runs a e2e test -func (runner *E2ERunner) RunE2ETest(e2eTest E2ETest, checkAccounting bool) (err error) { - // return an error on panic - // https://github.com/zeta-chain/node/issues/1500 - defer func() { - if r := recover(); r != nil { - // print stack trace - stack := make([]byte, 4096) - n := runtime.Stack(stack, false) - err = fmt.Errorf("%s failed: %v, stack trace %s", e2eTest.Name, r, stack[:n]) - } - }() - - startTime := time.Now() - runner.Logger.Print("⏳running - %s", e2eTest.Description) - - // run e2e test, if args are not provided, use default args - args := e2eTest.Args - if len(args) == 0 { - args = e2eTest.DefaultArgs() - } - e2eTest.E2ETest(runner, args) - - //check supplies - if checkAccounting { - if err := runner.CheckZRC20ReserveAndSupply(); err != nil { - return err - } - } - - runner.Logger.Print("✅ completed in %s - %s", time.Since(startTime), e2eTest.Description) - - return err -} - -// findE2ETest finds a e2e test by name -func findE2ETestByName(e2eTests []E2ETest, e2eTestName string) (E2ETest, bool) { - for _, test := range e2eTests { - if test.Name == e2eTestName { - return test, true - } - } - return E2ETest{}, false -} - // CopyAddressesFrom copies addresses from another E2ETestRunner that initialized the contracts func (runner *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) { // copy TSS address diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go index 16f8f3b64e..bb058a87d6 100644 --- a/e2e/runner/setup_zeta.go +++ b/e2e/runner/setup_zeta.go @@ -4,25 +4,29 @@ import ( "math/big" "time" - "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" - "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" - utils2 "github.com/zeta-chain/zetacore/e2e/utils" - - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/connectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" + "github.com/zeta-chain/zetacore/e2e/txserver" "github.com/btcsuite/btcutil" "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/connectorzevm.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" + "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" + e2eutils "github.com/zeta-chain/zetacore/e2e/utils" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolFunding represents the amount of ZETA to fund the emissions pool with +// This is the same value as used originally on mainnet (20M ZETA) +var EmissionsPoolFunding = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(2e7)) + // SetTSSAddresses set TSS addresses from information queried from ZetaChain func (runner *E2ERunner) SetTSSAddresses() error { runner.Logger.Print("⚙️ setting up TSS address") @@ -71,7 +75,7 @@ func (runner *E2ERunner) SetZEVMContracts() { // deploy system contracts and ZRC20 contracts on ZetaChain uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, err := runner.ZetaTxServer.DeploySystemContractsAndZRC20( - utils2.FungibleAdminName, + e2eutils.FungibleAdminName, runner.ERC20Addr.Hex(), ) if err != nil { @@ -154,14 +158,14 @@ func (runner *E2ERunner) SetZEVMContracts() { panic(err) } - receipt := utils2.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) + receipt := e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txZEVMSwapApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ZEVMSwapApp deployment failed") } runner.ZEVMSwapAppAddr = zevmSwapAppAddr runner.ZEVMSwapApp = zevmSwapApp - receipt = utils2.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) + receipt = e2eutils.MustWaitForTxReceipt(runner.Ctx, runner.ZEVMClient, txContextApp, runner.Logger, runner.ReceiptTimeout) if receipt.Status != 1 { panic("ContextApp deployment failed") } @@ -169,6 +173,7 @@ func (runner *E2ERunner) SetZEVMContracts() { runner.ContextApp = contextApp } +// SetupETHZRC20 sets up the ETH ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupETHZRC20() { ethZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.GoerliLocalnetChain().ChainId)) if err != nil { @@ -185,6 +190,7 @@ func (runner *E2ERunner) SetupETHZRC20() { runner.ETHZRC20 = ethZRC20 } +// SetupBTCZRC20 sets up the BTC ZRC20 in the runner from the values queried from the chain func (runner *E2ERunner) SetupBTCZRC20() { BTCZRC20Addr, err := runner.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(common.BtcRegtestChain().ChainId)) if err != nil { @@ -198,3 +204,10 @@ func (runner *E2ERunner) SetupBTCZRC20() { } runner.BTCZRC20 = BTCZRC20 } + +// FundEmissionsPool funds the emissions pool on ZetaChain with the same value as used originally on mainnet (20M ZETA) +func (runner *E2ERunner) FundEmissionsPool() error { + runner.Logger.Print("⚙️ funding the emissions pool on ZetaChain with 20M ZETA (%s)", txserver.EmissionsPoolAddress) + + return runner.ZetaTxServer.FundEmissionsPool(e2eutils.FungibleAdminName, EmissionsPoolFunding) +} diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 4aedf29c92..910224d567 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math/big" "os" "strings" @@ -32,6 +33,7 @@ import ( etherminttypes "github.com/evmos/ethermint/types" evmtypes "github.com/evmos/ethermint/x/evm/types" rpchttp "github.com/tendermint/tendermint/rpc/client/http" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/common" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" @@ -39,6 +41,10 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// EmissionsPoolAddress is the address of the emissions pool +// This address is constant for all networks because it is derived from emissions name +const EmissionsPoolAddress = "zeta1w43fn2ze2wyhu5hfmegr6vp52c3dgn0srdgymy" + // ZetaTxServer is a ZetaChain tx server for E2E test type ZetaTxServer struct { clientCtx client.Context @@ -279,6 +285,36 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20(account, erc20Addr string) return uniswapV2FactoryAddr, uniswapV2RouterAddr, zevmConnectorAddr, wzetaAddr, erc20zrc20Addr, nil } +// FundEmissionsPool funds the emissions pool with the given amount +func (zts ZetaTxServer) FundEmissionsPool(account string, amount *big.Int) error { + // retrieve account + acc, err := zts.clientCtx.Keyring.Key(account) + if err != nil { + return err + } + addr, err := acc.GetAddress() + if err != nil { + return err + } + + // retrieve account address + emissionPoolAccAddr, err := sdktypes.AccAddressFromBech32(EmissionsPoolAddress) + if err != nil { + return err + } + + // convert amount + amountInt := sdktypes.NewIntFromBigInt(amount) + + // fund emissions pool + _, err = zts.BroadcastTx(account, banktypes.NewMsgSend( + addr, + emissionPoolAccAddr, + sdktypes.NewCoins(sdktypes.NewCoin(config.BaseDenom, amountInt)), + )) + return err +} + // newCodec returns the codec for msg server func newCodec() (*codec.ProtoCodec, codectypes.InterfaceRegistry) { interfaceRegistry := codectypes.NewInterfaceRegistry() diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index a359d93249..e61f6cd76e 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -15,18 +15,15 @@ import ( ) type EmissionMockOptions struct { - UseBankMock bool - UseStakingMock bool - UseObserverMock bool - UseAccountMock bool + UseBankMock bool + UseStakingMock bool + UseObserverMock bool + UseAccountMock bool + UseParamStoreMock bool } func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { - return EmissionKeeperWithMockOptions(t, EmissionMockOptions{ - UseBankMock: false, - UseStakingMock: false, - UseObserverMock: false, - }) + return EmissionKeeperWithMockOptions(t, EmissionMockOptions{}) } func EmissionKeeperWithMockOptions( t testing.TB, @@ -91,18 +88,31 @@ func EmissionKeeperWithMockOptions( observerKeeper = emissionsmocks.NewEmissionObserverKeeper(t) } + var paramStore types.ParamStore + if mockOptions.UseParamStoreMock { + mock := emissionsmocks.NewEmissionParamStore(t) + // mock this method for the keeper constructor + mock.On("HasKeyTable").Maybe().Return(true) + paramStore = mock + } else { + paramStore = sdkKeepers.ParamsKeeper.Subspace(types.ModuleName) + } + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), + paramStore, authtypes.FeeCollectorName, bankKeeper, stakingKeeper, observerKeeper, authKeeper, ) - k.SetParams(ctx, types.DefaultParams()) + + if !mockOptions.UseParamStoreMock { + k.SetParams(ctx, types.DefaultParams()) + } return k, ctx, sdkKeepers, zetaKeepers } @@ -112,3 +122,9 @@ func GetEmissionsBankMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.E require.True(t, ok) return cbk } + +func GetEmissionsParamStoreMock(t testing.TB, keeper *keeper.Keeper) *emissionsmocks.EmissionParamStore { + m, ok := keeper.GetParamStore().(*emissionsmocks.EmissionParamStore) + require.True(t, ok) + return m +} diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index 8e2a40b9cb..d100894b02 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -555,24 +555,6 @@ func (_m *CrosschainObserverKeeper) GetObserverSet(ctx types.Context) (observert return r0, r1 } -// GetParams provides a mock function with given fields: ctx -func (_m *CrosschainObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetPendingNonces provides a mock function with given fields: ctx, tss, chainID func (_m *CrosschainObserverKeeper) GetPendingNonces(ctx types.Context, tss string, chainID int64) (observertypes.PendingNonces, bool) { ret := _m.Called(ctx, tss, chainID) diff --git a/testutil/keeper/mocks/emissions/param_store.go b/testutil/keeper/mocks/emissions/param_store.go new file mode 100644 index 0000000000..f9923f3d94 --- /dev/null +++ b/testutil/keeper/mocks/emissions/param_store.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionParamStore is an autogenerated mock type for the EmissionParamStore type +type EmissionParamStore struct { + mock.Mock +} + +// GetParamSetIfExists provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) GetParamSetIfExists(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// HasKeyTable provides a mock function with given fields: +func (_m *EmissionParamStore) HasKeyTable() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for HasKeyTable") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SetParamSet provides a mock function with given fields: ctx, ps +func (_m *EmissionParamStore) SetParamSet(ctx types.Context, ps paramstypes.ParamSet) { + _m.Called(ctx, ps) +} + +// WithKeyTable provides a mock function with given fields: table +func (_m *EmissionParamStore) WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace { + ret := _m.Called(table) + + if len(ret) == 0 { + panic("no return value specified for WithKeyTable") + } + + var r0 paramstypes.Subspace + if rf, ok := ret.Get(0).(func(paramstypes.KeyTable) paramstypes.Subspace); ok { + r0 = rf(table) + } else { + r0 = ret.Get(0).(paramstypes.Subspace) + } + + return r0 +} + +// NewEmissionParamStore creates a new instance of EmissionParamStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionParamStore(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionParamStore { + mock := &EmissionParamStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/fungible/observer.go b/testutil/keeper/mocks/fungible/observer.go index 3010f8faaf..83cdb37bcc 100644 --- a/testutil/keeper/mocks/fungible/observer.go +++ b/testutil/keeper/mocks/fungible/observer.go @@ -6,8 +6,6 @@ import ( mock "github.com/stretchr/testify/mock" common "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" - types "github.com/cosmos/cosmos-sdk/types" ) @@ -16,24 +14,6 @@ type FungibleObserverKeeper struct { mock.Mock } -// GetParams provides a mock function with given fields: ctx -func (_m *FungibleObserverKeeper) GetParams(ctx types.Context) observertypes.Params { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetParams") - } - - var r0 observertypes.Params - if rf, ok := ret.Get(0).(func(types.Context) observertypes.Params); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(observertypes.Params) - } - - return r0 -} - // GetSupportedChains provides a mock function with given fields: ctx func (_m *FungibleObserverKeeper) GetSupportedChains(ctx types.Context) []*common.Chain { ret := _m.Called(ctx) diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index 0da231a7e5..1180fca27a 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -94,6 +94,11 @@ type EmissionObserverKeeper interface { emissionstypes.ObserverKeeper } +//go:generate mockery --name EmissionParamStore --filename param_store.go --case underscore --output ./emissions +type EmissionParamStore interface { + emissionstypes.ParamStore +} + /** * Observer Mocks */ diff --git a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go index 013f992fd9..50e8164eb7 100644 --- a/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go +++ b/x/crosschain/keeper/msg_server_add_to_intx_tracker_test.go @@ -16,7 +16,7 @@ import ( ) func setupVerificationParams(zk keepertest.ZetaKeepers, ctx sdk.Context, tx_index int64, chainID int64, header ethtypes.Header, headerRLP []byte, block *ethtypes.Block) { - params := zk.ObserverKeeper.GetParams(ctx) + params := zk.ObserverKeeper.GetParamsIfExists(ctx) zk.ObserverKeeper.SetParams(ctx, params) zk.ObserverKeeper.SetBlockHeader(ctx, common.BlockHeader{ Height: block.Number().Int64(), diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 4286bbf48b..6f77d4c2dc 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -33,7 +33,6 @@ type BankKeeper interface { type ObserverKeeper interface { GetObserverSet(ctx sdk.Context) (val observertypes.ObserverSet, found bool) GetBallot(ctx sdk.Context, index string) (val observertypes.Ballot, found bool) - GetParams(ctx sdk.Context) (params observertypes.Params) GetChainParamsByChainID(ctx sdk.Context, chainID int64) (params *observertypes.ChainParams, found bool) GetNodeAccount(ctx sdk.Context, address string) (nodeAccount observertypes.NodeAccount, found bool) GetAllNodeAccount(ctx sdk.Context) (nodeAccounts []observertypes.NodeAccount) diff --git a/x/emissions/abci.go b/x/emissions/abci.go index 96be4ae613..6472ae9e20 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -18,9 +18,10 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return } - validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() - observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() - tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() + + // Get the distribution of rewards + params := keeper.GetParamsIfExists(ctx) + validatorRewards, observerRewards, tssSignerRewards := types.GetRewardsDistributions(params) // TODO : Replace hardcoded slash amount with a parameter // https://github.com/zeta-chain/node/pull/1861 @@ -70,7 +71,6 @@ func DistributeValidatorRewards(ctx sdk.Context, amount sdkmath.Int, bankKeeper // The total rewards are distributed equally among all Successful votes // NotVoted or Unsuccessful votes are slashed // rewards given or slashed amounts are in azeta - func DistributeObserverRewards( ctx sdk.Context, amount sdkmath.Int, @@ -126,7 +126,6 @@ func DistributeObserverRewards( continue } if observerRewardUnits < 0 { - keeper.SlashObserverEmission(ctx, observerAddress.String(), slashAmount) finalDistributionList = append(finalDistributionList, &types.ObserverEmission{ EmissionType: types.EmissionType_Slash, diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index a4a66b34a0..207840f79b 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -102,9 +102,9 @@ func TestBeginBlocker(t *testing.T) { emissionPool := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() blockRewards := emissionstypes.BlockReward - observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ObserverEmissionPercentage)).TruncateInt() - validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ValidatorEmissionPercentage)).TruncateInt() - tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).TssSignerEmissionPercentage)).TruncateInt() + observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ObserverEmissionPercentage)).TruncateInt() + validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).ValidatorEmissionPercentage)).TruncateInt() + tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TssSignerEmissionPercentage)).TruncateInt() distributedRewards := observerRewardsForABlock.Add(validatorRewardsForABlock).Add(tssSignerRewardsForABlock) require.True(t, blockRewards.TruncateInt().GT(distributedRewards)) diff --git a/x/emissions/genesis.go b/x/emissions/genesis.go index 4805bec1ef..b278330eeb 100644 --- a/x/emissions/genesis.go +++ b/x/emissions/genesis.go @@ -19,7 +19,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the emissions module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { var genesis types.GenesisState - genesis.Params = k.GetParams(ctx) + genesis.Params = k.GetParamsIfExists(ctx) genesis.WithdrawableEmissions = k.GetAllWithdrawableEmission(ctx) return &genesis diff --git a/x/emissions/keeper/block_rewards_components.go b/x/emissions/keeper/block_rewards_components.go index ab70e13de5..fe4140805d 100644 --- a/x/emissions/keeper/block_rewards_components.go +++ b/x/emissions/keeper/block_rewards_components.go @@ -17,9 +17,9 @@ func (k Keeper) GetBlockRewardComponents(ctx sdk.Context) (sdk.Dec, sdk.Dec, sdk return reservesFactor, bondFactor, durationFactor } func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper) sdk.Dec { - targetBondRatio := sdk.MustNewDecFromStr(k.GetParams(ctx).TargetBondRatio) - maxBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MaxBondFactor) - minBondFactor := sdk.MustNewDecFromStr(k.GetParams(ctx).MinBondFactor) + targetBondRatio := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).TargetBondRatio) + maxBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MaxBondFactor) + minBondFactor := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).MinBondFactor) currentBondedRatio := stakingKeeper.BondedRatio(ctx) // Bond factor ranges between minBondFactor (0.75) to maxBondFactor (1.25) @@ -37,10 +37,10 @@ func (k Keeper) GetBondFactor(ctx sdk.Context, stakingKeeper types.StakingKeeper } func (k Keeper) GetDurationFactor(ctx sdk.Context) sdk.Dec { - avgBlockTime := sdk.MustNewDecFromStr(k.GetParams(ctx).AvgBlockTime) + avgBlockTime := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).AvgBlockTime) NumberOfBlocksInAMonth := sdk.NewDec(types.SecsInMonth).Quo(avgBlockTime) monthFactor := sdk.NewDec(ctx.BlockHeight()).Quo(NumberOfBlocksInAMonth) - logValueDec := sdk.MustNewDecFromStr(k.GetParams(ctx).DurationFactorConstant) + logValueDec := sdk.MustNewDecFromStr(k.GetParamsIfExists(ctx).DurationFactorConstant) // month * log(1 + 0.02 / 12) fractionNumerator := monthFactor.Mul(logValueDec) // (month * log(1 + 0.02 / 12) ) + 1 diff --git a/x/emissions/keeper/grpc_query_params.go b/x/emissions/keeper/grpc_query_params.go index 7b6b16965a..acc804124d 100644 --- a/x/emissions/keeper/grpc_query_params.go +++ b/x/emissions/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) - return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil + return &types.QueryParamsResponse{Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/emissions/keeper/keeper.go b/x/emissions/keeper/keeper.go index b0fe4d26ee..aadabb61f7 100644 --- a/x/emissions/keeper/keeper.go +++ b/x/emissions/keeper/keeper.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) type ( @@ -17,7 +16,7 @@ type ( cdc codec.BinaryCodec storeKey storetypes.StoreKey memKey storetypes.StoreKey - paramstore paramtypes.Subspace + paramStore types.ParamStore feeCollectorName string bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper @@ -30,7 +29,7 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey, memKey storetypes.StoreKey, - ps paramtypes.Subspace, + ps types.ParamStore, feeCollectorName string, bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, @@ -46,7 +45,7 @@ func NewKeeper( cdc: cdc, storeKey: storeKey, memKey: memKey, - paramstore: ps, + paramStore: ps, feeCollectorName: feeCollectorName, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, @@ -78,3 +77,7 @@ func (k Keeper) GetObserverKeeper() types.ObserverKeeper { func (k Keeper) GetAuthKeeper() types.AccountKeeper { return k.authKeeper } + +func (k Keeper) GetParamStore() types.ParamStore { + return k.paramStore +} diff --git a/x/emissions/keeper/params.go b/x/emissions/keeper/params.go index 17d506bbca..5369e0953a 100644 --- a/x/emissions/keeper/params.go +++ b/x/emissions/keeper/params.go @@ -5,13 +5,14 @@ import ( "github.com/zeta-chain/zetacore/x/emissions/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) +// GetParamsIfExists get all parameters as types.Params if they exist +// non existent parameters will return zero values +func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { + k.paramStore.GetParamSetIfExists(ctx, ¶ms) return } // SetParams set the params func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramstore.SetParamSet(ctx, ¶ms) + k.paramStore.SetParamSet(ctx, ¶ms) } diff --git a/x/emissions/keeper/params_test.go b/x/emissions/keeper/params_test.go index e73031b824..6b85f76444 100644 --- a/x/emissions/keeper/params_test.go +++ b/x/emissions/keeper/params_test.go @@ -219,9 +219,9 @@ func TestKeeper_GetParams(t *testing.T) { }, tt.isPanic) if tt.isPanic != "" { - require.Equal(t, emissionstypes.DefaultParams(), k.GetParams(ctx)) + require.Equal(t, emissionstypes.DefaultParams(), k.GetParamsIfExists(ctx)) } else { - require.Equal(t, tt.params, k.GetParams(ctx)) + require.Equal(t, tt.params, k.GetParamsIfExists(ctx)) } }) } diff --git a/x/emissions/types/distributions.go b/x/emissions/types/distributions.go new file mode 100644 index 0000000000..fb67135ea9 --- /dev/null +++ b/x/emissions/types/distributions.go @@ -0,0 +1,34 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetRewardsDistributions returns the current distribution of rewards +// for validators, observers and TSS signers +// If the percentage is not set, it returns 0 +func GetRewardsDistributions(params Params) (sdkmath.Int, sdkmath.Int, sdkmath.Int) { + // Fetch the validator rewards, use 0 if the percentage is not set + validatorRewards := sdk.NewInt(0) + validatorRewardsDec, err := sdk.NewDecFromStr(params.ValidatorEmissionPercentage) + if err == nil { + validatorRewards = validatorRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the observer rewards, use 0 if the percentage is not set + observerRewards := sdk.NewInt(0) + observerRewardsDec, err := sdk.NewDecFromStr(params.ObserverEmissionPercentage) + if err == nil { + observerRewards = observerRewardsDec.Mul(BlockReward).TruncateInt() + } + + // Fetch the TSS signer rewards, use 0 if the percentage is not set + tssSignerRewards := sdk.NewInt(0) + tssSignerRewardsDec, err := sdk.NewDecFromStr(params.TssSignerEmissionPercentage) + if err == nil { + tssSignerRewards = tssSignerRewardsDec.Mul(BlockReward).TruncateInt() + } + + return validatorRewards, observerRewards, tssSignerRewards +} diff --git a/x/emissions/types/distributions_test.go b/x/emissions/types/distributions_test.go new file mode 100644 index 0000000000..76d2534583 --- /dev/null +++ b/x/emissions/types/distributions_test.go @@ -0,0 +1,34 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/x/emissions/types" +) + +func TestKeeper_GetRewardsDistributions(t *testing.T) { + t.Run("Return fractions of block reward", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "0.5", + ObserverEmissionPercentage: "0.25", + TssSignerEmissionPercentage: "0.25", + }) + + require.EqualValues(t, "4810474537037037037", val.String()) // 0.5 * block reward + require.EqualValues(t, "2405237268518518518", obs.String()) // 0.25 * block reward + require.EqualValues(t, "2405237268518518518", tss.String()) // 0.25 * block reward + }) + + t.Run("Return zero in case of invalid string", func(t *testing.T) { + val, obs, tss := types.GetRewardsDistributions(types.Params{ + ValidatorEmissionPercentage: "invalid", + ObserverEmissionPercentage: "invalid", + TssSignerEmissionPercentage: "invalid", + }) + + require.True(t, val.IsZero()) + require.True(t, obs.IsZero()) + require.True(t, tss.IsZero()) + }) +} diff --git a/x/emissions/types/expected_keepers.go b/x/emissions/types/expected_keepers.go index 286490e7c5..298a0caf42 100644 --- a/x/emissions/types/expected_keepers.go +++ b/x/emissions/types/expected_keepers.go @@ -3,6 +3,7 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -28,3 +29,11 @@ type BankKeeper interface { type StakingKeeper interface { BondedRatio(ctx sdk.Context) sdk.Dec } + +// ParamStore defines the expected paramstore methods to store and load Params (noalias) +type ParamStore interface { + GetParamSetIfExists(ctx sdk.Context, ps paramstypes.ParamSet) + SetParamSet(ctx sdk.Context, ps paramstypes.ParamSet) + WithKeyTable(table paramstypes.KeyTable) paramstypes.Subspace + HasKeyTable() bool +} diff --git a/x/fungible/types/expected_keepers.go b/x/fungible/types/expected_keepers.go index feddedf4b7..5ffc04c723 100644 --- a/x/fungible/types/expected_keepers.go +++ b/x/fungible/types/expected_keepers.go @@ -13,7 +13,6 @@ import ( evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/zeta-chain/zetacore/common" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // AccountKeeper defines the expected account keeper used for simulations (noalias) @@ -32,7 +31,6 @@ type BankKeeper interface { } type ObserverKeeper interface { - GetParams(ctx sdk.Context) (params observertypes.Params) GetSupportedChains(ctx sdk.Context) []*common.Chain } diff --git a/x/observer/genesis.go b/x/observer/genesis.go index 8098394f65..1c1b10ef26 100644 --- a/x/observer/genesis.go +++ b/x/observer/genesis.go @@ -141,7 +141,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the observer module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - params := k.GetParams(ctx) + params := k.GetParamsIfExists(ctx) chainParams, found := k.GetChainParamsList(ctx) if !found { diff --git a/x/observer/keeper/ballot.go b/x/observer/keeper/ballot.go index 787afd7831..4c960743ac 100644 --- a/x/observer/keeper/ballot.go +++ b/x/observer/keeper/ballot.go @@ -63,7 +63,7 @@ func (k Keeper) AddBallotToList(ctx sdk.Context, ballot types.Ballot) { // GetMaturedBallotList Returns a list of ballots which are matured at current height func (k Keeper) GetMaturedBallotList(ctx sdk.Context) []string { - maturityBlocks := k.GetParams(ctx).BallotMaturityBlocks + maturityBlocks := k.GetParamsIfExists(ctx).BallotMaturityBlocks list, found := k.GetBallotList(ctx, ctx.BlockHeight()-maturityBlocks) if !found { return []string{} diff --git a/x/observer/keeper/grpc_query_params.go b/x/observer/keeper/grpc_query_params.go index f02060de25..ef095420af 100644 --- a/x/observer/keeper/grpc_query_params.go +++ b/x/observer/keeper/grpc_query_params.go @@ -15,5 +15,5 @@ func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types } ctx := sdk.UnwrapSDKContext(c) return &types.QueryParamsResponse{ - Params: k.GetParams(ctx)}, nil + Params: k.GetParamsIfExists(ctx)}, nil } diff --git a/x/observer/keeper/params.go b/x/observer/keeper/params.go index 3c570c9cf0..f137d04a18 100644 --- a/x/observer/keeper/params.go +++ b/x/observer/keeper/params.go @@ -5,12 +5,6 @@ import ( "github.com/zeta-chain/zetacore/x/observer/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramstore.GetParamSet(ctx, ¶ms) - return -} - func (k Keeper) GetParamsIfExists(ctx sdk.Context) (params types.Params) { k.paramstore.GetParamSetIfExists(ctx, ¶ms) return diff --git a/x/observer/keeper/params_test.go b/x/observer/keeper/params_test.go index 5ec4c260a3..c487ea0f73 100644 --- a/x/observer/keeper/params_test.go +++ b/x/observer/keeper/params_test.go @@ -18,7 +18,7 @@ func TestGetParams(t *testing.T) { k.SetParams(ctx, params) - require.EqualValues(t, params, k.GetParams(ctx)) + require.EqualValues(t, params, k.GetParamsIfExists(ctx)) } func TestGenerateAddress(t *testing.T) { diff --git a/x/observer/migrations/v3/migrate_test.go b/x/observer/migrations/v3/migrate_test.go index 81e3747517..2ff0b36255 100644 --- a/x/observer/migrations/v3/migrate_test.go +++ b/x/observer/migrations/v3/migrate_test.go @@ -19,7 +19,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err := v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 0) // update admin policy @@ -42,7 +42,7 @@ func TestMigrateStore(t *testing.T) { k.SetParams(ctx, params) err = v3.MigrateStore(ctx, k) require.NoError(t, err) - params = k.GetParams(ctx) + params = k.GetParamsIfExists(ctx) require.Len(t, params.AdminPolicy, 2) require.Equal(t, params.AdminPolicy[0].PolicyType, types.Policy_Type_group1) require.Equal(t, params.AdminPolicy[1].PolicyType, types.Policy_Type_group2) diff --git a/x/observer/migrations/v4/migrate.go b/x/observer/migrations/v4/migrate.go index 7444c46233..9ca22467a2 100644 --- a/x/observer/migrations/v4/migrate.go +++ b/x/observer/migrations/v4/migrate.go @@ -10,7 +10,6 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) diff --git a/x/observer/migrations/v5/migrate.go b/x/observer/migrations/v5/migrate.go index a626ce192b..2eb261a3f4 100644 --- a/x/observer/migrations/v5/migrate.go +++ b/x/observer/migrations/v5/migrate.go @@ -10,7 +10,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) types.Params + GetParamsIfExists(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) GetChainParamsList(ctx sdk.Context) (params types.ChainParamsList, found bool) SetChainParamsList(ctx sdk.Context, params types.ChainParamsList) @@ -64,7 +64,7 @@ func MigrateObserverParams(ctx sdk.Context, observerKeeper observerKeeper) error } // search for the observer params with chain params entry - observerParams := observerKeeper.GetParams(ctx).ObserverParams + observerParams := observerKeeper.GetParamsIfExists(ctx).ObserverParams for _, observerParam := range observerParams { for i := range chainParamsList.ChainParams { // if the chain is found, update the chain params with the observer params diff --git a/x/observer/migrations/v7/migrate.go b/x/observer/migrations/v7/migrate.go index 2b2c759e09..414a87a38e 100644 --- a/x/observer/migrations/v7/migrate.go +++ b/x/observer/migrations/v7/migrate.go @@ -8,7 +8,7 @@ import ( // observerKeeper prevents circular dependency type observerKeeper interface { - GetParams(ctx sdk.Context) (params types.Params) + GetParamsIfExists(ctx sdk.Context) (params types.Params) GetAuthorityKeeper() types.AuthorityKeeper } @@ -20,7 +20,7 @@ func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { // MigratePolicies migrates policies from observer to authority func MigratePolicies(ctx sdk.Context, observerKeeper observerKeeper) error { - params := observerKeeper.GetParams(ctx) + params := observerKeeper.GetParamsIfExists(ctx) authorityKeeper := observerKeeper.GetAuthorityKeeper() var policies authoritytypes.Policies diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 200066f109..69e4e8da2c 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -2,13 +2,10 @@ package config import ( "encoding/json" - "fmt" "strings" "sync" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/common" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // KeyringBackend is the type of keyring backend to use for the hotkey @@ -138,63 +135,3 @@ func (c *Config) GetKeyringBackend() KeyringBackend { defer c.cfgLock.RUnlock() return c.KeyringBackend } - -// TODO: remove this duplicated function https://github.com/zeta-chain/node/issues/1838 -// ValidateChainParams performs some basic checks on core params -func ValidateChainParams(chainParams *observertypes.ChainParams) error { - if chainParams == nil { - return fmt.Errorf("invalid chain params: nil") - } - chain := common.GetChainFromChainID(chainParams.ChainId) - if chain == nil { - return fmt.Errorf("invalid chain params: chain %d not supported", chainParams.ChainId) - } - if chainParams.ConfirmationCount < 1 { - return fmt.Errorf("invalid chain params: ConfirmationCount %d", chainParams.ConfirmationCount) - } - // zeta chain skips the rest of the checks for now - if chain.IsZetaChain() { - return nil - } - - // check tickers - if chainParams.GasPriceTicker < 1 { - return fmt.Errorf("invalid chain params: GasPriceTicker %d", chainParams.GasPriceTicker) - } - if chainParams.InTxTicker < 1 { - return fmt.Errorf("invalid chain params: InTxTicker %d", chainParams.InTxTicker) - } - if chainParams.OutTxTicker < 1 { - return fmt.Errorf("invalid chain params: OutTxTicker %d", chainParams.OutTxTicker) - } - if chainParams.OutboundTxScheduleInterval < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleInterval %d", chainParams.OutboundTxScheduleInterval) - } - if chainParams.OutboundTxScheduleLookahead < 1 { - return fmt.Errorf("invalid chain params: OutboundTxScheduleLookahead %d", chainParams.OutboundTxScheduleLookahead) - } - - // chain type specific checks - if common.IsBitcoinChain(chainParams.ChainId) && chainParams.WatchUtxoTicker < 1 { - return fmt.Errorf("invalid chain params: watchUtxo ticker %d", chainParams.WatchUtxoTicker) - } - if common.IsEVMChain(chainParams.ChainId) { - if !validCoreContractAddress(chainParams.ZetaTokenContractAddress) { - return fmt.Errorf("invalid chain params: zeta token contract address %s", chainParams.ZetaTokenContractAddress) - } - if !validCoreContractAddress(chainParams.ConnectorContractAddress) { - return fmt.Errorf("invalid chain params: connector contract address %s", chainParams.ConnectorContractAddress) - } - if !validCoreContractAddress(chainParams.Erc20CustodyContractAddress) { - return fmt.Errorf("invalid chain params: erc20 custody contract address %s", chainParams.Erc20CustodyContractAddress) - } - } - return nil -} - -func validCoreContractAddress(address string) bool { - if !strings.HasPrefix(address, "0x") { - return false - } - return ethcommon.IsHexAddress(address) -} diff --git a/zetaclient/zetabridge/zetacore_bridge.go b/zetaclient/zetabridge/zetacore_bridge.go index fbe5187088..babdc505bf 100644 --- a/zetaclient/zetabridge/zetacore_bridge.go +++ b/zetaclient/zetabridge/zetacore_bridge.go @@ -221,7 +221,7 @@ func (b *ZetaCoreBridge) UpdateZetaCoreContext(coreContext *corecontext.ZetaCore // check and update chain params for each chain for _, chainParam := range chainParams { - err := config.ValidateChainParams(chainParam) + err := observertypes.ValidateChainParams(chainParam) if err != nil { b.logger.Warn().Err(err).Msgf("Invalid chain params for chain %d", chainParam.ChainId) continue