diff --git a/app/common_test.go b/app/common_test.go index 28de268d..7e98a9f5 100644 --- a/app/common_test.go +++ b/app/common_test.go @@ -31,8 +31,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" - dnConfig "github.com/dfinance/dnode/cmd/config" vmConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/cmd/config/restrictions" "github.com/dfinance/dnode/helpers/tests" "github.com/dfinance/dnode/x/currencies" @@ -369,13 +369,13 @@ func GetGenesis(app *DnServiceApp, chainID, monikerID string, nodeAccPrivKey sec stakingGenesis := staking.GenesisState{} app.cdc.MustUnmarshalJSON(genesisState[staking.ModuleName], &stakingGenesis) - stakingGenesis.Params.BondDenom = dnConfig.MainDenom + stakingGenesis.Params.BondDenom = defaults.MainDenom genesisState[staking.ModuleName] = codec.MustMarshalJSONIndent(app.cdc, stakingGenesis) // update mint denom mintGenesis := mint.GenesisState{} app.cdc.MustUnmarshalJSON(genesisState[mint.ModuleName], &mintGenesis) - mintGenesis.Params.MintDenom = dnConfig.MainDenom + mintGenesis.Params.MintDenom = defaults.MainDenom genesisState[mint.ModuleName] = codec.MustMarshalJSONIndent(app.cdc, mintGenesis) oracleGenesis := oracle.GenesisState{ @@ -408,14 +408,14 @@ func GetGenesis(app *DnServiceApp, chainID, monikerID string, nodeAccPrivKey sec msg := staking.NewMsgCreateValidator( nodeAcc.Address.Bytes(), nodeAcc.PubKey, - sdk.NewCoin(dnConfig.MainDenom, tokenAmount), + sdk.NewCoin(defaults.MainDenom, tokenAmount), staking.NewDescription(monikerID, "", "", "", ""), staking.NewCommissionRates(commissionRate, commissionMaxRate, commissionChangeRate), minSelfDelegation, ) txFee := auth.StdFee{ - Amount: sdk.Coins{{Denom: dnConfig.MainDenom, Amount: sdk.NewInt(1)}}, + Amount: sdk.Coins{{Denom: defaults.MainDenom, Amount: sdk.NewInt(1)}}, Gas: defGasAmount, } txMemo := "testmemo" @@ -503,7 +503,7 @@ func GenTx(msgs []sdk.Msg, accnums []uint64, seq []uint64, priv ...crypto.PrivKe memo := "testmemotestmemo" fee := auth.StdFee{ - Amount: sdk.Coins{{Denom: dnConfig.MainDenom, Amount: sdk.NewInt(1)}}, + Amount: sdk.Coins{{Denom: defaults.MainDenom, Amount: sdk.NewInt(1)}}, Gas: defGasAmount, } @@ -524,7 +524,7 @@ func GenTx(msgs []sdk.Msg, accnums []uint64, seq []uint64, priv ...crypto.PrivKe // GenDefCoins returns Coins with xfi amount. func GenDefCoins(t *testing.T) sdk.Coins { - coins, err := sdk.ParseCoins("1000000000000000000000" + dnConfig.MainDenom) + coins, err := sdk.ParseCoins("1000000000000000000000" + defaults.MainDenom) if t != nil { require.NoError(t, err) } diff --git a/app/integ_app_test.go b/app/integ_app_test.go index 9e0bca57..f817f94d 100644 --- a/app/integ_app_test.go +++ b/app/integ_app_test.go @@ -12,7 +12,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" - dnConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/x/common_vm" "github.com/dfinance/dnode/x/vm" "github.com/dfinance/dnode/x/vm/client/vm_client" @@ -138,7 +138,7 @@ func TestIntegApp_Crisis(t *testing.T) { } getXfiBtcAccCoins := func(addr sdk.AccAddress) (sdk.Coin, sdk.Coin) { - xfiCoin := sdk.NewCoin(dnConfig.MainDenom, sdk.ZeroInt()) + xfiCoin := sdk.NewCoin(defaults.MainDenom, sdk.ZeroInt()) btcCoin := sdk.NewCoin("btc", sdk.ZeroInt()) acc := GetAccountCheckTx(app, addr) for _, coin := range acc.GetCoins() { diff --git a/app/integ_gov_test.go b/app/integ_gov_test.go index 9aea205f..96b9ef75 100644 --- a/app/integ_gov_test.go +++ b/app/integ_gov_test.go @@ -11,7 +11,7 @@ import ( "github.com/dfinance/glav" "github.com/stretchr/testify/require" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/helpers/tests" cliTester "github.com/dfinance/dnode/helpers/tests/clitester" "github.com/dfinance/dnode/x/common_vm" @@ -136,22 +136,22 @@ func TestIntegGov_StdlibUpdate(t *testing.T) { { // invalid from { - tx := ct.TxVmStdlibUpdateProposal("invalid_address", moduleV1BytecodePath, "http://ya.ru", "Desc", 50, config.GovMinDeposit) + tx := ct.TxVmStdlibUpdateProposal("invalid_address", moduleV1BytecodePath, "http://ya.ru", "Desc", 50, defaults.GovMinDepositCoin) tx.CheckFailedWithErrorSubstring("keyring") } // invalid file path { - tx := ct.TxVmStdlibUpdateProposal(senderAddr, "invalid_path", "http://ya.ru", "Desc", 50, config.GovMinDeposit) + tx := ct.TxVmStdlibUpdateProposal(senderAddr, "invalid_path", "http://ya.ru", "Desc", 50, defaults.GovMinDepositCoin) tx.CheckFailedWithErrorSubstring("moveFile") } // invalid blockHeight { - tx1 := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Desc", 0, config.GovMinDeposit) + tx1 := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Desc", 0, defaults.GovMinDepositCoin) tx1.CheckFailedWithErrorSubstring("height") - tx2 := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Desc", 0, config.GovMinDeposit) + tx2 := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Desc", 0, defaults.GovMinDepositCoin) tx2.ChangeCmdArg("0", "abc") tx2.CheckFailedWithErrorSubstring("ParseInt") } @@ -159,7 +159,7 @@ func TestIntegGov_StdlibUpdate(t *testing.T) { // Add DVM stdlib update proposal for module version 1 (cover the min deposit) { - tx := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Foo module V1 added", -1, config.GovMinDeposit) + tx := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV1BytecodePath, "http://ya.ru", "Foo module V1 added", -1, defaults.GovMinDepositCoin) ct.SubmitAndConfirmProposal(tx, true) } @@ -182,7 +182,7 @@ func TestIntegGov_StdlibUpdate(t *testing.T) { // Add DVM stdlib update proposal for module version 2 { - tx := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV2BytecodePath, "http://ya.ru", "Foo module V2 added", -1, config.GovMinDeposit) + tx := ct.TxVmStdlibUpdateProposal(senderAddr, moduleV2BytecodePath, "http://ya.ru", "Foo module V2 added", -1, defaults.GovMinDepositCoin) ct.SubmitAndConfirmProposal(tx, true) } @@ -222,27 +222,27 @@ func TestIntegGov_RegisterCurrency(t *testing.T) { { // invalid from { - tx := ct.TxCCAddCurrencyProposal("invalid_from", crDenom, crBalancePathHex, crInfoPathHex, crDecimals, config.GovMinDeposit) + tx := ct.TxCCAddCurrencyProposal("invalid_from", crDenom, crBalancePathHex, crInfoPathHex, crDecimals, defaults.GovMinDepositCoin) tx.CheckFailedWithErrorSubstring("keyring") } // invalid denom { - tx := ct.TxCCAddCurrencyProposal(senderAddr, "invalid1", crBalancePathHex, crInfoPathHex, crDecimals, config.GovMinDeposit) + tx := ct.TxCCAddCurrencyProposal(senderAddr, "invalid1", crBalancePathHex, crInfoPathHex, crDecimals, defaults.GovMinDepositCoin) tx.CheckFailedWithErrorSubstring("denom") } // invalid path { - tx1 := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, "zzvv", crInfoPathHex, crDecimals, config.GovMinDeposit) + tx1 := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, "zzvv", crInfoPathHex, crDecimals, defaults.GovMinDepositCoin) tx1.CheckFailedWithErrorSubstring("vmBalancePathHex") - tx2 := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, "abc", crDecimals, config.GovMinDeposit) + tx2 := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, "abc", crDecimals, defaults.GovMinDepositCoin) tx2.CheckFailedWithErrorSubstring("vmInfoPathHex") } // invalid decimals { - tx := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, crInfoPathHex, crDecimals, config.GovMinDeposit) + tx := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, crInfoPathHex, crDecimals, defaults.GovMinDepositCoin) tx.ChangeCmdArg("8", "abc") tx.CheckFailedWithErrorSubstring("decimals") } @@ -250,7 +250,7 @@ func TestIntegGov_RegisterCurrency(t *testing.T) { // Add proposal { - tx := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, crInfoPathHex, crDecimals, config.GovMinDeposit) + tx := ct.TxCCAddCurrencyProposal(senderAddr, crDenom, crBalancePathHex, crInfoPathHex, crDecimals, defaults.GovMinDepositCoin) ct.SubmitAndConfirmProposal(tx, false) } diff --git a/app/unit_distribution_test.go b/app/unit_distribution_test.go index e754b57d..2f0b1c95 100644 --- a/app/unit_distribution_test.go +++ b/app/unit_distribution_test.go @@ -10,7 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" ) // Check disabled distribution transactions. @@ -46,7 +46,7 @@ func TestDistribution_MessagesNotWorking(t *testing.T) { // check fund community pool. { senderAcc, senderPrivKey := GetAccountCheckTx(app, nodeAddress), nodePrivKey - withdrawComissionMsg := distribution.NewMsgFundPublicTreasuryPool(sdk.NewCoins(sdk.NewCoin(config.MainDenom, sdk.NewInt(1))), nodeAddress) + withdrawComissionMsg := distribution.NewMsgFundPublicTreasuryPool(sdk.NewCoins(sdk.NewCoin(defaults.MainDenom, sdk.NewInt(1))), nodeAddress) tx := GenTx([]sdk.Msg{withdrawComissionMsg}, []uint64{senderAcc.GetAccountNumber()}, []uint64{senderAcc.GetSequence()}, senderPrivKey) CheckDeliverSpecificErrorTx(t, app, tx, errors.ErrUnknownRequest) } diff --git a/cmd/config/bech_config.go b/cmd/config/bech_config.go new file mode 100644 index 00000000..040b15a7 --- /dev/null +++ b/cmd/config/bech_config.go @@ -0,0 +1,20 @@ +package config + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + MainPrefix = "wallet" // Main prefix for all addresses. + Bech32PrefixAccAddr = MainPrefix // Bech32 prefix for account addresses. + Bech32PrefixAccPub = MainPrefix + sdk.PrefixPublic // Bech32 prefix for accounts pub keys. + Bech32PrefixValAddr = MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator // Bech32 prefix for validators addresses. + Bech32PrefixValPub = MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic // Bech32 prefix for validator pub keys. + Bech32PrefixConsAddr = MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus // Bech32 prefix for consensus addresses. + Bech32PrefixConsPub = MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic // Bech32 prefix for consensus pub keys. +) + +// Initializing DN custom prefixes. +func InitBechPrefixes(config *sdk.Config) { + config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) + config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) + config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) +} diff --git a/cmd/config/cli_config.go b/cmd/config/cli_config.go index bb5c17fc..135572b4 100644 --- a/cmd/config/cli_config.go +++ b/cmd/config/cli_config.go @@ -15,8 +15,7 @@ import ( ) const ( - flagGet = "get" - DefaultCompilerAddr = "tcp://127.0.0.1:50051" + flagGet = "get" ) var configDefaults = map[string]string{ diff --git a/cmd/config/config.go b/cmd/config/config.go deleted file mode 100644 index eb56a2de..00000000 --- a/cmd/config/config.go +++ /dev/null @@ -1,125 +0,0 @@ -// Configuration for DNode and DNCli. -package config - -import ( - "bytes" - "os" - "path/filepath" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/viper" - tmOs "github.com/tendermint/tendermint/libs/os" -) - -const ( - LiquidityProviderDenom = "lpt" - MainDenom = "xfi" - StakingDenom = "sxfi" - DefaultFeeAmount = "100000000000000" - DefaultFee = DefaultFeeAmount + MainDenom - MainPrefix = "wallet" // Main prefix for all addresses. - Bech32PrefixAccAddr = MainPrefix // Bech32 prefix for account addresses. - Bech32PrefixAccPub = MainPrefix + sdk.PrefixPublic // Bech32 prefix for accounts pub keys. - Bech32PrefixValAddr = MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator // Bech32 prefix for validators addresses. - Bech32PrefixValPub = MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic // Bech32 prefix for validator pub keys. - Bech32PrefixConsAddr = MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus // Bech32 prefix for consensus addresses. - Bech32PrefixConsPub = MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic // Bech32 prefix for consensus pub keys. - - VMConfigFile = "vm.toml" // Default file to store config. - ConfigDir = "config" // Default directory to store all configurations. - - // VM configs. - DefaultVMAddress = "tcp://127.0.0.1:50051" // Default virtual machine address to connect from Cosmos SDK. - DefaultDataListen = "tcp://127.0.0.1:50052" // Default data server address to listen for connections from VM. - - // Default retry configs. - DefaultMaxAttempts = 0 // Default maximum attempts for retry. - DefaultReqTimeout = 0 // Default request timeout per attempt [ms]. - - // Default governance params. - DefaultGovMinDepositAmount = "100000000000000000000" // 100 sxfi - - // Invariants check period for crisis module (in blocks) - DefInvCheckPeriod = 10 - - // Default staking validator minSelfDelegation amount - DefMinSelfDelegation = "250000000000000000000000" // 250000 sxfi -) - -var ( - GovMinDeposit sdk.Coin -) - -// Virtual machine connection config (see config/vm.toml). -type VMConfig struct { - Address string `mapstructure:"vm_address"` // address of virtual machine. - DataListen string `mapstructure:"vm_data_listen"` // data listen. - - // Retry policy - MaxAttempts uint `mapstructure:"vm_retry_max_attempts"` // maximum attempts for retry (0 - infinity) - ReqTimeoutInMs uint `mapstructure:"vm_retry_req_timeout_ms"` // request timeout per attempt (0 - infinity) [ms] -} - -// Default VM configuration. -func DefaultVMConfig() *VMConfig { - return &VMConfig{ - Address: DefaultVMAddress, - DataListen: DefaultDataListen, - MaxAttempts: DefaultMaxAttempts, - ReqTimeoutInMs: DefaultReqTimeout, - } -} - -// Initializing DN custom prefixes. -func InitBechPrefixes(config *sdk.Config) { - config.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) - config.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) - config.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) -} - -// Write VM config file in configuration directory. -func WriteVMConfig(rootDir string, vmConfig *VMConfig) { - configFilePath := filepath.Join(rootDir, ConfigDir, VMConfigFile) - - var buffer bytes.Buffer - - if err := configTemplate.Execute(&buffer, vmConfig); err != nil { - panic(err) - } - - tmOs.MustWriteFile(configFilePath, buffer.Bytes(), 0644) -} - -// Read VM config file from configuration directory. -func ReadVMConfig(rootDir string) (*VMConfig, error) { - configFilePath := filepath.Join(rootDir, ConfigDir, VMConfigFile) - - if _, err := os.Stat(configFilePath); os.IsNotExist(err) { - config := DefaultVMConfig() - WriteVMConfig(rootDir, config) - return config, nil - } - - viper.SetConfigFile(configFilePath) - - if err := viper.ReadInConfig(); err != nil { - panic(err) - } - - // read config - config := DefaultVMConfig() - if err := viper.Unmarshal(config); err != nil { - panic(err) - } - - return config, nil -} - -func init() { - minDepositAmount, ok := sdk.NewIntFromString(DefaultGovMinDepositAmount) - if !ok { - panic("governance genesisState: minDeposit convertation failed") - } - - GovMinDeposit = sdk.NewCoin(StakingDenom, minDepositAmount) -} diff --git a/cmd/config/gen_init.go b/cmd/config/gen_init.go deleted file mode 100644 index 29d5854e..00000000 --- a/cmd/config/gen_init.go +++ /dev/null @@ -1,211 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/cosmos/cosmos-sdk/x/crisis" - "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/mint" - "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/spf13/viper" - cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/libs/cli" - tmos "github.com/tendermint/tendermint/libs/os" - tmrand "github.com/tendermint/tendermint/libs/rand" - tmTypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/genutil" -) - -const ( - flagOverwrite = "overwrite" - maxGas = 10000000 -) - -type printInfo struct { - Moniker string `json:"moniker" yaml:"moniker"` - ChainID string `json:"chain_id" yaml:"chain_id"` - NodeID string `json:"node_id" yaml:"node_id"` - GenTxsDir string `json:"gentxs_dir" yaml:"gentxs_dir"` - AppMessage json.RawMessage `json:"app_message" yaml:"app_message"` -} - -func newPrintInfo(moniker, chainID, nodeID, genTxsDir string, - appMessage json.RawMessage) printInfo { - - return printInfo{ - Moniker: moniker, - ChainID: chainID, - NodeID: nodeID, - GenTxsDir: genTxsDir, - AppMessage: appMessage, - } -} - -func displayInfo(cdc *codec.Codec, info printInfo) error { - out, err := codec.MarshalJSONIndent(cdc, info) - if err != nil { - return err - } - - _, err = fmt.Fprintf(os.Stderr, "%s\n", string(sdk.MustSortJSON(out))) - return err -} - -// InitCmd returns a command that initializes all files needed for Tendermint -// and the respective application. -func InitCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, - defaultNodeHome string) *cobra.Command { // nolint: golint - cmd := &cobra.Command{ - Use: "init [moniker]", - Short: "Initialize private validator, p2p, genesis, and application configuration files", - Long: `Initialize validators's and node's configuration files.`, - Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, args []string) error { - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) - - chainID := viper.GetString(flags.FlagChainID) - if chainID == "" { - chainID = fmt.Sprintf("test-chain-%v", tmrand.Str(6)) - } - - nodeID, _, err := genutil.InitializeNodeValidatorFiles(config) - if err != nil { - return err - } - - config.Moniker = args[0] - - genFile := config.GenesisFile() - if !viper.GetBool(flagOverwrite) && tmos.FileExists(genFile) { - return fmt.Errorf("genesis.json file already exists: %v", genFile) - } - - appGenState := mbm.DefaultGenesis() - - // Change default staking params: denom, minSelfDelegation - minSelfDelegation, ok := sdk.NewIntFromString(DefMinSelfDelegation) - if !ok { - return fmt.Errorf("staking genState: default minSelfDelegation convertion failed: %s", DefMinSelfDelegation) - } - - stakingDataBz := appGenState[staking.ModuleName] - var stakingGenState staking.GenesisState - - cdc.MustUnmarshalJSON(stakingDataBz, &stakingGenState) - stakingGenState.Params.BondDenom = StakingDenom - stakingGenState.Params.LPDenom = LiquidityProviderDenom - stakingGenState.Params.MinSelfDelegationLvl = minSelfDelegation - appGenState[staking.ModuleName] = cdc.MustMarshalJSON(stakingGenState) - - // Change default mint params - mintDataBz := appGenState[mint.ModuleName] - var mintGenState mint.GenesisState - - cdc.MustUnmarshalJSON(mintDataBz, &mintGenState) - mintGenState.Params.MintDenom = StakingDenom - // - mintGenState.Params.InflationMax = sdk.NewDecWithPrec(50, 2) // 50% - mintGenState.Params.InflationMin = sdk.NewDecWithPrec(1776, 4) // 17.76% - // - mintGenState.Params.FeeBurningRatio = sdk.NewDecWithPrec(50, 2) // 50% - mintGenState.Params.InfPwrBondedLockedRatio = sdk.NewDecWithPrec(4, 1) // 40% - mintGenState.Params.FoundationAllocationRatio = sdk.NewDecWithPrec(45, 2) // 45% - // - mintGenState.Params.AvgBlockTimeWindow = 100 // 100 blocks - appGenState[mint.ModuleName] = cdc.MustMarshalJSON(mintGenState) - - // Change default distribution params - distDataBz := appGenState[distribution.ModuleName] - var distGenState distribution.GenesisState - - cdc.MustUnmarshalJSON(distDataBz, &distGenState) - distGenState.Params.ValidatorsPoolTax = sdk.NewDecWithPrec(4825, 4) // 48.25% - distGenState.Params.LiquidityProvidersPoolTax = sdk.NewDecWithPrec(4825, 4) // 48.25% - distGenState.Params.PublicTreasuryPoolTax = sdk.NewDecWithPrec(15, 3) // 1.5% - distGenState.Params.HARPTax = sdk.NewDecWithPrec(2, 2) // 2% - // - distGenState.Params.PublicTreasuryPoolCapacity = sdk.NewInt(250000) // 250K (doesn't include currency decimals) - // - distGenState.Params.BaseProposerReward = sdk.NewDecWithPrec(1, 2) // 1% - distGenState.Params.BonusProposerReward = sdk.NewDecWithPrec(4, 2) // 4% - // - distGenState.Params.WithdrawAddrEnabled = true - appGenState[distribution.ModuleName] = cdc.MustMarshalJSON(distGenState) - - // Change default minimal governance deposit coin - govDataBz := appGenState[gov.ModuleName] - var govGenState gov.GenesisState - - cdc.MustUnmarshalJSON(govDataBz, &govGenState) - govGenState.DepositParams.MinDeposit = sdk.NewCoins(GovMinDeposit) - appGenState[gov.ModuleName] = cdc.MustMarshalJSON(govGenState) - - // Change default crisis constant fee - crisisDataBz := appGenState[crisis.ModuleName] - var crisisGenState crisis.GenesisState - - cdc.MustUnmarshalJSON(crisisDataBz, &crisisGenState) - defFeeAmount, _ := sdk.NewIntFromString(DefaultFeeAmount) - crisisGenState.ConstantFee.Denom = MainDenom - crisisGenState.ConstantFee.Amount = defFeeAmount - appGenState[crisis.ModuleName] = cdc.MustMarshalJSON(crisisGenState) - - // Prepare genesis state - appState, err := codec.MarshalJSONIndent(cdc, appGenState) - if err != nil { - return errors.Wrap(err, "Failed to marshall default genesis state") - } - - genDoc := &tmTypes.GenesisDoc{} - if _, err := os.Stat(genFile); err != nil { - if !os.IsNotExist(err) { - return err - } - } else { - genDoc, err = tmTypes.GenesisDocFromFile(genFile) - if err != nil { - return errors.Wrap(err, "Failed to read genesis doc from file") - } - } - - genDoc.ChainID = chainID - genDoc.Validators = nil - genDoc.AppState = appState - - // Setup max gas - if genDoc.ConsensusParams == nil { - genDoc.ConsensusParams = tmTypes.DefaultConsensusParams() - } - - genDoc.ConsensusParams.Block.MaxGas = maxGas - - if err = genutil.ExportGenesisFile(genDoc, genFile); err != nil { - return errors.Wrap(err, "Failed to export gensis file") - } - - toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) - - cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) - return displayInfo(cdc, toPrint) - }, - } - - cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory") - cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") - cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") - - return cmd -} diff --git a/cmd/config/genesis/cmd_init.go b/cmd/config/genesis/cmd_init.go new file mode 100644 index 00000000..52f2bb98 --- /dev/null +++ b/cmd/config/genesis/cmd_init.go @@ -0,0 +1,140 @@ +package genesis + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/cli" + tmos "github.com/tendermint/tendermint/libs/os" + tmrand "github.com/tendermint/tendermint/libs/rand" + tmTypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + + "github.com/dfinance/dnode/cmd/config/genesis/defaults" +) + +const ( + flagOverwrite = "overwrite" +) + +type printInfo struct { + Moniker string `json:"moniker" yaml:"moniker"` + ChainID string `json:"chain_id" yaml:"chain_id"` + NodeID string `json:"node_id" yaml:"node_id"` + GenTxsDir string `json:"gentxs_dir" yaml:"gentxs_dir"` + AppMessage json.RawMessage `json:"app_message" yaml:"app_message"` +} + +func newPrintInfo(moniker, chainID, nodeID, genTxsDir string, appMessage json.RawMessage) printInfo { + return printInfo{ + Moniker: moniker, + ChainID: chainID, + NodeID: nodeID, + GenTxsDir: genTxsDir, + AppMessage: appMessage, + } +} + +func displayInfo(cdc *codec.Codec, info printInfo) error { + out, err := codec.MarshalJSONIndent(cdc, info) + if err != nil { + return err + } + + _, err = fmt.Fprintf(os.Stderr, "%s\n", string(sdk.MustSortJSON(out))) + return err +} + +// InitCmd returns a command that initializes all files needed for Tendermint +// and the respective application. +func InitCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, + defaultNodeHome string) *cobra.Command { // nolint: golint + cmd := &cobra.Command{ + Use: "init [moniker]", + Short: "Initialize private validator, p2p, genesis, and application configuration files", + Long: `Initialize validators's and node's configuration files.`, + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + + chainID := viper.GetString(flags.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", tmrand.Str(6)) + } + + nodeID, _, err := genutil.InitializeNodeValidatorFiles(config) + if err != nil { + return err + } + + config.Moniker = args[0] + + // Prepare genesis state + appGenState, err := OverrideGenesisStateDefaults(cdc, mbm.DefaultGenesis()) + if err != nil { + return fmt.Errorf("app genesis state overwrite: %v", err) + } + + appState, err := codec.MarshalJSONIndent(cdc, appGenState) + if err != nil { + return errors.Wrap(err, "failed to marshall app genesis state") + } + + // Prepare genesis file + genFile := config.GenesisFile() + if !viper.GetBool(flagOverwrite) && tmos.FileExists(genFile) { + return fmt.Errorf("genesis.json file already exists: %v", genFile) + } + + genDoc := &tmTypes.GenesisDoc{} + if _, err := os.Stat(genFile); err != nil { + if !os.IsNotExist(err) { + return err + } + } else { + genDoc, err = tmTypes.GenesisDocFromFile(genFile) + if err != nil { + return errors.Wrap(err, "failed to read genesis doc from file") + } + } + + genDoc.ChainID = chainID + genDoc.Validators = nil + genDoc.AppState = appState + + // Setup max gas + if genDoc.ConsensusParams == nil { + genDoc.ConsensusParams = tmTypes.DefaultConsensusParams() + } + genDoc.ConsensusParams.Block.MaxGas = defaults.MaxGas + + if err = genutil.ExportGenesisFile(genDoc, genFile); err != nil { + return errors.Wrap(err, "Failed to export gensis file") + } + + toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) + + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + return displayInfo(cdc, toPrint) + }, + } + + cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory") + cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") + cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + + return cmd +} diff --git a/cmd/config/genesis/defaults/common_defaults.go b/cmd/config/genesis/defaults/common_defaults.go new file mode 100644 index 00000000..1a86cf13 --- /dev/null +++ b/cmd/config/genesis/defaults/common_defaults.go @@ -0,0 +1,53 @@ +// Commonly used default values. +// Moved to a separate pkg to prevent circular dependency. +package defaults + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + MainDenom = "xfi" + StakingDenom = "sxfi" + LiquidityProviderDenom = "lpt" + + FeeAmount = "100000000000000" // 0.0001 + GovMinDepositAmount = "1000000000000000000000" // 1000.0 + MinSelfDelegationAmount = "2500000000000000000000" // 2500.0 + InvariantCheckAmount = "1000000000000000000000" // 1000.0 + + MaxGas = 10000000 +) + +var ( + FeeCoin sdk.Coin + GovMinDepositCoin sdk.Coin + MinSelfDelegationCoin sdk.Coin + InvariantCheckCoin sdk.Coin +) + +func init() { + if value, ok := sdk.NewIntFromString(FeeAmount); !ok { + panic("defaults: FeeAmount conversion failed") + } else { + FeeCoin = sdk.NewCoin(MainDenom, value) + } + + if value, ok := sdk.NewIntFromString(GovMinDepositAmount); !ok { + panic("governance defaults: GovMinDepositAmount conversion failed") + } else { + GovMinDepositCoin = sdk.NewCoin(StakingDenom, value) + } + + if value, ok := sdk.NewIntFromString(MinSelfDelegationAmount); !ok { + panic("staking defaults: MinSelfDelegationAmount conversion failed") + } else { + MinSelfDelegationCoin = sdk.NewCoin(StakingDenom, value) + } + + if value, ok := sdk.NewIntFromString(InvariantCheckAmount); !ok { + panic("crisis defaults: InvariantCheckAmount conversion failed") + } else { + InvariantCheckCoin = sdk.NewCoin(MainDenom, value) + } +} diff --git a/cmd/config/genesis/genesis.go b/cmd/config/genesis/genesis.go new file mode 100644 index 00000000..6d6e214d --- /dev/null +++ b/cmd/config/genesis/genesis.go @@ -0,0 +1,279 @@ +package genesis + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/evidence" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/dfinance/dnode/cmd/config/genesis/defaults" + "github.com/dfinance/dnode/x/multisig" + "github.com/dfinance/dnode/x/oracle" + "github.com/dfinance/dnode/x/poa" +) + +const ( + AvgYearDur = time.Duration(60*60*8766) * time.Second // 365.25 days + DayDur = 24 * time.Hour +) + +// OverrideGenesisStateDefaults takes default app genesis state and overwrites Cosmos SDK / Dfinance params. +func OverrideGenesisStateDefaults(cdc *codec.Codec, genState map[string]json.RawMessage) (map[string]json.RawMessage, error) { + // Mint module params + { + moduleName, moduleState := mint.ModuleName, mint.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.MintDenom = defaults.StakingDenom + // + moduleState.Params.InflationMax = sdk.NewDecWithPrec(50, 2) // 50% + moduleState.Params.InflationMin = sdk.NewDecWithPrec(1776, 4) // 17.76% + // + moduleState.Params.FeeBurningRatio = sdk.NewDecWithPrec(50, 2) // 50% + moduleState.Params.InfPwrBondedLockedRatio = sdk.NewDecWithPrec(4, 1) // 40% + moduleState.Params.FoundationAllocationRatio = sdk.NewDecWithPrec(15, 2) // 15% + // + moduleState.Params.AvgBlockTimeWindow = 100 // 100 blocks + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Distribution module params + { + moduleName, moduleState := distribution.ModuleName, distribution.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.ValidatorsPoolTax = sdk.NewDecWithPrec(4825, 4) // 48.25% + moduleState.Params.LiquidityProvidersPoolTax = sdk.NewDecWithPrec(4825, 4) // 48.25% + moduleState.Params.PublicTreasuryPoolTax = sdk.NewDecWithPrec(15, 3) // 1.5% + moduleState.Params.HARPTax = sdk.NewDecWithPrec(2, 2) // 2% + // + moduleState.Params.PublicTreasuryPoolCapacity = sdk.NewInt(250000) // 250000.0 + // + moduleState.Params.BaseProposerReward = sdk.NewDecWithPrec(1, 2) // 1% + moduleState.Params.BonusProposerReward = sdk.NewDecWithPrec(4, 2) // 4% + // + moduleState.Params.LockedRatio = sdk.NewDecWithPrec(5, 1) // 50% + moduleState.Params.LockedDuration = AvgYearDur // 1 year + // + moduleState.Params.WithdrawAddrEnabled = true + moduleState.Params.FoundationNominees = []sdk.AccAddress{} + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Staking module params + { + moduleName, moduleState := staking.ModuleName, staking.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.UnbondingTime = 7 * DayDur + moduleState.Params.ScheduledUnbondDelayTime = 3 * DayDur + // + moduleState.Params.MaxValidators = 31 + moduleState.Params.MaxEntries = 7 + moduleState.Params.HistoricalEntries = 0 + // + moduleState.Params.BondDenom = defaults.StakingDenom + moduleState.Params.LPDenom = defaults.LiquidityProviderDenom + // + moduleState.Params.MinSelfDelegationLvl = defaults.MinSelfDelegationCoin.Amount // 2500.0 + moduleState.Params.MaxDelegationsRatio = sdk.NewDecWithPrec(10, 0) // 10.0 + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Slashing module params + { + moduleName, moduleState := slashing.ModuleName, slashing.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.SignedBlocksWindow = 100 + moduleState.Params.MinSignedPerWindow = sdk.NewDecWithPrec(5, 1) + // + moduleState.Params.DowntimeJailDuration = 10 * time.Minute + // + moduleState.Params.SlashFractionDoubleSign = sdk.NewDec(1).Quo(sdk.NewDec(20)) // 1.0 / 20.0 + moduleState.Params.SlashFractionDowntime = sdk.NewDec(1).Quo(sdk.NewDec(100)) // 1.0 / 100.0 + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Evidence module params + { + moduleName, moduleState := evidence.ModuleName, evidence.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.MaxEvidenceAge = 2 * time.Minute + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Crisis module params + { + moduleName, moduleState := crisis.ModuleName, crisis.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.ConstantFee.Denom = defaults.InvariantCheckCoin.Denom // xfi + moduleState.ConstantFee.Amount = defaults.InvariantCheckCoin.Amount // 1000.0 + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Gov module params + { + moduleName, moduleState := gov.ModuleName, gov.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.VotingParams.VotingPeriod = 6 * time.Hour + // + moduleState.TallyParams.Quorum = sdk.NewDecWithPrec(334, 3) // 33.4% + moduleState.TallyParams.Threshold = sdk.NewDecWithPrec(5, 1) // 50% + moduleState.TallyParams.Veto = sdk.NewDecWithPrec(334, 3) // 33.4% + // + moduleState.DepositParams.MinDeposit = sdk.NewCoins(defaults.GovMinDepositCoin) // 1000.0sxfi + moduleState.DepositParams.MaxDepositPeriod = 3 * time.Hour + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Auth module params + { + moduleName, moduleState := auth.ModuleName, auth.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.MaxMemoCharacters = 256 + moduleState.Params.TxSigLimit = 7 + moduleState.Params.TxSizeCostPerByte = 10 + moduleState.Params.SigVerifyCostED25519 = 590 + moduleState.Params.SigVerifyCostSecp256k1 = 1000 + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Bank module genesis + { + moduleName, moduleState := bank.ModuleName, bank.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.SendEnabled = true + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // PoA module params + { + moduleName, moduleState := poa.ModuleName, poa.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Parameters.MaxValidators = 11 + moduleState.Parameters.MinValidators = 1 + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // MultiSig module params + { + moduleName, moduleState := multisig.ModuleName, multisig.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Parameters.IntervalToExecute = 86400 // 6 days approx. + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + // Oracle module params + { + moduleName, moduleState := oracle.ModuleName, oracle.GenesisState{} + if err := cdc.UnmarshalJSON(genState[moduleName], &moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON unmarshal: %v", moduleName, err) + } + + moduleState.Params.PostPrice.ReceivedAtDiffInS = 360 // 1 hour + + if moduleStateBz, err := cdc.MarshalJSON(moduleState); err != nil { + return nil, fmt.Errorf("%s module: JSON marshal: %v", moduleName, err) + } else { + genState[moduleName] = moduleStateBz + } + } + + return genState, nil +} diff --git a/cmd/config/restrictions/restrictions.go b/cmd/config/restrictions/restrictions.go index 76e10e06..b8678efa 100644 --- a/cmd/config/restrictions/restrictions.go +++ b/cmd/config/restrictions/restrictions.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/x/currencies" ) @@ -58,19 +58,20 @@ func GetAppRestrictions() AppRestrictions { params.RestrictedParam{Subspace: distribution.ModuleName, Key: string(distribution.ParamKeyHARPTax)}, params.RestrictedParam{Subspace: distribution.ModuleName, Key: string(distribution.ParamKeyFoundationNominees)}, params.RestrictedParam{Subspace: mint.ModuleName, Key: string(mint.KeyFoundationAllocationRatio)}, + params.RestrictedParam{Subspace: mint.ModuleName, Key: string(mint.KeyStakingTotalSupplyShift)}, }, CustomMsgVerifiers: func(msg sdk.Msg) error { switch msg := msg.(type) { case bank.MsgSend: for i := range msg.Amount { - if msg.Amount.GetDenomByIndex(i) == config.StakingDenom || msg.Amount.GetDenomByIndex(i) == config.LiquidityProviderDenom { + if msg.Amount.GetDenomByIndex(i) == defaults.StakingDenom || msg.Amount.GetDenomByIndex(i) == defaults.LiquidityProviderDenom { return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "bank transactions are disallowed for %s token", msg.Amount.GetDenomByIndex(i)) } } case gov.MsgDeposit: for i := range msg.Amount { - if msg.Amount.GetDenomByIndex(i) != config.StakingDenom { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "gov deposit only allowed for %s token", config.StakingDenom) + if msg.Amount.GetDenomByIndex(i) != defaults.StakingDenom { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "gov deposit only allowed for %s token", defaults.StakingDenom) } } } diff --git a/cmd/config/vm_config.go b/cmd/config/vm_config.go new file mode 100644 index 00000000..246d6942 --- /dev/null +++ b/cmd/config/vm_config.go @@ -0,0 +1,85 @@ +package config + +import ( + "bytes" + "os" + "path/filepath" + + "github.com/spf13/viper" + tmOs "github.com/tendermint/tendermint/libs/os" +) + +const ( + VMConfigFile = "vm.toml" // Default file to store config. + ConfigDir = "config" // Default directory to store all configurations. + + // VM configs. + DefaultVMAddress = "tcp://127.0.0.1:50051" // Default virtual machine address to connect from Cosmos SDK. + DefaultDataListen = "tcp://127.0.0.1:50052" // Default data server address to listen for connections from VM. + DefaultCompilerAddr = DefaultVMAddress + + // Default retry configs. + DefaultMaxAttempts = 0 // Default maximum attempts for retry. + DefaultReqTimeout = 0 // Default request timeout per attempt [ms]. + + // Invariants check period for crisis module (in blocks) + DefInvCheckPeriod = 10 +) + +// Virtual machine connection config (see config/vm.toml). +type VMConfig struct { + Address string `mapstructure:"vm_address"` // address of virtual machine. + DataListen string `mapstructure:"vm_data_listen"` // data listen. + + // Retry policy + MaxAttempts uint `mapstructure:"vm_retry_max_attempts"` // maximum attempts for retry (0 - infinity) + ReqTimeoutInMs uint `mapstructure:"vm_retry_req_timeout_ms"` // request timeout per attempt (0 - infinity) [ms] +} + +// Default VM configuration. +func DefaultVMConfig() *VMConfig { + return &VMConfig{ + Address: DefaultVMAddress, + DataListen: DefaultDataListen, + MaxAttempts: DefaultMaxAttempts, + ReqTimeoutInMs: DefaultReqTimeout, + } +} + +// Write VM config file in configuration directory. +func WriteVMConfig(rootDir string, vmConfig *VMConfig) { + configFilePath := filepath.Join(rootDir, ConfigDir, VMConfigFile) + + var buffer bytes.Buffer + + if err := configTemplate.Execute(&buffer, vmConfig); err != nil { + panic(err) + } + + tmOs.MustWriteFile(configFilePath, buffer.Bytes(), 0644) +} + +// Read VM config file from configuration directory. +func ReadVMConfig(rootDir string) (*VMConfig, error) { + configFilePath := filepath.Join(rootDir, ConfigDir, VMConfigFile) + + if _, err := os.Stat(configFilePath); os.IsNotExist(err) { + config := DefaultVMConfig() + WriteVMConfig(rootDir, config) + return config, nil + } + + viper.SetConfigFile(configFilePath) + + if err := viper.ReadInConfig(); err != nil { + panic(err) + } + + // read config + config := DefaultVMConfig() + if err := viper.Unmarshal(config); err != nil { + panic(err) + } + + return config, nil +} diff --git a/cmd/dncli/main.go b/cmd/dncli/main.go index 417393b1..3bf751f6 100644 --- a/cmd/dncli/main.go +++ b/cmd/dncli/main.go @@ -23,6 +23,7 @@ import ( "github.com/dfinance/dnode/app" dnConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/cmd/config/restrictions" "github.com/dfinance/dnode/helpers/logger" "github.com/dfinance/dnode/helpers/swagger" @@ -121,11 +122,11 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { // Set default gas for tx commands. func SetDefaultFeeForTxCmd(cmd *cobra.Command) { if feesFlag := cmd.Flag(flags.FlagFees); feesFlag != nil { - feesFlag.DefValue = dnConfig.DefaultFee - feesFlag.Usage = "Fees to pay along with transaction; eg: " + dnConfig.DefaultFee + feesFlag.DefValue = defaults.FeeCoin.String() + feesFlag.Usage = "Fees to pay along with transaction; eg: " + defaults.FeeCoin.String() if feesFlag.Value.String() == "" { - err := feesFlag.Value.Set(dnConfig.DefaultFee) + err := feesFlag.Value.Set(defaults.FeeCoin.String()) if err != nil { panic(err) } @@ -137,8 +138,8 @@ func SetDefaultFeeForTxCmd(cmd *cobra.Command) { return fmt.Errorf("can't parse fees value to coins: %v", err) } - if coin.Denom != dnConfig.MainDenom { - return fmt.Errorf("fees must be paid only in %q, current fees in are %q", dnConfig.MainDenom, coin.Denom) + if coin.Denom != defaults.MainDenom { + return fmt.Errorf("fees must be paid only in %q, current fees in are %q", defaults.MainDenom, coin.Denom) } return nil diff --git a/cmd/dnode/main.go b/cmd/dnode/main.go index caffeb1b..876e288e 100644 --- a/cmd/dnode/main.go +++ b/cmd/dnode/main.go @@ -22,6 +22,7 @@ import ( "github.com/dfinance/dnode/app" dnConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis" "github.com/dfinance/dnode/cmd/config/restrictions" "github.com/dfinance/dnode/helpers/logger" ccsCli "github.com/dfinance/dnode/x/ccstorage/client/cli" @@ -76,7 +77,6 @@ func main() { oracleCli.AddAssetGenCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome), marketsCli.AddMarketGenCmd(ctx, cdc, app.DefaultNodeHome), migrationCli.MigrateGenesisCmd(ctx, cdc), - testnetCmd(ctx, cdc, app.ModuleBasics, genaccounts.AppModuleBasic{}), ) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) @@ -132,7 +132,7 @@ func exportAppStateAndTMValidators( // Init cmd together with VM configruation. // nolint func InitCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, defaultNodeHome string) *cobra.Command { // nolint: golint - cmd := dnConfig.InitCmd(ctx, cdc, mbm, defaultNodeHome) + cmd := genesis.InitCmd(ctx, cdc, mbm, defaultNodeHome) cmd.PersistentPostRun = func(cmd *cobra.Command, args []string) { dnConfig.ReadVMConfig(viper.GetString(cli.HomeFlag)) diff --git a/cmd/dnode/testnet.go b/cmd/dnode/testnet.go deleted file mode 100644 index 0b659e6a..00000000 --- a/cmd/dnode/testnet.go +++ /dev/null @@ -1,410 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "os" - "path" - - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server" - sdkSrvConfig "github.com/cosmos/cosmos-sdk/server/config" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/genutil" - genutilTypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/spf13/cobra" - "github.com/spf13/viper" - tmConfig "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto" - tmOs "github.com/tendermint/tendermint/libs/os" - tmRand "github.com/tendermint/tendermint/libs/rand" - tmTypes "github.com/tendermint/tendermint/types" - tmTime "github.com/tendermint/tendermint/types/time" - - dnConfig "github.com/dfinance/dnode/cmd/config" - "github.com/dfinance/dnode/x/genaccounts" - "github.com/dfinance/dnode/x/oracle" -) - -// DONTCOVER - -var ( - flagNodeDirPrefix = "node-dir-prefix" - flagNumValidators = "v" - flagOutputDir = "output-dir" - flagNodeDaemonHome = "node-daemon-home" - flagNodeCLIHome = "node-cli-home" - flagStartingIPAddress = "starting-ip-address" - flagComissionRate = "comission-rate" - flagComissionMaxRate = "comission-max-rate" - flagComissionMaxChangeRate = "comission-max-change-rate" -) - -type cliFlags struct { - outputDir string - chainID string - minGasPrices string - nodeDirPrefix string - nodeDaemonHome string - nodeCLIHome string - startingIPAddress string - numValidators int - comissionRate sdk.Dec - comissionMaxRate sdk.Dec - comissionMaxChangeRate sdk.Dec -} - -// get cmd to initialize all files for tendermint testnet and application -func testnetCmd(ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, genAccIterator genutilTypes.GenesisAccountsIterator) *cobra.Command { - cmd := &cobra.Command{ - Use: "testnet", - Short: "Initialize files for a Dnode testnet", - Long: `testnet will create "v" number of directories and populate each with -necessary files (private validator, genesis, config, etc.). - -Note, strict routability for addresses is turned off in the config file. - -Example: - dnode testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 - `, - RunE: func(cmd *cobra.Command, _ []string) error { - config := ctx.Config - - cf := cliFlags{ - outputDir: viper.GetString(flagOutputDir), - chainID: viper.GetString(flags.FlagChainID), - minGasPrices: viper.GetString(server.FlagMinGasPrices), - nodeDirPrefix: viper.GetString(flagNodeDirPrefix), - nodeDaemonHome: viper.GetString(flagNodeDaemonHome), - nodeCLIHome: viper.GetString(flagNodeCLIHome), - startingIPAddress: viper.GetString(flagStartingIPAddress), - numValidators: viper.GetInt(flagNumValidators), - } - var err error - cf.comissionRate, err = sdk.NewDecFromStr(viper.GetString(flagComissionRate)) - if err != nil { - return err - } - cf.comissionMaxRate, err = sdk.NewDecFromStr(viper.GetString(flagComissionMaxRate)) - if err != nil { - return err - } - cf.comissionMaxChangeRate, err = sdk.NewDecFromStr(viper.GetString(flagComissionMaxChangeRate)) - if err != nil { - return err - } - - return InitTestnet(cmd, config, cdc, mbm, genAccIterator, &cf) - }, - } - - cmd.Flags().Int(flagNumValidators, 4, - "Number of validators to initialize the testnet with") - cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") - cmd.Flags().String(flagNodeDirPrefix, "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") - cmd.Flags().String(flagNodeDaemonHome, "dnode", - "Home directory of the node's daemon configuration") - cmd.Flags().String(flagNodeCLIHome, "dncli", - "Home directory of the node's cli configuration") - cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", - "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") - cmd.Flags().String(flags.FlagChainID, "", - "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("1%s", dnConfig.MainDenom), - "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)") - cmd.Flags().String(flagComissionRate, "0.100000000000000000", - "Comission rate") - cmd.Flags().String(flagComissionMaxRate, "0.200000000000000000", - "Comission rate") - cmd.Flags().String(flagComissionMaxChangeRate, "0.010000000000000000", - "Comission max change rate") - //cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, - // "Select keyring's backend (os|file|test)") - - return cmd -} - -const nodeDirPerm = 0755 - -// Initialize the testnet -func InitTestnet(cmd *cobra.Command, config *tmConfig.Config, cdc *codec.Codec, mbm module.BasicManager, genAccIterator genutilTypes.GenesisAccountsIterator, cf *cliFlags) error { - if cf.chainID == "" { - cf.chainID = "chain-" + tmRand.Str(6) - } - - monikers := make([]string, cf.numValidators) - nodeIDs := make([]string, cf.numValidators) - valPubKeys := make([]crypto.PubKey, cf.numValidators) - - dnCfg := sdkSrvConfig.DefaultConfig() - dnCfg.MinGasPrices = cf.minGasPrices - - // nolint:prealloc - var ( - genAccounts genaccounts.GenesisAccounts - genFiles []string - ) - - // generate private keys, node IDs, and initial transactions - for i := 0; i < cf.numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", cf.nodeDirPrefix, i) - nodeDir := path.Join(cf.outputDir, nodeDirName, cf.nodeDaemonHome) - clientDir := path.Join(cf.outputDir, nodeDirName, cf.nodeCLIHome) - gentxsDir := path.Join(cf.outputDir, "gentxs") - - config.SetRoot(nodeDir) - config.RPC.ListenAddress = "tcp://0.0.0.0:26657" - - if err := os.MkdirAll(path.Join(nodeDir, "config"), nodeDirPerm); err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - if err := os.MkdirAll(clientDir, nodeDirPerm); err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - monikers = append(monikers, nodeDirName) - config.Moniker = nodeDirName - - ip, err := getIP(i, cf.startingIPAddress) - if err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config) - if err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) - genFiles = append(genFiles, config.GenesisFile()) - - kb, err := keys.NewKeyBaseFromDir(clientDir) - if err != nil { - return err - } - - keyPass := keys.DefaultKeyPass - addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, keyPass, true) - if err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - info := map[string]string{"secret": secret} - - cliPrint, err := json.Marshal(info) - if err != nil { - return err - } - - // save private key seed words - if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint); err != nil { - return err - } - - accStakingTokens := sdk.TokensFromConsensusPower(500000000000) - coins := sdk.Coins{ - sdk.NewCoin(dnConfig.MainDenom, accStakingTokens), - } - - genAccounts = append(genAccounts, genaccounts.NewGenesisAccountRaw(addr, coins.Sort(), "")) - - valTokens := sdk.TokensFromConsensusPower(500000) - msg := staking.NewMsgCreateValidator( - sdk.ValAddress(addr), - valPubKeys[i], - sdk.NewCoin(dnConfig.MainDenom, valTokens), - staking.NewDescription(nodeDirName, "", "", "", ""), - staking.NewCommissionRates(cf.comissionRate, cf.comissionMaxRate, cf.comissionMaxChangeRate), - sdk.OneInt(), - ) - - inBuf := bufio.NewReader(cmd.InOrStdin()) - txBldr := auth.NewTxBuilderFromCLI(inBuf).WithChainID(cf.chainID).WithMemo(memo).WithKeybase(kb) - tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{Gas: 200000}, []auth.StdSignature{}, memo) - - signedTx, err := txBldr.SignStdTx(nodeDirName, keys.DefaultKeyPass, tx, false) - if err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - txBytes, err := cdc.MarshalJSON(signedTx) - if err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - // gather gentxs folder - if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes); err != nil { - _ = os.RemoveAll(cf.outputDir) - return err - } - - // TODO: Rename config file to server.toml as it's not particular to Dn - // (REF: https://github.com/cosmos/cosmos-sdk/issues/4125). - dnConfigpath := path.Join(nodeDir, "config/app.toml") - sdkSrvConfig.WriteConfigFile(dnConfigpath, dnCfg) - } - - if err := initGenFiles(cdc, mbm, cf.chainID, genAccounts, genFiles, cf.numValidators); err != nil { - return err - } - - err := collectGenFiles( - cdc, config, cf.chainID, monikers, nodeIDs, valPubKeys, cf.numValidators, - cf.outputDir, cf.nodeDirPrefix, cf.nodeDaemonHome, genAccIterator, - ) - if err != nil { - return err - } - - cmd.PrintErrf("Successfully initialized %d node directories\n", cf.numValidators) - return nil -} - -func initGenFiles( - cdc *codec.Codec, mbm module.BasicManager, chainID string, - genAccounts genaccounts.GenesisAccounts, genFiles []string, numValidators int, -) error { - - appGenState := mbm.DefaultGenesis() - - // set the accounts in the genesis state - appGenState[genaccounts.ModuleName] = cdc.MustMarshalJSON(genAccounts) - - stakingDataBz := appGenState[staking.ModuleName] - var stakingGenState staking.GenesisState - cdc.MustUnmarshalJSON(stakingDataBz, &stakingGenState) - stakingGenState.Params.BondDenom = dnConfig.MainDenom - appGenState[staking.ModuleName] = cdc.MustMarshalJSON(stakingGenState) - - oracleDataBz := appGenState[oracle.ModuleName] - var oracleGenState oracle.GenesisState - cdc.MustUnmarshalJSON(oracleDataBz, &oracleGenState) - nominees := make([]string, len(genAccounts)) - for i, ga := range genAccounts { - nominees[i] = ga.BaseAccount.Address.String() - } - oracleGenState.Params.Nominees = nominees - appGenState[oracle.ModuleName] = cdc.MustMarshalJSON(oracleGenState) - - appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) - if err != nil { - return err - } - - genDoc := tmTypes.GenesisDoc{ - ChainID: chainID, - AppState: appGenStateJSON, - Validators: nil, - } - - // generate empty genesis files for each validator and save - for i := 0; i < numValidators; i++ { - if err := genDoc.SaveAs(genFiles[i]); err != nil { - return err - } - } - return nil -} - -func collectGenFiles( - cdc *codec.Codec, config *tmConfig.Config, chainID string, - monikers, nodeIDs []string, valPubKeys []crypto.PubKey, - numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string, - genAccIterator genutilTypes.GenesisAccountsIterator) error { - - var appState json.RawMessage - genTime := tmTime.Now() - - for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) - nodeDir := path.Join(outputDir, nodeDirName, nodeDaemonHome) - gentxsDir := path.Join(outputDir, "gentxs") - moniker := monikers[i] - config.Moniker = nodeDirName - - config.SetRoot(nodeDir) - - nodeID, valPubKey := nodeIDs[i], valPubKeys[i] - initCfg := genutil.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey) - - genDoc, err := tmTypes.GenesisDocFromFile(config.GenesisFile()) - if err != nil { - return err - } - - nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genAccIterator) - if err != nil { - return err - } - - if appState == nil { - // set the canonical application state (they should not differ) - appState = nodeAppState - } - - genFile := config.GenesisFile() - - // overwrite each validator's genesis file to have a canonical genesis time - if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil { - return err - } - } - - return nil -} - -func getIP(i int, startingIPAddr string) (ip string, err error) { - if len(startingIPAddr) == 0 { - ip, err = server.ExternalIP() - if err != nil { - return "", err - } - return ip, nil - } - return calculateIP(startingIPAddr, i) -} - -func calculateIP(ip string, i int) (string, error) { - ipv4 := net.ParseIP(ip).To4() - if ipv4 == nil { - return "", fmt.Errorf("%v: non ipv4 address", ip) - } - - for j := 0; j < i; j++ { - ipv4[3]++ - } - - return ipv4.String(), nil -} - -func writeFile(name string, dir string, contents []byte) error { - writePath := path.Join(dir) - file := path.Join(writePath, name) - - if err := tmOs.EnsureDir(writePath, 0700); err != nil { - return err - } - - if err := tmOs.WriteFile(file, contents, 0600); err != nil { - return err - } - - return nil -} diff --git a/go.mod b/go.mod index 430c9606..55f57d85 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,11 @@ module github.com/dfinance/dnode go 1.14 -replace github.com/cosmos/cosmos-sdk => github.com/dfinance/cosmos-sdk v0.38.4-0.20201002151830-6c83c28b89f4 +replace github.com/cosmos/cosmos-sdk => github.com/dfinance/cosmos-sdk v0.38.4-0.20201006151646-1ac22b3a4a09 // Local development option //replace github.com/cosmos/cosmos-sdk => /Users/boris/go/src/github.com/dfinance/cosmos-sdk +//replace github.com/cosmos/cosmos-sdk => /Users/tiky/Go_Projects/src/github.com/dfinance/cosmos-sdk require ( github.com/99designs/keyring v1.1.3 diff --git a/go.sum b/go.sum index 39101f49..46e9fad1 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/dfinance/cosmos-sdk v0.38.4-0.20201002002655-db666862c14b h1:0z853VXV github.com/dfinance/cosmos-sdk v0.38.4-0.20201002002655-db666862c14b/go.mod h1:6A7WcR5vEsVN5MSi5gkMJmJIMulHZftD7q63tBdt2r0= github.com/dfinance/cosmos-sdk v0.38.4-0.20201002151830-6c83c28b89f4 h1:sUQDCT0ddxJH14HssQkmA+TbX4PqEhHXK/py/RmaucU= github.com/dfinance/cosmos-sdk v0.38.4-0.20201002151830-6c83c28b89f4/go.mod h1:6A7WcR5vEsVN5MSi5gkMJmJIMulHZftD7q63tBdt2r0= +github.com/dfinance/cosmos-sdk v0.38.4-0.20201006151646-1ac22b3a4a09 h1:hNzQYh6QR6K097fL0HYDtgfyN2KgUXLu17/zRxSJezc= +github.com/dfinance/cosmos-sdk v0.38.4-0.20201006151646-1ac22b3a4a09/go.mod h1:6A7WcR5vEsVN5MSi5gkMJmJIMulHZftD7q63tBdt2r0= github.com/dfinance/dvm-proto/go v0.0.0-20200819065410-6b70956c85de h1:PZfrjeOs9epAU0n+FpX/JAr/e+5m5/GdfUsgtO8gCOY= github.com/dfinance/dvm-proto/go v0.0.0-20200819065410-6b70956c85de/go.mod h1:Vt1T0G56AYXbsduNKzSkq1RDTNa8PFraSqB9DaTCV0U= github.com/dfinance/glav v0.0.0-20200814081332-c4701f6c12a6 h1:fZIYncA5BRad0+YnP88cfBfo0ZPgxPSVeuh/jvoGrLc= diff --git a/helpers/tests/clitester/cli_tester_configs.go b/helpers/tests/clitester/cli_tester_configs.go index 4d8ce5d3..20f9f2a2 100644 --- a/helpers/tests/clitester/cli_tester_configs.go +++ b/helpers/tests/clitester/cli_tester_configs.go @@ -14,7 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/dfinance/glav" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/x/ccstorage" "github.com/dfinance/dnode/x/orders" ) @@ -131,91 +131,91 @@ func NewAccountMap() (accounts map[string]*CLIAccount, retErr error) { accounts["pos"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - DenomSXFI: sdk.NewCoin(DenomSXFI, bigAmount), - config.MainDenom: sdk.NewCoin(config.MainDenom, bigAmount), + DenomSXFI: sdk.NewCoin(DenomSXFI, bigAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, bigAmount), }, } accounts["bank"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, bigAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, bigAmount), }, } accounts["validator1"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), - DenomSXFI: sdk.NewCoin(DenomSXFI, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), + DenomSXFI: sdk.NewCoin(DenomSXFI, smallAmount), }, IsPOAValidator: true, } accounts["validator2"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsPOAValidator: true, } accounts["validator3"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsPOAValidator: true, } accounts["validator4"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsPOAValidator: true, } accounts["validator5"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsPOAValidator: true, } accounts["nominee"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsOracleNominee: true, } accounts["oracle1"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsOracle: true, } accounts["oracle2"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsOracle: false, } accounts["oracle3"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsOracle: false, } accounts["plain"] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, } accounts[orders.ModuleName] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsModuleAcc: true, } accounts[gov.ModuleName] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), }, IsModuleAcc: true, } accounts[DenomSXFI] = &CLIAccount{ Coins: map[string]sdk.Coin{ - config.MainDenom: sdk.NewCoin(config.MainDenom, smallAmount), - DenomSXFI: sdk.NewCoin(DenomSXFI, smallAmount), + defaults.MainDenom: sdk.NewCoin(defaults.MainDenom, smallAmount), + DenomSXFI: sdk.NewCoin(DenomSXFI, smallAmount), }, } diff --git a/helpers/tests/clitester/cli_tester_init.go b/helpers/tests/clitester/cli_tester_init.go index a99ed654..52f454b3 100644 --- a/helpers/tests/clitester/cli_tester_init.go +++ b/helpers/tests/clitester/cli_tester_init.go @@ -6,13 +6,12 @@ import ( "strings" sdkKeys "github.com/cosmos/cosmos-sdk/crypto/keys" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/spf13/viper" "github.com/stretchr/testify/require" - "github.com/dfinance/dnode/cmd/config" dnConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" ) func (ct *CLITester) initChain() { @@ -161,18 +160,15 @@ func (ct *CLITester) initChain() { // validator genTX { - minSelfDelegation, ok := sdk.NewIntFromString(dnConfig.DefMinSelfDelegation) - require.True(ct.t, ok, "DefMinSelfDelegation conversion failed") - - stakingCoin := ct.Accounts["pos"].Coins[config.StakingDenom] - stakingCoin.Amount = minSelfDelegation + stakingCoin := ct.Accounts["pos"].Coins[defaults.StakingDenom] + stakingCoin.Amount = defaults.MinSelfDelegationCoin.Amount cmd := ct.newWbdCmd(). AddArg("", "gentx"). AddArg("home-client", ct.Dirs.DncliDir). AddArg("name", "pos"). AddArg("amount", stakingCoin.String()). - AddArg("min-self-delegation", minSelfDelegation.String()). + AddArg("min-self-delegation", defaults.MinSelfDelegationCoin.Amount.String()). AddArg("keyring-backend", string(ct.keyringBackend)) cmd.CheckSuccessfulExecute(nil, ct.AccountPassphrase, ct.AccountPassphrase, ct.AccountPassphrase) diff --git a/helpers/tests/clitester/cli_tester_rest.go b/helpers/tests/clitester/cli_tester_rest.go index fb1929f1..d12886e5 100644 --- a/helpers/tests/clitester/cli_tester_rest.go +++ b/helpers/tests/clitester/cli_tester_rest.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" tmCoreTypes "github.com/tendermint/tendermint/rpc/core/types" - dnConfig "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" dnTypes "github.com/dfinance/dnode/helpers/types" "github.com/dfinance/dnode/x/ccstorage" "github.com/dfinance/dnode/x/currencies" @@ -34,17 +34,13 @@ import ( // buildBaseReq returns BaseReq used to prepare REST Tx send. func (ct *CLITester) buildBaseReq(accName, memo string) restTypes.BaseReq { - feeAmount, ok := sdk.NewIntFromString(dnConfig.DefaultFeeAmount) - require.True(ct.t, ok, "fee coin amount parsing") - feeCoin := sdk.NewCoin(dnConfig.MainDenom, feeAmount) - accInfo, ok := ct.Accounts[accName] require.True(ct.t, ok, "account %q: not found", accName) return restTypes.BaseReq{ ChainID: ct.IDs.ChainID, From: accInfo.Address, - Fees: sdk.Coins{feeCoin}, + Fees: sdk.Coins{defaults.FeeCoin}, Gas: strconv.Itoa(DefaultGas), Memo: memo, } @@ -57,7 +53,7 @@ func (ct *CLITester) newRestTxRequest(accName string, acc *auth.BaseAccount, msg func (ct *CLITester) newRestTxRequestRaw(accName string, accNumber, accSequence uint64, msg sdk.Msg, isSync bool) (r *RestRequest, txResp *sdk.TxResponse) { // build broadcast Tx request txFee := auth.StdFee{ - Amount: sdk.Coins{{Denom: dnConfig.MainDenom, Amount: sdk.NewInt(1)}}, + Amount: sdk.Coins{{Denom: defaults.MainDenom, Amount: sdk.NewInt(1)}}, Gas: DefaultGas, } txMemo := "restTxMemo" diff --git a/helpers/tests/clitester/types_tx.go b/helpers/tests/clitester/types_tx.go index bcff4a82..f31da768 100644 --- a/helpers/tests/clitester/types_tx.go +++ b/helpers/tests/clitester/types_tx.go @@ -11,7 +11,7 @@ import ( sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" ) type TxRequest struct { @@ -36,7 +36,7 @@ func (r *TxRequest) SetCmd(module, fromAddress string, args ...string) { } r.cmd.AddArg("broadcast-mode", "block") r.cmd.AddArg("node", r.nodeRpcAddress) - r.cmd.AddArg("fees", config.DefaultFee) + r.cmd.AddArg("fees", defaults.FeeCoin.String()) r.cmd.AddArg("gas", strconv.FormatUint(r.gas, 10)) r.cmd.AddArg("", "--yes") } diff --git a/helpers/tests/simulator/inflation_test.go b/helpers/tests/simulator/inflation_test.go index ccd36f13..35da2711 100644 --- a/helpers/tests/simulator/inflation_test.go +++ b/helpers/tests/simulator/inflation_test.go @@ -7,46 +7,130 @@ import ( "io/ioutil" "net/http" _ "net/http/pprof" + "os" "path" + "strings" "testing" "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" ) -func TestSimInflation(t *testing.T) { +type SimProfile struct { + ID string + SimDuration time.Duration + // + BlockTimeMin time.Duration + BlockTimeMax time.Duration + // + MainTokensBalanceWODec int64 + BondingTokensBalanceWODec int64 + LPTokensBalanceWODec int64 + // + Accounts uint + POAValidators uint + TMValidatorsTotal uint + TMValidatorsActive uint + // + OpCreateValidator time.Duration + // + OpDelegateBonding time.Duration + OpDelegateBondingAmountRatio sdk.Dec + OpDelegateBondingMaxSupplyRatio sdk.Dec + // + OpDelegateLP time.Duration + OpDelegateLPAmountRatio sdk.Dec + OpDelegateLPMaxSupplyRatio sdk.Dec + // + OpRedelegateBonding time.Duration + OpRedelegateBondingAmountRatio sdk.Dec + // + OpRedelegateLP time.Duration + OpRedelegateLPAmountRatio sdk.Dec + // + OpUndelegateBonding time.Duration + OpUndelegateBondingAmountRatio sdk.Dec + // + OpUndelegateLP time.Duration + OpUndelegateLPAmountRatio sdk.Dec + // + OpGetValidatorRewards time.Duration + OpGetDelegatorRewards time.Duration + // + OpLockRewards time.Duration + OpLockRewardsRatio sdk.Dec +} + +func (p SimProfile) String() string { + str := strings.Builder{} + str.WriteString("Simulation:\n") + str.WriteString(fmt.Sprintf(" - ID: %s\n", p.ID)) + str.WriteString(fmt.Sprintf(" - SimDuration: %s\n", FormatDuration(p.SimDuration))) + str.WriteString(fmt.Sprintf(" - BlockTimeMin: %s\n", FormatDuration(p.BlockTimeMin))) + str.WriteString(fmt.Sprintf(" - BlockTimeMax: %s\n", FormatDuration(p.BlockTimeMax))) + str.WriteString("Initial balances:\n") + str.WriteString(fmt.Sprintf(" - MainTokens: %d.0%s\n", p.MainTokensBalanceWODec, defaults.MainDenom)) + str.WriteString(fmt.Sprintf(" - StakingTokens: %d.0%s\n", p.BondingTokensBalanceWODec, defaults.StakingDenom)) + str.WriteString(fmt.Sprintf(" - LPTokens: %d.0%s\n", p.LPTokensBalanceWODec, defaults.LiquidityProviderDenom)) + str.WriteString("Total number of:\n") + str.WriteString(fmt.Sprintf(" - Account: %d\n", p.Accounts)) + str.WriteString(fmt.Sprintf(" - PoA validators: %d\n", p.POAValidators)) + str.WriteString(fmt.Sprintf(" - TM validators (total): %d\n", p.TMValidatorsTotal)) + str.WriteString(fmt.Sprintf(" - TM validators (active): %d\n", p.TMValidatorsActive)) + str.WriteString("Operations:\n") + str.WriteString(fmt.Sprintf(" - Create validators: %s\n", FormatDuration(p.OpCreateValidator))) + str.WriteString(fmt.Sprintf(" - Delegate bonding tokens: %s\n", FormatDuration(p.OpDelegateBonding))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpDelegateBondingAmountRatio)) + str.WriteString(fmt.Sprintf(" Max limit ratio: %s\n", p.OpDelegateBondingMaxSupplyRatio)) + str.WriteString(fmt.Sprintf(" - Delegate LP tokens: %s\n", FormatDuration(p.OpDelegateLP))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpDelegateLPAmountRatio)) + str.WriteString(fmt.Sprintf(" Max limit ratio: %s\n", p.OpDelegateLPMaxSupplyRatio)) + str.WriteString(fmt.Sprintf(" - Redelegate bonding tokens: %s\n", FormatDuration(p.OpRedelegateBonding))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpRedelegateBondingAmountRatio)) + str.WriteString(fmt.Sprintf(" - Redelegate LP tokens: %s\n", FormatDuration(p.OpRedelegateLP))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpRedelegateLPAmountRatio)) + str.WriteString(fmt.Sprintf(" - Undelegate bonding tokens: %s\n", FormatDuration(p.OpUndelegateBonding))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpUndelegateBondingAmountRatio)) + str.WriteString(fmt.Sprintf(" - Undelegate LP tokens: %s\n", FormatDuration(p.OpUndelegateLP))) + str.WriteString(fmt.Sprintf(" Amount ratio: %s\n", p.OpUndelegateLPAmountRatio)) + str.WriteString(fmt.Sprintf(" - Withdraw validator comission: %s\n", FormatDuration(p.OpGetValidatorRewards))) + str.WriteString(fmt.Sprintf(" - Withdraw delegator reward: %s\n", FormatDuration(p.OpGetDelegatorRewards))) + str.WriteString(fmt.Sprintf(" - Lock rewards: %s\n", FormatDuration(p.OpLockRewards))) + str.WriteString(fmt.Sprintf(" Ratio: %s\n", p.OpLockRewardsRatio)) + + return str.String() +} + +func simulate(t *testing.T, profile SimProfile) { go http.ListenAndServe(":8090", nil) - expSimDur := 24 * 30 * 24 * time.Hour + t.Logf(profile.String()) // create a tmp directory - workingDir, err := ioutil.TempDir("/tmp", fmt.Sprintf("dnode-simulator-%s-", t.Name())) + workingDir, err := ioutil.TempDir("/tmp", fmt.Sprintf("dnode-simulator-%s-", profile.ID)) require.NoError(t, err) - // genesis accounts balance (1M xfi, 1M sxfi) - genAmount, ok := sdk.NewIntFromString("1000000000000000000000000") - require.True(t, ok) + // genesis accounts balance + amtDecimals := sdk.NewInt(1000000000000000000) genCoins := sdk.NewCoins( - sdk.NewCoin(config.MainDenom, genAmount), - sdk.NewCoin(config.StakingDenom, genAmount), + sdk.NewCoin(defaults.MainDenom, sdk.NewInt(profile.MainTokensBalanceWODec).Mul(amtDecimals)), + sdk.NewCoin(defaults.StakingDenom, sdk.NewInt(profile.BondingTokensBalanceWODec).Mul(amtDecimals)), + sdk.NewCoin(defaults.LiquidityProviderDenom, sdk.NewInt(profile.LPTokensBalanceWODec).Mul(amtDecimals)), ) - // custom distribution params - treasuryCapacity, ok := sdk.NewIntFromString("250000000000000000000000") - require.True(t, ok) - distParams := distribution.DefaultParams() - distParams.PublicTreasuryPoolCapacity = treasuryCapacity - - // custom staking params - stakingParams := staking.DefaultParams() - stakingParams.UnbondingTime = 24 * time.Hour - stakingParams.MaxValidators = 100 + // write profile to file + { + f, err := os.Create(path.Join(workingDir, "profile.txt")) + require.NoError(t, err) + _, err = f.WriteString(profile.String()) + require.NoError(t, err) + f.Close() + } // CSV report writer reportWriter, writerClose := NewSimReportCSVWriter(t, path.Join(workingDir, "report.csv")) @@ -55,15 +139,18 @@ func TestSimInflation(t *testing.T) { // create simulator s := NewSimulator(t, workingDir, NewDefferOps(), //InMemoryDBOption(), - BlockTimeOption(60*time.Second, 65*time.Second), - GenerateWalletAccountsOption(500, 3, 150, genCoins), + BlockTimeOption(profile.BlockTimeMin, profile.BlockTimeMax), + GenerateWalletAccountsOption(profile.Accounts, profile.POAValidators, genCoins), LogOption(log.AllowInfoWith("module", "x/staking")), LogOption(log.AllowInfoWith("module", "x/mint")), LogOption(log.AllowInfoWith("module", "x/distribution")), LogOption(log.AllowInfoWith("module", "x/slashing")), LogOption(log.AllowInfoWith("module", "x/evidence")), - StakingParamsOption(stakingParams), - DistributionParamsOption(distParams), + StakingParamsOption(func(state *staking.GenesisState) { + state.Params.UnbondingTime = 24 * time.Hour + state.Params.MaxValidators = uint16(profile.TMValidatorsActive) + state.Params.MaxDelegationsRatio = sdk.NewDecWithPrec(1000, 0) + }), InvariantCheckPeriodOption(1000), OperationsOption( NewSimInvariantsOp(1*time.Hour), @@ -71,13 +158,17 @@ func TestSimInflation(t *testing.T) { // NewReportOp(24*time.Hour, false, NewSimReportConsoleWriter(), reportWriter), // - NewCreateValidatorOp(2*24*time.Hour), - NewDelegateOp(8*time.Hour, sdk.NewDecWithPrec(40, 2)), // 40 % - NewRedelegateOp(20*time.Hour, sdk.NewDecWithPrec(20, 2)), // 20 % - NewUndelegateOp(48*time.Hour, sdk.NewDecWithPrec(25, 2)), // 25 % + NewCreateValidatorOp(profile.OpCreateValidator, profile.TMValidatorsTotal), + NewDelegateBondingOp(profile.OpDelegateBonding, profile.OpDelegateBondingAmountRatio, profile.OpDelegateBondingMaxSupplyRatio), + NewDelegateLPOp(profile.OpDelegateLP, profile.OpDelegateLPAmountRatio, profile.OpDelegateLPMaxSupplyRatio), + NewRedelegateBondingOp(profile.OpRedelegateBonding, profile.OpRedelegateBondingAmountRatio), + NewRedelegateLPOp(profile.OpRedelegateLP, profile.OpRedelegateLPAmountRatio), + NewUndelegateBondingOp(profile.OpUndelegateBonding, profile.OpUndelegateBondingAmountRatio), + NewUndelegateLPOp(profile.OpUndelegateLP, profile.OpUndelegateLPAmountRatio), // - NewGetDelRewardOp(120*time.Hour), - NewGetValRewardOp(72*time.Hour), + NewGetValidatorRewardOp(profile.OpGetValidatorRewards), + NewGetDelegatorRewardOp(profile.OpGetDelegatorRewards), + NewLockValidatorRewardsOp(profile.OpLockRewards, profile.OpLockRewardsRatio), ), ) @@ -85,9 +176,58 @@ func TestSimInflation(t *testing.T) { // work loop _, simDur := s.SimulatedDur() - for simDur < expSimDur { + for simDur < profile.SimDuration { s.Next() - _, simDur = s.SimulatedDur() } + + t.Logf("Simulation is done, output dir: %s", s.workingDir) +} + +func TestSimInflation(t *testing.T) { + profile := SimProfile{ + ID: "low_staking", + SimDuration: 1*Year + 6*Month, + BlockTimeMin: 120 * time.Second, + BlockTimeMax: 125 * time.Second, + // + MainTokensBalanceWODec: 50000, + BondingTokensBalanceWODec: 500000, + LPTokensBalanceWODec: 100000, + // + Accounts: 300, + POAValidators: 3, + TMValidatorsTotal: 150, + TMValidatorsActive: 100, + // + OpCreateValidator: 3 * time.Hour, + // + OpDelegateBonding: 6 * time.Hour, + OpDelegateBondingAmountRatio: sdk.NewDecWithPrec(40, 2), + OpDelegateBondingMaxSupplyRatio: sdk.NewDecWithPrec(30, 2), + // + OpDelegateLP: 1 * Day, + OpDelegateLPAmountRatio: sdk.NewDecWithPrec(40, 2), + OpDelegateLPMaxSupplyRatio: sdk.NewDecWithPrec(30, 2), + // + OpRedelegateBonding: 4 * Day, + OpRedelegateBondingAmountRatio: sdk.NewDecWithPrec(30, 2), + // + OpRedelegateLP: 8 * Day, + OpRedelegateLPAmountRatio: sdk.NewDecWithPrec(30, 2), + // + OpUndelegateBonding: 2 * Day, + OpUndelegateBondingAmountRatio: sdk.NewDecWithPrec(15, 2), + // + OpUndelegateLP: 4 * Day, + OpUndelegateLPAmountRatio: sdk.NewDecWithPrec(15, 2), + // + OpGetValidatorRewards: 1 * Week, + OpGetDelegatorRewards: 1 * Day, + // + OpLockRewards: 1 * Week, + OpLockRewardsRatio: sdk.NewDecWithPrec(30, 2), + } + + simulate(t, profile) } diff --git a/helpers/tests/simulator/sim.go b/helpers/tests/simulator/sim.go index fd496b20..5e47bf73 100644 --- a/helpers/tests/simulator/sim.go +++ b/helpers/tests/simulator/sim.go @@ -11,9 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/crisis" "github.com/cosmos/cosmos-sdk/x/genutil" - "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -23,6 +21,8 @@ import ( "github.com/dfinance/dnode/app" "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/cmd/config/restrictions" "github.com/dfinance/dnode/x/genaccounts" "github.com/dfinance/dnode/x/poa" @@ -34,16 +34,16 @@ var ( type Simulator struct { // configurable settings - genesisState map[string]json.RawMessage - invariantCheckPeriod uint - logOptions []log.Option - minSelfDelegationLvl sdk.Int - nodeValidatorConfig SimValidatorConfig - operations []*SimOperation - accounts []*SimAccount - useInMemDB bool - minBlockDur time.Duration - maxBlockDur time.Duration + genesisState map[string]json.RawMessage + invariantCheckPeriod uint + logOptions []log.Option + minSelfDelegationCoin sdk.Coin + nodeValidatorConfig SimValidatorConfig + operations []*SimOperation + accounts SimAccounts + useInMemDB bool + minBlockDur time.Duration + maxBlockDur time.Duration // predefined settings chainID string monikerID string @@ -53,6 +53,7 @@ type Simulator struct { unbondingDur time.Duration mainDenom string stakingDenom string + lpDenom string mainDenomDecimals uint8 stakingDenomDecimals uint8 workingDir string @@ -70,13 +71,17 @@ type Simulator struct { } type Counter struct { - Delegations int64 - Undelegations int64 - Redelegations int64 + BDelegations int64 + BUndelegations int64 + BRedelegations int64 + LPDelegations int64 + LPUndelegations int64 + LPRedelegations int64 + LockedRewards int64 Rewards int64 Commissions int64 - RewardsCollected sdk.Int - CommissionsCollected sdk.Int + RewardsCollected sdk.Coins + CommissionsCollected sdk.Coins } // BuildTmpFilePath builds file name inside of the Simulator working dir. @@ -90,7 +95,7 @@ func (s *Simulator) Start() { // generate wallet accounts genAccs := make(genaccounts.GenesisState, 0) - poaAccs := make([]*SimAccount, 0) + poaAccs := make(SimAccounts, 0) for accIdx := 0; accIdx < len(s.accounts); accIdx++ { acc := s.accounts[accIdx] @@ -147,6 +152,7 @@ func (s *Simulator) Start() { EthAddress: "0x17f7D1087971dF1a0E6b8Dae7428E97484E32615", }) } + s.genesisState[poa.ModuleName] = codec.MustMarshalJSONIndent(s.cdc, poa.GenesisState{ Parameters: poa.DefaultParams(), Validators: validators, @@ -157,49 +163,19 @@ func (s *Simulator) Start() { state := staking.GenesisState{} s.cdc.MustUnmarshalJSON(s.genesisState[staking.ModuleName], &state) - state.Params.BondDenom = s.stakingDenom - state.Params.MinSelfDelegationLvl = s.minSelfDelegationLvl - - s.genesisState[staking.ModuleName] = codec.MustMarshalJSONIndent(s.cdc, state) - s.unbondingDur = state.Params.UnbondingTime } - // mint - { - state := mint.GenesisState{} - s.cdc.MustUnmarshalJSON(s.genesisState[mint.ModuleName], &state) - - state.Params.MintDenom = s.stakingDenom - - s.genesisState[mint.ModuleName] = codec.MustMarshalJSONIndent(s.cdc, state) - } - // crisis - { - state := crisis.GenesisState{} - s.cdc.MustUnmarshalJSON(s.genesisState[crisis.ModuleName], &state) - - defFeeAmount, ok := sdk.NewIntFromString(config.DefaultFeeAmount) - require.True(s.t, ok) - - state.ConstantFee.Denom = s.mainDenom - state.ConstantFee.Amount = defFeeAmount - - s.genesisState[crisis.ModuleName] = codec.MustMarshalJSONIndent(s.cdc, state) - - s.defFee = sdk.NewCoin(s.mainDenom, defFeeAmount) - } // genutil, create node validator { nodeAcc := s.accounts[0] - selfDelegation := sdk.NewCoin(s.stakingDenom, s.minSelfDelegationLvl) msg := staking.NewMsgCreateValidator( nodeAcc.Address.Bytes(), nodeAcc.PublicKey, - selfDelegation, + s.minSelfDelegationCoin, staking.NewDescription(s.monikerID, "", "", "", ""), s.nodeValidatorConfig.Commission, - s.minSelfDelegationLvl, + s.minSelfDelegationCoin.Amount, ) tx := s.GenTxAdvanced(msg, 0, 0, nodeAcc.PublicKey, nodeAcc.PrivateKey) @@ -223,11 +199,10 @@ func (s *Simulator) Start() { // get node validator validators := s.QueryStakeValidators(1, 10, sdk.BondStatusBonded) require.Len(s.t, validators, 1) - s.accounts[0].OperatedValidator = &validators[0] + s.accounts[0].OperatedValidator = NewSimValidator(validators[0]) // update node account delegations - delegation := s.QueryStakeDelegation(s.accounts[0], s.accounts[0].OperatedValidator) - s.accounts[0].Delegations = append(s.accounts[0].Delegations, delegation) + s.UpdateAccount(s.accounts[0]) s.t.Logf("Simulator working / output directory: %s", s.workingDir) } @@ -252,7 +227,18 @@ func (s *Simulator) Next() { blockCreated := false for _, op := range s.operations { - blockCreated = op.Exec(s, s.prevBlockTime) + opReport := op.Exec(s, s.prevBlockTime) + if msg := opReport.String(); msg != "" { + if opReport.Executed { + s.logger.Info(msg) + } else { + s.logger.Error(msg) + } + } + + if opReport.Executed { + blockCreated = true + } } if !blockCreated { @@ -281,8 +267,8 @@ func (s *Simulator) beginBlock() { for _, val := range validators { lastCommitInfo.Votes = append(lastCommitInfo.Votes, abci.VoteInfo{ Validator: abci.Validator{ - Address: val.ConsPubKey.Address(), - Power: val.GetConsensusPower(), + Address: val.Validator.ConsPubKey.Address(), + Power: val.Validator.GetConsensusPower(), }, SignedLastBlock: true, }) @@ -293,7 +279,7 @@ func (s *Simulator) beginBlock() { ChainID: s.chainID, Height: nextHeight, Time: nextBlockTime, - ProposerAddress: proposer.ConsPubKey.Address(), + ProposerAddress: proposer.Validator.ConsPubKey.Address(), }, LastCommitInfo: lastCommitInfo, }) @@ -308,9 +294,6 @@ func (s *Simulator) endBlock() { // NewSimulator creates a new Simulator. func NewSimulator(t *testing.T, workingDir string, defferQueue *DefferOps, options ...SimOption) *Simulator { // defaults init - minSelfDelegation, ok := sdk.NewIntFromString(config.DefMinSelfDelegation) - require.True(t, ok) - nodeValCommissionRate, err := sdk.NewDecFromStr("0.100000000000000000") require.NoError(t, err) @@ -321,10 +304,9 @@ func NewSimulator(t *testing.T, workingDir string, defferQueue *DefferOps, optio require.NoError(t, err) s := &Simulator{ - genesisState: app.ModuleBasics.DefaultGenesis(), - invariantCheckPeriod: 1, - logOptions: make([]log.Option, 0), - minSelfDelegationLvl: minSelfDelegation, + invariantCheckPeriod: 1, + logOptions: make([]log.Option, 0), + minSelfDelegationCoin: defaults.MinSelfDelegationCoin, nodeValidatorConfig: SimValidatorConfig{ Commission: staking.CommissionRates{ Rate: nodeValCommissionRate, @@ -332,16 +314,18 @@ func NewSimulator(t *testing.T, workingDir string, defferQueue *DefferOps, optio MaxChangeRate: nodeValCommissionMaxChangeRate, }, }, - accounts: make([]*SimAccount, 0), + accounts: make(SimAccounts, 0), minBlockDur: 5 * time.Second, maxBlockDur: 6 * time.Second, // chainID: "simChainID", monikerID: "simMoniker", - defGas: 500000, + defFee: defaults.FeeCoin, + defGas: defaults.MaxGas, // - mainDenom: config.MainDenom, - stakingDenom: config.StakingDenom, + mainDenom: defaults.MainDenom, + stakingDenom: defaults.StakingDenom, + lpDenom: defaults.LiquidityProviderDenom, mainDenomDecimals: 18, stakingDenomDecimals: 18, mainAmountDecimalsRatio: sdk.NewDecWithPrec(1, 0), @@ -354,8 +338,12 @@ func NewSimulator(t *testing.T, workingDir string, defferQueue *DefferOps, optio cdc: app.MakeCodec(), defferQueue: defferQueue, } - s.counter.RewardsCollected = sdk.ZeroInt() - s.counter.CommissionsCollected = sdk.ZeroInt() + s.counter.RewardsCollected = sdk.NewCoins() + s.counter.CommissionsCollected = sdk.NewCoins() + + defaultGenesis, err := genesis.OverrideGenesisStateDefaults(s.cdc, app.ModuleBasics.DefaultGenesis()) + require.NoError(t, err) + s.genesisState = defaultGenesis for _, option := range options { option(s) diff --git a/helpers/tests/simulator/sim_account.go b/helpers/tests/simulator/sim_account.go index 792f6616..fc7398e8 100644 --- a/helpers/tests/simulator/sim_account.go +++ b/helpers/tests/simulator/sim_account.go @@ -1,8 +1,12 @@ package simulator import ( + "math/rand" + "sort" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" ) @@ -15,11 +19,15 @@ type SimAccount struct { PublicKey crypto.PubKey Coins sdk.Coins IsPoAValidator bool - CreateValidator bool - OperatedValidator *staking.Validator + OperatedValidator *SimValidator Delegations []staking.DelegationResponse } +// IsValOperator checks if account is a validator operator. +func (a *SimAccount) IsValOperator() bool { + return a.OperatedValidator != nil +} + // HasDelegation checks if account has already delegated to the specified validator. func (a *SimAccount) HasDelegation(valAddress sdk.ValAddress) bool { for _, del := range a.Delegations { @@ -30,3 +38,98 @@ func (a *SimAccount) HasDelegation(valAddress sdk.ValAddress) bool { return false } + +// GetSortedDelegations returns delegations sorted by tokens balance list. +func (a *SimAccount) GetSortedDelegations(bondingTokens, desc bool) staking.DelegationResponses { + tmpDels := make(staking.DelegationResponses, len(a.Delegations)) + copy(tmpDels, a.Delegations) + + sort.Slice(tmpDels, func(i, j int) bool { + if bondingTokens { + if tmpDels[i].BondingBalance.Amount.GT(tmpDels[j].BondingBalance.Amount) { + return desc + } + return !desc + } + + if tmpDels[i].LPBalance.Amount.GT(tmpDels[j].LPBalance.Amount) { + return desc + } + return !desc + }) + + return tmpDels +} + +// GetShuffledDelegations returns shuffled delegations list. +func (a *SimAccount) GetShuffledDelegations(bondingTokens bool) staking.DelegationResponses { + tmpDels := make(staking.DelegationResponses, len(a.Delegations)) + copy(tmpDels, a.Delegations) + + for i := range tmpDels { + j := rand.Intn(i + 1) + tmpDels[i], tmpDels[j] = tmpDels[j], tmpDels[i] + } + + return tmpDels +} + +type SimAccounts []*SimAccount + +// GetByAddress returns account by address. +func (a SimAccounts) GetByAddress(address sdk.AccAddress) *SimAccount { + for _, acc := range a { + if acc.Address.Equals(address) { + return acc + } + } + + return nil +} + +// GetRandom returns randomly selected account. +func (a SimAccounts) GetRandom() *SimAccount { + aMaxIndex := len(a) - 1 + + return a[rand.Intn(aMaxIndex)] +} + +// GetShuffled returns random sorted accounts list. +func (a SimAccounts) GetShuffled() SimAccounts { + tmpAcc := make(SimAccounts, len(a)) + copy(tmpAcc, a) + + for i := range tmpAcc { + j := rand.Intn(i + 1) + tmpAcc[i], tmpAcc[j] = tmpAcc[j], tmpAcc[i] + } + + return tmpAcc +} + +// GetAccountsSortedByBalance returns account sorted by staking denom list. +func (a SimAccounts) GetSortedByBalance(denom string, desc bool) SimAccounts { + tmpAccs := make(SimAccounts, len(a)) + copy(tmpAccs, a) + + sort.Slice(tmpAccs, func(i, j int) bool { + iBalance := tmpAccs[i].Coins.AmountOf(denom) + jBalance := tmpAccs[j].Coins.AmountOf(denom) + + if iBalance.GT(jBalance) { + return desc + } + return !desc + }) + + return tmpAccs +} + +// UpdateAccount updates account balance and active delegations. +func (s *Simulator) UpdateAccount(simAcc *SimAccount) { + require.NotNil(s.t, simAcc) + + updAcc := s.QueryAuthAccount(simAcc.Address) + simAcc.Coins = updAcc.GetCoins() + simAcc.Delegations = s.QueryStakeDelDelegations(simAcc.Address) +} diff --git a/helpers/tests/simulator/sim_helpers.go b/helpers/tests/simulator/sim_helpers.go index 485f5272..d165df73 100644 --- a/helpers/tests/simulator/sim_helpers.go +++ b/helpers/tests/simulator/sim_helpers.go @@ -1,62 +1,44 @@ package simulator import ( - "math/rand" - "sort" + "strings" + "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/stretchr/testify/require" ) -func (s *Simulator) GetRandomAccount() *SimAccount { - aMaxIndex := len(s.accounts) - 1 - return s.accounts[rand.Intn(aMaxIndex)] -} - -// UpdateAccount updates account balance and active delegations. -func (s *Simulator) UpdateAccount(simAcc *SimAccount) { - require.NotNil(s.t, simAcc) +const ( + Day = 24 * time.Hour + Week = 7 * Day + Month = 4 * Week + Year = 12 * Month +) - updAcc := s.QueryAuthAccount(simAcc.Address) - simAcc.Coins = updAcc.GetCoins() - simAcc.Delegations = s.QueryStakeDelDelegations(simAcc.Address) +// GetAllAccounts returns all known to Simulator accounts. +func (s *Simulator) GetAllAccounts() SimAccounts { + return s.accounts } -// GetValidatorByAddress returns validator. -func (s *Simulator) GetValidatorByAddress(address sdk.ValAddress) *staking.Validator { +// GetAllValidators returns all known to Simulator validators. +func (s *Simulator) GetAllValidators() SimValidators { + validators := make(SimValidators, 0) for _, acc := range s.accounts { - if acc.OperatedValidator != nil { - if acc.OperatedValidator.OperatorAddress.Equals(address) { - return acc.OperatedValidator - } + if !acc.IsValOperator() { + continue } + validators = append(validators, acc.OperatedValidator) } - return nil -} - -// UpdateValidator updates validator status. -func (s *Simulator) UpdateValidator(val *staking.Validator) { - require.NotNil(s.t, val) - - updVal := s.QueryStakeValidator(val.OperatorAddress) - val.Status = updVal.Status - val.Jailed = updVal.Jailed - val.Bonding = updVal.Bonding - val.LP = updVal.LP - val.UnbondingHeight = updVal.UnbondingHeight - val.UnbondingCompletionTime = updVal.UnbondingCompletionTime + return validators } -// GetValidators returns all known to Simulator validators. -func (s *Simulator) GetValidators(bonded, unbonding, unbonded bool) []*staking.Validator { - validators := make([]*staking.Validator, 0) +// GetValidators returns known to Simulator validators filtered by status. +func (s *Simulator) GetValidators(bonded, unbonding, unbonded bool) SimValidators { + validators := make(SimValidators, 0) for _, acc := range s.accounts { if acc.OperatedValidator != nil { add := false - switch acc.OperatedValidator.Status { + switch acc.OperatedValidator.GetStatus() { case sdk.Bonded: if bonded { add = true @@ -80,41 +62,22 @@ func (s *Simulator) GetValidators(bonded, unbonding, unbonded bool) []*staking.V return validators } -// GetShuffledAccounts returns random sorted account list. -func (s Simulator) GetShuffledAccounts() []*SimAccount { - tmpAcc := make([]*SimAccount, len(s.accounts)) - copy(tmpAcc, s.accounts) - - for i := range tmpAcc { - j := rand.Intn(i + 1) - tmpAcc[i], tmpAcc[j] = tmpAcc[j], tmpAcc[i] - } - - return tmpAcc +// FormatCoin formats coin to decimal string. +func (s *Simulator) FormatCoin(coin sdk.Coin) string { + return s.FormatIntDecimals(coin.Amount, s.stakingAmountDecimalsRatio) + coin.Denom } -// GetAccountsSortedByBalance returns account sorted by staking denom list. -func (s Simulator) GetAccountsSortedByBalance(desc bool) []*SimAccount { - tmpAccs := make([]*SimAccount, len(s.accounts)) - copy(tmpAccs, s.accounts) - - sort.Slice(tmpAccs, func(i, j int) bool { - iBalance := tmpAccs[i].Coins.AmountOf(s.stakingDenom) - jBalance := tmpAccs[j].Coins.AmountOf(s.stakingDenom) - - if iBalance.GT(jBalance) { - return desc - } - return !desc - }) - - return tmpAccs -} +// FormatCoins formats coins to decimal string. +func (s *Simulator) FormatCoins(coins sdk.Coins) string { + out := make([]string, 0, len(coins)) + for _, coin := range coins { + out = append(out, s.FormatIntDecimals(coin.Amount, s.stakingAmountDecimalsRatio)+coin.Denom) + } -func (s *Simulator) FormatStakingCoin(coin sdk.Coin) string { - return s.FormatIntDecimals(coin.Amount, s.stakingAmountDecimalsRatio) + s.stakingDenom + return strings.Join(out, ",") } +// FormatIntDecimals converts sdk.Int to sdk.Dec using convert ratio and returns a string representation. func (s *Simulator) FormatIntDecimals(value sdk.Int, decRatio sdk.Dec) string { valueDec := sdk.NewDecFromInt(value) fixedDec := valueDec.Mul(decRatio) @@ -127,53 +90,3 @@ func (s *Simulator) FormatDecDecimals(value sdk.Dec, decRatio sdk.Dec) string { return fixedDec.String() } - -// GetSortedByStakeValidator returns validators sorted by stake. -func GetSortedByStakeValidator(validators []*staking.Validator, desc bool) []*staking.Validator { - sort.Slice(validators, func(i, j int) bool { - if validators[i].Bonding.Tokens.GT(validators[j].Bonding.Tokens) { - return desc - } - return !desc - }) - - return validators -} - -// GetSortedDelegation returns delegation sorted list. -func GetSortedDelegation(responses staking.DelegationResponses, desc bool) staking.DelegationResponses { - sort.Slice(responses, func(i, j int) bool { - if responses[i].BondingBalance.Amount.GT(responses[j].BondingBalance.Amount) { - return desc - } - return !desc - }) - - return responses -} - -// GetShuffledDelegations returns delegations in the random order. -func GetShuffledDelegations(delegations staking.DelegationResponses) staking.DelegationResponses { - tmp := make(staking.DelegationResponses, len(delegations)) - copy(tmp, delegations) - - for i := range tmp { - j := rand.Intn(i + 1) - tmp[i], tmp[j] = tmp[j], tmp[i] - } - - return tmp -} - -// ShuffleRewards returns rewards in the random order. -func ShuffleRewards(rewards []distribution.DelegationDelegatorReward) []distribution.DelegationDelegatorReward { - tmp := make([]distribution.DelegationDelegatorReward, len(rewards)) - copy(tmp, rewards) - - for i := range tmp { - j := rand.Intn(i + 1) - tmp[i], tmp[j] = tmp[j], tmp[i] - } - - return tmp -} diff --git a/helpers/tests/simulator/sim_invariant.go b/helpers/tests/simulator/sim_invariant.go index abce3b25..224cd018 100644 --- a/helpers/tests/simulator/sim_invariant.go +++ b/helpers/tests/simulator/sim_invariant.go @@ -8,53 +8,52 @@ import ( // NewSimInvariantsOp checks inner simulator state integrity. func NewSimInvariantsOp(period time.Duration) *SimOperation { - handler := func(s *Simulator) bool { + id := "InvariantsOp" + + handler := func(s *Simulator) (bool, string) { // check validator owner has exactly one self-delegation - for _, acc := range s.accounts { - if acc.OperatedValidator != nil { + for _, acc := range s.GetAllAccounts() { + if acc.IsValOperator() { selfDelCnt := 0 for _, del := range acc.Delegations { - if del.ValidatorAddress.Equals(acc.OperatedValidator.OperatorAddress) { + if del.ValidatorAddress.Equals(acc.OperatedValidator.GetAddress()) { selfDelCnt++ } } - require.Equal(s.t, 1, selfDelCnt, "simInvariants: invalid number of selfDelegations found for: %s", acc.Address) + require.Equal(s.t, 1, selfDelCnt, "%s: invalid number of selfDelegations found for: %s", id, acc.Address) } } // check for duplicated validators validatorsMap := make(map[string]bool, len(s.accounts)) - for _, acc := range s.accounts { - if acc.OperatedValidator != nil { - valAddrStr := acc.OperatedValidator.OperatorAddress.String() - found := validatorsMap[valAddrStr] - require.False(s.t, found, "duplicated validator found: %s", valAddrStr) + for _, val := range s.GetAllValidators() { + valAddrStr := val.GetAddress().String() + found := validatorsMap[valAddrStr] + require.False(s.t, found, "%s: duplicated validator found: %s", id, valAddrStr) - validatorsMap[valAddrStr] = true - } + validatorsMap[valAddrStr] = true } - return true + return true, "" } - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) } // NewForceUpdateOp updates various simulator states for consistency. func NewForceUpdateOp(period time.Duration) *SimOperation { - handler := func(s *Simulator) bool { - for _, acc := range s.accounts { - accValidator := acc.OperatedValidator - if accValidator == nil { - continue - } + id := "ForceUpdateOp" - s.UpdateValidator(accValidator) + handler := func(s *Simulator) (bool, string) { + for _, val := range s.GetAllValidators() { + s.UpdateValidator(val) } - return true + s.counter.LockedRewards = int64(len(s.GetAllValidators().GetLocked())) + + return true, "" } - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) } diff --git a/helpers/tests/simulator/sim_operation.go b/helpers/tests/simulator/sim_operation.go index 7b259923..7c62e97c 100644 --- a/helpers/tests/simulator/sim_operation.go +++ b/helpers/tests/simulator/sim_operation.go @@ -1,6 +1,11 @@ package simulator -import "time" +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) // SimOperationNextExecFn returns next execution time for SimOperation. type SimOperationNextExecFn func(curTime time.Time, period time.Duration) time.Time @@ -13,11 +18,12 @@ func NewPeriodicNextExecFn() SimOperationNextExecFn { } // SimOperationHandler handles operation using Simulator infra. -type SimOperationHandler func(s *Simulator) bool +type SimOperationHandler func(s *Simulator) (executed bool, message string) // SimOperation keeps operation state and handlers. // CONTRACT: operation must update changed Simulator state (account balance, modified validator, new delegation, etc). type SimOperation struct { + id string handlerFn SimOperationHandler nextExecFn SimOperationNextExecFn period time.Duration @@ -25,10 +31,28 @@ type SimOperation struct { execCounter int } +// SimOperationReport contains SimOperation execution report. +type SimOperationReport struct { + ID string + Executed bool + Duration time.Duration + LogMessage string +} + +func (r SimOperationReport) String() string { + if r.LogMessage == "" { + return "" + } + + return fmt.Sprintf("%s [%v]: %s", r.ID, r.Duration.Truncate(time.Millisecond), r.LogMessage) +} + // Exec executes the operation if its time has come. -func (op *SimOperation) Exec(s *Simulator, curTime time.Time) (executed bool) { +func (op *SimOperation) Exec(s *Simulator, curTime time.Time) (report SimOperationReport) { + report.ID = op.id + defer func() { - if !executed { + if !report.Executed { return } @@ -37,23 +61,37 @@ func (op *SimOperation) Exec(s *Simulator, curTime time.Time) (executed bool) { }() if op.nextExecTime.IsZero() { - executed = true + report.Executed = true op.execCounter-- return } if curTime.After(op.nextExecTime) { - executed = op.handlerFn(s) + opStart := time.Now() + report.Executed, report.LogMessage = op.handlerFn(s) + report.Duration = time.Since(opStart) } return } // NewSimOperation creates a new SimOperation. -func NewSimOperation(period time.Duration, nextExecFn SimOperationNextExecFn, handlerFn SimOperationHandler) *SimOperation { +func NewSimOperation(id string, period time.Duration, nextExecFn SimOperationNextExecFn, handlerFn SimOperationHandler) *SimOperation { return &SimOperation{ + id: id, handlerFn: handlerFn, nextExecFn: nextExecFn, period: period, } } + +// checkRatioArg checks SimOperation ratio coef input (0 < value <= 1.0). +func checkRatioArg(opName, argName string, argValue sdk.Dec) { + errMsgPrefix := fmt.Sprintf("%s: %s: ", opName, argName) + if argValue.LTE(sdk.ZeroDec()) { + panic(fmt.Errorf("%s: LTE 0", errMsgPrefix)) + } + if argValue.GT(sdk.OneDec()) { + panic(fmt.Errorf("%s: GE 1", errMsgPrefix)) + } +} diff --git a/helpers/tests/simulator/sim_ops.go b/helpers/tests/simulator/sim_ops.go deleted file mode 100644 index 02980b0d..00000000 --- a/helpers/tests/simulator/sim_ops.go +++ /dev/null @@ -1,362 +0,0 @@ -package simulator - -import ( - "fmt" - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/stretchr/testify/require" -) - -// NewCreateValidatorOp creates validator for account which is not an operator already. -func NewCreateValidatorOp(period time.Duration) *SimOperation { - handler := func(s *Simulator) bool { - // find account without validator - var simAcc *SimAccount - for _, acc := range s.accounts { - if !acc.CreateValidator { - continue - } - - if acc.OperatedValidator == nil { - simAcc = acc - break - } - } - if simAcc == nil { - return true - } - - // define commissions - comRate, err := sdk.NewDecFromStr("0.100000000000000000") - require.NoError(s.t, err) - - comMaxRate, err := sdk.NewDecFromStr("0.200000000000000000") - require.NoError(s.t, err) - - comMaxChangeRate, err := sdk.NewDecFromStr("0.010000000000000000") - require.NoError(s.t, err) - - // create - s.TxStakingCreateValidator(simAcc, staking.NewCommissionRates(comRate, comMaxRate, comMaxChangeRate)) - s.beginBlock() - s.endBlock() - - // update account - validator := s.QueryStakeValidator(sdk.ValAddress(simAcc.Address)) - s.UpdateAccount(simAcc) - simAcc.OperatedValidator = &validator - - s.logger.Info(fmt.Sprintf("ValidatorOp: %s (%s) created for %s", validator.OperatorAddress, validator.GetConsAddr(), simAcc.Address)) - - return true - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -// NewDelegateOp picks a validator and searches for account to delegate. -// SelfStake increment is allowed. -// Delegation amount = current account balance * ratioCoef. -// Op priorities: -// validator: -// - bonded; -// - lowest tokens amount; -// account - random, enough coins; -func NewDelegateOp(period time.Duration, delegateRatio sdk.Dec) *SimOperation { - checkRatioArg("DelegateOp", "delegateRatio", delegateRatio) - - handler := func(s *Simulator) bool { - // pick a validator with the lowest tokens amount - validators := GetSortedByStakeValidator(s.GetValidators(true, false, false), false) - if len(validators) == 0 { - return false - } - validator := validators[0] - - // pick a target account with enough coins - var delAmt sdk.Int - var targetAcc *SimAccount - for _, acc := range s.GetAccountsSortedByBalance(true) { - // estimate delegation amount - accCoinAmtDec := sdk.NewDecFromInt(acc.Coins.AmountOf(s.stakingDenom)) - delAmt = accCoinAmtDec.Mul(delegateRatio).TruncateInt() - if delAmt.IsZero() { - continue - } - - targetAcc = acc - } - if targetAcc == nil { - return false - } - - // delegate - coin := sdk.NewCoin(s.stakingDenom, delAmt) - s.TxStakingDelegate(targetAcc, validator, coin) - - // update account - s.UpdateAccount(targetAcc) - // update validator - s.UpdateValidator(validator) - // update stats - s.counter.Delegations++ - - s.logger.Info(fmt.Sprintf("DelegateOp: %s: %s -> %s", targetAcc.Address, s.FormatStakingCoin(coin), validator.OperatorAddress)) - - return true - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -// NewRedelegateOp picks a validator and redelegate tokens to an other validator. -// Redelegation amount = current account delegation amount * ratioCoef. -// Op priorities: -// dstValidator: -// - bonded; -// - lowest tokens amount; -// srcValidator - highest account delegation shares; -// account: -// - random; -// - has no active redelegations with srcValidator and dstValidator; -// - has enough coins; -// - not a dstValidator owner; -func NewRedelegateOp(period time.Duration, redelegateRatio sdk.Dec) *SimOperation { - checkRatioArg("RedelegateOp", "redelegateRatio", redelegateRatio) - - handler := func(s *Simulator) bool { - // pick a dstValidator with the lowest tokens amount - validators := GetSortedByStakeValidator(s.GetValidators(true, false, false), true) - if len(validators) == 0 { - return false - } - dstValidator := validators[0] - - rdInProcess := func(accAddr sdk.AccAddress, srcValAddr, dstValAddr sdk.ValAddress) bool { - for _, rd := range s.QueryStakeRedelegations(accAddr, sdk.ValAddress{}, sdk.ValAddress{}) { - if rd.ValidatorSrcAddress.Equals(srcValAddr) || rd.ValidatorDstAddress.Equals(srcValAddr) { - return true - } - - if rd.ValidatorSrcAddress.Equals(dstValAddr) || rd.ValidatorDstAddress.Equals(dstValAddr) { - return true - } - } - - return false - } - - // pick a target account - for _, acc := range s.GetShuffledAccounts() { - accValAddr := sdk.ValAddress{} - if acc.OperatedValidator != nil { - accValAddr = acc.OperatedValidator.OperatorAddress - } - - // check not redelegating to the account owned validator - if dstValidator.OperatorAddress.Equals(accValAddr) { - continue - } - - // pick a delegation with the highest share - for _, delegation := range GetSortedDelegation(acc.Delegations, true) { - srcValidator := s.GetValidatorByAddress(delegation.ValidatorAddress) - - if srcValidator.OperatorAddress.Equals(dstValidator.OperatorAddress) { - continue - } - - // check not redelegating from the account owned validator - if srcValidator.OperatorAddress.Equals(accValAddr) { - continue - } - - // check if an other redelegation is in progress for the selected account - if rdInProcess(acc.Address, srcValidator.OperatorAddress, dstValidator.OperatorAddress) { - continue - } - - // estimate redelegation amount - rdAmtDec := sdk.NewDecFromInt(delegation.BondingBalance.Amount) - rdAmt := rdAmtDec.Mul(redelegateRatio).TruncateInt() - if rdAmt.IsZero() { - continue - } - - // redelegate - coin := sdk.NewCoin(delegation.BondingBalance.Denom, rdAmt) - s.TxStakingRedelegate(acc, srcValidator.OperatorAddress, dstValidator.OperatorAddress, coin) - - // update validators - s.UpdateValidator(srcValidator) - s.UpdateValidator(dstValidator) - // update account - s.UpdateAccount(acc) - // update stats - s.counter.Redelegations++ - - s.logger.Info(fmt.Sprintf("RedelegateOp: %s: %s -> %s -> %s", acc.Address, srcValidator.OperatorAddress, s.FormatStakingCoin(coin), dstValidator.OperatorAddress)) - - return true - } - } - - return true - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -// NewUndelegateOp picks a validator and undelegates tokens. -// Undelegation amount = current account delegation amount * ratioCoef. -// Op priorities: -// validator - highest tokens amount; -// account: -// - random; -// - has a validators delegation; -// - not a validator owner; -func NewUndelegateOp(period time.Duration, undelegateRatio sdk.Dec) *SimOperation { - checkRatioArg("UndelegateOp", "undelegateRatio", undelegateRatio) - - handler := func(s *Simulator) bool { - // pick a validator with the highest tokens amount; - validators := GetSortedByStakeValidator(s.GetValidators(true, true, true), true) - if len(validators) == 0 { - return false - } - validator := validators[0] - - for _, acc := range s.GetShuffledAccounts() { - accValAddr := sdk.ValAddress{} - if acc.OperatedValidator != nil { - accValAddr = acc.OperatedValidator.OperatorAddress - } - - for _, delegation := range acc.Delegations { - // check if account did delegate to the selected validator - if !validator.OperatorAddress.Equals(delegation.ValidatorAddress) { - continue - } - - // check not undelegating from the account owned validator - if accValAddr.Equals(validator.OperatorAddress) { - continue - } - - // estimate undelegation amount - udAmtDec := sdk.NewDecFromInt(delegation.BondingBalance.Amount) - udAmt := udAmtDec.Mul(undelegateRatio).TruncateInt() - if udAmt.IsZero() { - continue - } - - // undelegate - coin := sdk.NewCoin(delegation.BondingBalance.Denom, udAmt) - s.TxStakingUndelegate(acc, validator.OperatorAddress, coin) - - // update validator - s.UpdateValidator(validator) - // update account - s.UpdateAccount(acc) - // update stats - s.counter.Undelegations++ - - s.defferQueue.Add(s.prevBlockTime.Add(s.unbondingDur+5*time.Minute), func() { - s.UpdateAccount(acc) - }) - - s.logger.Info(fmt.Sprintf("UndelegateOp: %s: %s -> %s", acc.Address, validator.OperatorAddress, s.FormatStakingCoin(coin))) - - return true - } - } - - return false - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -// NewGetDelRewardOp take delegator rewards. -// Op priority: -// account; -// - random; -// - has delegations; -// validator - random account delegation; -func NewGetDelRewardOp(period time.Duration) *SimOperation { - handler := func(s *Simulator) bool { - for _, acc := range s.GetShuffledAccounts() { - if len(acc.Delegations) == 0 { - continue - } - targetDelegation := GetShuffledDelegations(acc.Delegations)[0] - - rewardsDec := s.QueryDistDelReward(acc.Address, targetDelegation.ValidatorAddress) - rewards := rewardsDec.AmountOf(s.stakingDenom).TruncateInt() - rewardsCoin := sdk.NewCoin(s.stakingDenom, rewards) - - // distribute - s.TxDistributionReward(acc, targetDelegation.ValidatorAddress) - - // update account - s.UpdateAccount(acc) - // update stats - s.counter.Rewards++ - s.counter.RewardsCollected = s.counter.RewardsCollected.Add(rewards) - - s.logger.Info(fmt.Sprintf("DelRewardOp: %s from %s: %s", acc.Address, targetDelegation.ValidatorAddress, s.FormatStakingCoin(rewardsCoin))) - - return true - } - - return false - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -// NewGetValRewardOp takes validator commissions rewards. -// Op priority: -// validator - random; -func NewGetValRewardOp(period time.Duration) *SimOperation { - handler := func(s *Simulator) bool { - for _, acc := range s.GetShuffledAccounts() { - if acc.OperatedValidator == nil { - continue - } - - rewardsDec := s.QueryDistrValCommission(acc.OperatedValidator.OperatorAddress) - rewards := rewardsDec.AmountOf(s.stakingDenom).TruncateInt() - rewardsCoin := sdk.NewCoin(s.stakingDenom, rewards) - - // distribute - s.TxDistributionCommission(acc, acc.OperatedValidator.OperatorAddress) - - // update account - s.UpdateAccount(acc) - // update stats - s.counter.Commissions++ - s.counter.CommissionsCollected = s.counter.CommissionsCollected.Add(rewards) - - s.logger.Info(fmt.Sprintf("ValRewardOp: %s for %s: %s", acc.OperatedValidator.OperatorAddress, acc.Address, s.FormatStakingCoin(rewardsCoin))) - - return true - } - - return false - } - - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) -} - -func checkRatioArg(opName, argName string, argValue sdk.Dec) { - errMsgPrefix := fmt.Sprintf("%s: %s: ", opName, argName) - if argValue.LTE(sdk.ZeroDec()) { - panic(fmt.Errorf("%s: LTE 0", errMsgPrefix)) - } - if argValue.GT(sdk.OneDec()) { - panic(fmt.Errorf("%s: GE 1", errMsgPrefix)) - } -} diff --git a/helpers/tests/simulator/sim_ops_create_validator.go b/helpers/tests/simulator/sim_ops_create_validator.go new file mode 100644 index 00000000..4711a8a3 --- /dev/null +++ b/helpers/tests/simulator/sim_ops_create_validator.go @@ -0,0 +1,83 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/stretchr/testify/require" +) + +// NewCreateValidatorOp creates validator for an account which is not an operator yet and has enough coins. +func NewCreateValidatorOp(period time.Duration, maxValidators uint) *SimOperation { + id := "ValidatorOp" + handler := func(s *Simulator) (bool, string) { + if createValidatorOpCheckInput(s, maxValidators) { + return true, "" + } + + targetAcc := createValidatorOpFindTarget(s) + if targetAcc == nil { + return true, "target not found" + } + createValidatorOpHandle(s, targetAcc) + + createdVal := createValidatorOpPost(s, targetAcc) + msg := fmt.Sprintf("%s (%s) created for %s", createdVal.GetAddress(), createdVal.Validator.GetConsAddr(), targetAcc.Address) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func createValidatorOpCheckInput(s *Simulator, maxValidators uint) (stop bool) { + // check limit is reached + return len(s.GetAllValidators()) >= int(maxValidators) +} + +func createValidatorOpFindTarget(s *Simulator) (targetAcc *SimAccount) { + // pick an account without a validator + for _, acc := range s.GetAllAccounts().GetShuffled() { + if acc.IsValOperator() { + continue + } + + // check balance + if acc.Coins.AmountOf(s.stakingDenom).LT(s.minSelfDelegationCoin.Amount) { + continue + } + + targetAcc = acc + break + } + + return +} + +func createValidatorOpHandle(s *Simulator, targetAcc *SimAccount) { + // define commissions + comRate, err := sdk.NewDecFromStr("0.100000000000000000") + require.NoError(s.t, err) + + comMaxRate, err := sdk.NewDecFromStr("0.200000000000000000") + require.NoError(s.t, err) + + comMaxChangeRate, err := sdk.NewDecFromStr("0.010000000000000000") + require.NoError(s.t, err) + + // create a new validator with min self-delegation + s.TxStakeCreateValidator(targetAcc, staking.NewCommissionRates(comRate, comMaxRate, comMaxChangeRate)) + s.beginBlock() + s.endBlock() +} + +func createValidatorOpPost(s *Simulator, targetAcc *SimAccount) (createdVal *SimValidator) { + // update account + validator := s.QueryStakeValidator(sdk.ValAddress(targetAcc.Address)) + s.UpdateAccount(targetAcc) + targetAcc.OperatedValidator = NewSimValidator(validator) + + return targetAcc.OperatedValidator +} diff --git a/helpers/tests/simulator/sim_ops_delegate.go b/helpers/tests/simulator/sim_ops_delegate.go new file mode 100644 index 00000000..ac138aed --- /dev/null +++ b/helpers/tests/simulator/sim_ops_delegate.go @@ -0,0 +1,156 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +// NewDelegateBondingOp picks a validator and searches for an account to delegate bonding tokens. +// SelfStake increment is allowed. +// Delegation amount = current account balance * {delegateRatio}. +// Delegation is allowed if ratio (current staking bonding pools supply / total bonding tokens supply) < {maxBondingRatio}. +// Op priorities: +// validator: +// - bonded; +// - lowest bonding tokens amount; +// account: +// - highest bonding tokens balance; +// - enough coins; +func NewDelegateBondingOp(period time.Duration, delegateRatio, maxBondingRatio sdk.Dec) *SimOperation { + id := "DelegateBondingOp" + checkRatioArg(id, "delegateRatio", delegateRatio) + checkRatioArg(id, "maxBondingRatio", maxBondingRatio) + + handler := func(s *Simulator) (bool, string) { + if delegateOpCheckInput(s, true, maxBondingRatio) { + return true, "" + } + + targetVal, targetAcc, delCoin := delegateOpFindTarget(s, true, delegateRatio) + if targetVal == nil || targetAcc == nil { + return false, "target not found" + } + + if delegateOpHandle(s, targetVal, targetAcc, delCoin) { + return false, fmt.Sprintf("DelegateBondingOp: %s: overflow", targetVal.GetAddress()) + } + + delegateOpPost(s, targetVal, targetAcc, true) + msg := fmt.Sprintf("%s: %s -> %s", targetAcc.Address, s.FormatCoin(delCoin), targetVal.GetAddress()) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +// NewDelegateLPOp picks a validator and searches for an account to delegate LP tokens. +// Delegation amount = current account balance * {delegateRatio}. +// Delegation is allowed if ratio (current staking LP pool supply / total LP tokens supply) < {maxBondingRatio}. +// Op priorities: +// validator: +// - bonded; +// - lowest LP tokens amount; +// account: +// - highest LP tokens balance; +// - enough coins; +func NewDelegateLPOp(period time.Duration, delegateRatio, maxBondingRatio sdk.Dec) *SimOperation { + id := "DelegateLPOp" + checkRatioArg(id, "delegateRatio", delegateRatio) + checkRatioArg(id, "maxBondingRatio", maxBondingRatio) + + handler := func(s *Simulator) (bool, string) { + if delegateOpCheckInput(s, false, maxBondingRatio) { + return true, "" + } + + targetVal, targetAcc, delCoin := delegateOpFindTarget(s, false, delegateRatio) + if targetVal == nil || targetAcc == nil { + return false, "target not found" + } + + if overflow := delegateOpHandle(s, targetVal, targetAcc, delCoin); overflow { + require.False(s.t, overflow) + } + + delegateOpPost(s, targetVal, targetAcc, false) + msg := fmt.Sprintf("%s: %s -> %s", targetAcc.Address, s.FormatCoin(delCoin), targetVal.GetAddress()) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func delegateOpCheckInput(s *Simulator, bondingD bool, maxRatio sdk.Dec) (stop bool) { + pool := s.QueryStakePools() + + var stakingSupply, totalSupply sdk.Int + if bondingD { + stakingSupply = pool.BondedTokens.Add(pool.NotBondedTokens) + totalSupply = s.QuerySupplyTotal().AmountOf(s.stakingDenom) + } else { + stakingSupply = pool.LiquidityTokens + totalSupply = s.QuerySupplyTotal().AmountOf(s.lpDenom) + } + + // check staking pool total supply to all tokens supply ratio + curRatio := stakingSupply.ToDec().Quo(totalSupply.ToDec()) + + return curRatio.GT(maxRatio) +} + +func delegateOpFindTarget(s *Simulator, bondingD bool, delegateRatio sdk.Dec) (targetVal *SimValidator, targetAcc *SimAccount, delCoin sdk.Coin) { + denom := s.stakingDenom + if !bondingD { + denom = s.lpDenom + } + + // pick a bonded validator with the lowest tokens amount + validators := s.GetValidators(true, false, false).GetSortedByTokens(bondingD, false) + if len(validators) == 0 { + return + } + targetVal = validators[0] + + // pick an account with max tokens + var delAmt sdk.Int + for _, acc := range s.GetAllAccounts().GetSortedByBalance(denom, true) { + // estimate delegation amount + accCoinAmtDec := sdk.NewDecFromInt(acc.Coins.AmountOf(denom)) + delAmt = accCoinAmtDec.Mul(delegateRatio).TruncateInt() + if delAmt.IsZero() { + continue + } + + targetAcc = acc + delCoin = sdk.NewCoin(denom, delAmt) + } + + return +} + +func delegateOpHandle(s *Simulator, targetVal *SimValidator, targetAcc *SimAccount, delCoin sdk.Coin) (stop bool) { + overflow := s.TxStakeDelegate(targetAcc, targetVal, delCoin) + if overflow { + stop = true + } + + return +} + +func delegateOpPost(s *Simulator, targetVal *SimValidator, targetAcc *SimAccount, bondingD bool) { + // update account + s.UpdateAccount(targetAcc) + // update validator + s.UpdateValidator(targetVal) + // update stats + if bondingD { + s.counter.BDelegations++ + } else { + s.counter.LPDelegations++ + } +} diff --git a/helpers/tests/simulator/sim_ops_redelegate.go b/helpers/tests/simulator/sim_ops_redelegate.go new file mode 100644 index 00000000..03f62115 --- /dev/null +++ b/helpers/tests/simulator/sim_ops_redelegate.go @@ -0,0 +1,172 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewRedelegateBondingOp picks a validator and redelegate bonding tokens to an other validator. +// Redelegation amount = current account delegation amount * {redelegateRatio}. +// Op priorities: +// dstValidator: +// - bonded; +// - lowest bonding tokens amount; +// srcValidator - highest account delegation bonding shares; +// account: +// - random; +// - has no active redelegations with srcValidator and dstValidator; +// - has enough bonding coins; +// - not a dstValidator owner; +func NewRedelegateBondingOp(period time.Duration, redelegateRatio sdk.Dec) *SimOperation { + id := "RedelegateBondingOp" + checkRatioArg(id, "redelegateRatio", redelegateRatio) + + handler := func(s *Simulator) (bool, string) { + targetAcc, srcValidator, dstValidator, rdCoin := redelegateOpFindTarget(s, true, redelegateRatio) + if srcValidator == nil || dstValidator == nil { + return false, "target not found" + } + redelegateOpHandle(s, targetAcc, srcValidator, dstValidator, rdCoin) + + redelegateOpPost(s, targetAcc, srcValidator, dstValidator, true) + msg := fmt.Sprintf("%s: %s -> %s -> %s", targetAcc.Address, srcValidator.GetAddress(), s.FormatCoin(rdCoin), dstValidator.GetAddress()) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +// NewRedelegateLPOp picks a validator and redelegate LP tokens to an other validator. +// Redelegation amount = current account delegation amount * {redelegateRatio}. +// Op priorities: +// dstValidator: +// - bonded; +// - lowest LP tokens amount; +// srcValidator - highest account delegation LP shares; +// account: +// - random; +// - has no active redelegations with srcValidator and dstValidator; +// - has enough LP coins; +// - not a dstValidator owner; +func NewRedelegateLPOp(period time.Duration, redelegateRatio sdk.Dec) *SimOperation { + id := "RedelegateLPOp" + checkRatioArg(id, "redelegateRatio", redelegateRatio) + + handler := func(s *Simulator) (bool, string) { + targetAcc, srcValidator, dstValidator, rdCoin := redelegateOpFindTarget(s, false, redelegateRatio) + if srcValidator == nil || dstValidator == nil { + return false, "target not found" + } + redelegateOpHandle(s, targetAcc, srcValidator, dstValidator, rdCoin) + + redelegateOpPost(s, targetAcc, srcValidator, dstValidator, false) + msg := fmt.Sprintf("%s: %s -> %s -> %s", targetAcc.Address, srcValidator.GetAddress(), s.FormatCoin(rdCoin), dstValidator.GetAddress()) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func redelegateOpFindTarget(s *Simulator, bondingRD bool, rdRatio sdk.Dec) (targetAcc *SimAccount, srcValidator, dstValidator *SimValidator, rdCoin sdk.Coin) { + denom := s.stakingDenom + if !bondingRD { + denom = s.lpDenom + } + + // pick a bonded dstValidator with the lowest tokens amount + validators := s.GetValidators(true, false, false).GetSortedByTokens(bondingRD, false) + if len(validators) == 0 { + return + } + dstValidator = validators[0] + + rdInProgress := func(accAddr sdk.AccAddress, srcValAddr, dstValAddr sdk.ValAddress) bool { + for _, rd := range s.QueryStakeRedelegations(accAddr, sdk.ValAddress{}, sdk.ValAddress{}) { + if rd.ValidatorSrcAddress.Equals(srcValAddr) || rd.ValidatorDstAddress.Equals(srcValAddr) { + return true + } + + if rd.ValidatorSrcAddress.Equals(dstValAddr) || rd.ValidatorDstAddress.Equals(dstValAddr) { + return true + } + } + return false + } + + // pick a target account + for _, acc := range s.GetAllAccounts().GetShuffled() { + accValAddr := sdk.ValAddress{} + if acc.IsValOperator() { + accValAddr = acc.OperatedValidator.GetAddress() + } + + // check not redelegating to the account owned validator + if dstValidator.GetAddress().Equals(accValAddr) { + continue + } + + // pick a delegation with the highest share + for _, delegation := range acc.GetSortedDelegations(bondingRD, true) { + srcValidatorApplicant := validators.GetByAddress(delegation.ValidatorAddress) + + // check if applicant was found (that validator can be unbonded by now) + if srcValidatorApplicant == nil { + continue + } + + // check not the one picked above + if srcValidatorApplicant.GetAddress().Equals(dstValidator.GetAddress()) { + continue + } + + // check not redelegating from the account owned validator + if srcValidatorApplicant.GetAddress().Equals(accValAddr) { + continue + } + + // check if an other redelegation is in progress for the selected account + if rdInProgress(acc.Address, srcValidatorApplicant.GetAddress(), dstValidator.GetAddress()) { + continue + } + + // estimate redelegation amount + rdAmtDec := sdk.NewDecFromInt(delegation.BondingBalance.Amount) + if !bondingRD { + rdAmtDec = sdk.NewDecFromInt(delegation.LPBalance.Amount) + } + rdAmt := rdAmtDec.Mul(rdRatio).TruncateInt() + if rdAmt.IsZero() { + continue + } + + targetAcc = acc + srcValidator = srcValidatorApplicant + rdCoin = sdk.NewCoin(denom, rdAmt) + return + } + } + + return +} + +func redelegateOpHandle(s *Simulator, targetAcc *SimAccount, srcValidator, dstValidator *SimValidator, rdCoin sdk.Coin) { + s.TxStakeRedelegate(targetAcc, srcValidator.GetAddress(), dstValidator.GetAddress(), rdCoin) +} + +func redelegateOpPost(s *Simulator, targetAcc *SimAccount, srcValidator, dstValidator *SimValidator, bondingRD bool) { + // update validators + s.UpdateValidator(srcValidator) + s.UpdateValidator(dstValidator) + // update account + s.UpdateAccount(targetAcc) + // update stats + if bondingRD { + s.counter.BRedelegations++ + } else { + s.counter.LPRedelegations++ + } +} diff --git a/helpers/tests/simulator/sim_ops_rewards_delegator.go b/helpers/tests/simulator/sim_ops_rewards_delegator.go new file mode 100644 index 00000000..f75f8821 --- /dev/null +++ b/helpers/tests/simulator/sim_ops_rewards_delegator.go @@ -0,0 +1,78 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewGetDelegatorRewardOp takes delegator rewards. +// Op priority: +// account; +// - random; +// - has delegations; +// validator +// - random account delegation; +// - rewards are not locked; +func NewGetDelegatorRewardOp(period time.Duration) *SimOperation { + id := "DelegatorRewardOp" + + handler := func(s *Simulator) (bool, string) { + targetAcc, targetVal, rewardCoins := getDelegatorRewardOpFindTarget(s) + if targetAcc == nil || targetVal == nil { + return false, "target not found" + } + getDelegatorRewardOpHandle(s, targetAcc, targetVal) + + getDelegatorRewardOpPost(s, targetAcc, rewardCoins) + msg := fmt.Sprintf("%s from %s: %s", targetAcc.Address, targetVal.GetAddress(), s.FormatCoins(rewardCoins)) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func getDelegatorRewardOpFindTarget(s *Simulator) (targetAcc *SimAccount, targetVal *SimValidator, rewardCoins sdk.Coins) { + rewardCoins = sdk.NewCoins() + validators := s.GetAllValidators() + + for _, acc := range s.GetAllAccounts().GetShuffled() { + for _, delegation := range acc.GetShuffledDelegations(true) { + validator := validators.GetByAddress(delegation.ValidatorAddress) + if validator.RewardsLocked() { + continue + } + + // estimate reward coins + for _, decCoin := range s.QueryDistDelReward(acc.Address, delegation.ValidatorAddress) { + coin, _ := decCoin.TruncateDecimal() + rewardCoins = rewardCoins.Add(coin) + } + + // check there are some rewards + if rewardCoins.Empty() { + continue + } + + targetAcc = acc + targetVal = validator + return + } + } + + return +} + +func getDelegatorRewardOpHandle(s *Simulator, targetAcc *SimAccount, targetVal *SimValidator) { + s.TxDistDelegatorRewards(targetAcc, targetVal.GetAddress()) +} + +func getDelegatorRewardOpPost(s *Simulator, targetAcc *SimAccount, rewardCoins sdk.Coins) { + // update account + s.UpdateAccount(targetAcc) + // update stats + s.counter.Rewards++ + s.counter.RewardsCollected = s.counter.RewardsCollected.Add(rewardCoins...) +} diff --git a/helpers/tests/simulator/sim_ops_rewards_lock.go b/helpers/tests/simulator/sim_ops_rewards_lock.go new file mode 100644 index 00000000..f97cbcf2 --- /dev/null +++ b/helpers/tests/simulator/sim_ops_rewards_lock.go @@ -0,0 +1,76 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewGetValidatorRewardOp takes validator commissions rewards. +// Op priority: +// validator - random; +func NewLockValidatorRewardsOp(period time.Duration, maxLockedRatio sdk.Dec) *SimOperation { + id := "LockValidatorRewardsOp" + + handler := func(s *Simulator) (bool, string) { + if lockValidatorRewardsOpCheckInput(s, maxLockedRatio) { + return true, "" + } + + targetAcc, targetVal := lockValidatorRewardsOpFindTarget(s) + if targetAcc == nil || targetVal == nil { + return false, "target not found" + } + lockValidatorRewardsOpHandle(s, targetAcc, targetVal) + + lockValidatorRewardsOpPost(s, targetVal) + msg := fmt.Sprintf("%s for %s", targetVal.GetAddress(), targetAcc.Address) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func lockValidatorRewardsOpCheckInput(s *Simulator, maxRatio sdk.Dec) (stop bool) { + // check current locked ratio + validators := s.GetValidators(true, false, false) + locked := len(validators.GetLocked()) + total := len(validators) + + curRatio := sdk.NewDec(int64(locked)).Quo(sdk.NewDec(int64(total))) + if curRatio.GT(maxRatio) { + stop = true + return + } + + return +} + +func lockValidatorRewardsOpFindTarget(s *Simulator) (targetAcc *SimAccount, targetVal *SimValidator) { + for _, val := range s.GetAllValidators().GetShuffled() { + if val.RewardsLocked() { + continue + } + + targetAcc = s.GetAllAccounts().GetByAddress(val.GetOperatorAddress()) + targetVal = val + break + } + + return +} + +func lockValidatorRewardsOpHandle(s *Simulator, targetAcc *SimAccount, targetVal *SimValidator) { + // lock and disable auto-renewal + s.TxDistLockRewards(targetAcc, targetVal.GetAddress()) + s.TxDistDisableAutoRenewal(targetAcc, targetVal.GetAddress()) +} + +func lockValidatorRewardsOpPost(s *Simulator, targetVal *SimValidator) { + // update validator + s.UpdateValidator(targetVal) + // update stats + s.counter.LockedRewards++ +} diff --git a/helpers/tests/simulator/sim_ops_rewards_validator.go b/helpers/tests/simulator/sim_ops_rewards_validator.go new file mode 100644 index 00000000..1cfcb9e3 --- /dev/null +++ b/helpers/tests/simulator/sim_ops_rewards_validator.go @@ -0,0 +1,73 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewGetValidatorRewardOp takes validator commissions rewards. +// Op priority: +// validator - random; +func NewGetValidatorRewardOp(period time.Duration) *SimOperation { + id := "ValidatorRewardOp" + + handler := func(s *Simulator) (bool, string) { + targetAcc, targetVal, rewardCoins := getValidatorRewardOpFindTarget(s) + if targetAcc == nil || targetVal == nil { + return false, "target not found" + } + + if getValidatorRewardOpHandle(s, targetAcc, targetVal) { + msg := fmt.Sprintf("can't withdraw %s validator commission", targetVal.GetAddress()) + return false, msg + } + + getValidatorRewardOpPost(s, targetAcc, rewardCoins) + msg := fmt.Sprintf("%s for %s: %s", targetVal.GetAddress(), targetAcc.Address, s.FormatCoins(rewardCoins)) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func getValidatorRewardOpFindTarget(s *Simulator) (targetAcc *SimAccount, targetVal *SimValidator, rewardCoins sdk.Coins) { + rewardCoins = sdk.NewCoins() + + for _, val := range s.GetAllValidators().GetShuffled() { + // check there are some commission rewards available + decCoins := s.QueryDistValCommission(val.GetAddress()) + if decCoins.Empty() { + continue + } + + // estimate reward coins + for _, decCoin := range decCoins { + coin, _ := decCoin.TruncateDecimal() + rewardCoins = rewardCoins.Add(coin) + } + + targetVal = val + targetAcc = s.GetAllAccounts().GetByAddress(targetVal.GetOperatorAddress()) + } + + return +} + +func getValidatorRewardOpHandle(s *Simulator, targetAcc *SimAccount, targetVal *SimValidator) (stop bool) { + if s.TxDistValidatorCommission(targetAcc, targetVal.GetAddress()) { + stop = true + } + + return +} + +func getValidatorRewardOpPost(s *Simulator, targetAcc *SimAccount, rewardCoins sdk.Coins) { + // update account + s.UpdateAccount(targetAcc) + // update stats + s.counter.Commissions++ + s.counter.CommissionsCollected = s.counter.CommissionsCollected.Add(rewardCoins...) +} diff --git a/helpers/tests/simulator/sim_ops_undelegate.go b/helpers/tests/simulator/sim_ops_undelegate.go new file mode 100644 index 00000000..ed41b3bc --- /dev/null +++ b/helpers/tests/simulator/sim_ops_undelegate.go @@ -0,0 +1,134 @@ +package simulator + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewUndelegateBondingOp picks a validator and undelegates bonding tokens. +// Undelegation amount = current account delegation amount * {undelegateRatio}. +// Op priorities: +// validator - highest bonding tokens amount (all statuses); +// account: +// - random; +// - has a validators bonding delegation; +// - not a validator owner; +func NewUndelegateBondingOp(period time.Duration, undelegateRatio sdk.Dec) *SimOperation { + id := "UndelegateBondingOp" + checkRatioArg(id, "undelegateRatio", undelegateRatio) + + handler := func(s *Simulator) (bool, string) { + targetAcc, targetVal, udCoin := undelegateOpFindTarget(s, true, undelegateRatio) + if targetAcc == nil || targetVal == nil { + return false, "target not found" + } + undelegateOpHandle(s, targetAcc, targetVal, udCoin) + + undelegateOpPost(s, targetAcc, targetVal, true) + msg := fmt.Sprintf("%s: %s -> %s", targetAcc.Address, targetVal.GetAddress(), s.FormatCoin(udCoin)) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +// NewUndelegateLPOp picks a validator and undelegates LP tokens. +// Undelegation amount = current account delegation amount * {undelegateRatio}. +// Op priorities: +// validator - highest LP tokens amount (all statuses); +// account: +// - random; +// - has a validators LP delegation; +// - not a validator owner; +func NewUndelegateLPOp(period time.Duration, undelegateRatio sdk.Dec) *SimOperation { + id := "UndelegateLPOp" + checkRatioArg(id, "undelegateRatio", undelegateRatio) + + handler := func(s *Simulator) (bool, string) { + targetAcc, targetVal, udCoin := undelegateOpFindTarget(s, false, undelegateRatio) + if targetAcc == nil || targetVal == nil { + return false, "target not found" + } + undelegateOpHandle(s, targetAcc, targetVal, udCoin) + + undelegateOpPost(s, targetAcc, targetVal, false) + msg := fmt.Sprintf("%s: %s -> %s", targetAcc.Address, targetVal.GetAddress(), s.FormatCoin(udCoin)) + + return true, msg + } + + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) +} + +func undelegateOpFindTarget(s *Simulator, bondingUD bool, udRatio sdk.Dec) (targetAcc *SimAccount, targetVal *SimValidator, udCoin sdk.Coin) { + denom := s.stakingDenom + if !bondingUD { + denom = s.lpDenom + } + + // pick a validator with the highest tokens amount (all statuses) + for _, val := range s.GetAllValidators().GetSortedByTokens(bondingUD, true) { + // pick a random account + for _, acc := range s.GetAllAccounts().GetShuffled() { + accValAddr := sdk.ValAddress{} + if acc.IsValOperator() { + accValAddr = acc.OperatedValidator.GetAddress() + } + + // pick a corresponding delegation (targetValidator) + for _, delegation := range acc.Delegations { + // check if account did delegate to the selected validator + if !val.GetAddress().Equals(delegation.ValidatorAddress) { + continue + } + + // check not undelegating from the account owned validator + if accValAddr.Equals(val.GetAddress()) { + continue + } + + // estimate undelegation amount + udAmtDec := sdk.NewDecFromInt(delegation.BondingBalance.Amount) + if !bondingUD { + udAmtDec = sdk.NewDecFromInt(delegation.LPBalance.Amount) + } + + udAmt := udAmtDec.Mul(udRatio).TruncateInt() + if udAmt.IsZero() { + continue + } + + targetAcc = acc + targetVal = val + udCoin = sdk.NewCoin(denom, udAmt) + return + } + } + } + + return +} + +func undelegateOpHandle(s *Simulator, targetAcc *SimAccount, targetVal *SimValidator, udCoin sdk.Coin) { + s.TxStakeUndelegate(targetAcc, targetVal.GetAddress(), udCoin) +} + +func undelegateOpPost(s *Simulator, targetAcc *SimAccount, targetVal *SimValidator, bondingUD bool) { + // update validator + s.UpdateValidator(targetVal) + // update account + s.UpdateAccount(targetAcc) + // update stats + if bondingUD { + s.counter.BUndelegations++ + } else { + s.counter.LPUndelegations++ + } + + s.defferQueue.Add(s.prevBlockTime.Add(s.unbondingDur+5*time.Minute), func() { + s.UpdateAccount(targetAcc) + }) +} diff --git a/helpers/tests/simulator/sim_options.go b/helpers/tests/simulator/sim_options.go index 68de2ebd..00e01f97 100644 --- a/helpers/tests/simulator/sim_options.go +++ b/helpers/tests/simulator/sim_options.go @@ -44,7 +44,7 @@ func OperationsOption(ops ...*SimOperation) SimOption { } } -func GenerateWalletAccountsOption(walletsQuantity, poaValidatorsQuantity, tmValidatorQuantity uint, genCoins sdk.Coins) SimOption { +func GenerateWalletAccountsOption(walletsQuantity, poaValidatorsQuantity uint, genCoins sdk.Coins) SimOption { return func(s *Simulator) { for i := uint(0); i < walletsQuantity; i++ { acc := &SimAccount{ @@ -55,9 +55,6 @@ func GenerateWalletAccountsOption(walletsQuantity, poaValidatorsQuantity, tmVali acc.IsPoAValidator = true poaValidatorsQuantity-- } - if tmValidatorQuantity > 0 { - acc.CreateValidator = true - } s.accounts = append(s.accounts, acc) } @@ -70,35 +67,35 @@ func NodeValidatorConfigOption(config SimValidatorConfig) SimOption { } } -func MintParamsOption(params mint.Params) SimOption { +func MintParamsOption(modifier func(state *mint.GenesisState)) SimOption { return func(s *Simulator) { state := mint.GenesisState{} stateBz := s.genesisState[mint.ModuleName] s.cdc.MustUnmarshalJSON(stateBz, &state) - state.Params = params + modifier(&state) s.genesisState[mint.ModuleName] = s.cdc.MustMarshalJSON(state) } } -func StakingParamsOption(params staking.Params) SimOption { +func StakingParamsOption(modifier func(state *staking.GenesisState)) SimOption { return func(s *Simulator) { state := staking.GenesisState{} stateBz := s.genesisState[staking.ModuleName] s.cdc.MustUnmarshalJSON(stateBz, &state) - state.Params = params + modifier(&state) s.genesisState[staking.ModuleName] = s.cdc.MustMarshalJSON(state) } } -func DistributionParamsOption(params distribution.Params) SimOption { +func DistributionParamsOption(modifier func(state *distribution.GenesisState)) SimOption { return func(s *Simulator) { state := distribution.GenesisState{} stateBz := s.genesisState[distribution.ModuleName] s.cdc.MustUnmarshalJSON(stateBz, &state) - state.Params = params + modifier(&state) s.genesisState[distribution.ModuleName] = s.cdc.MustMarshalJSON(state) } } diff --git a/helpers/tests/simulator/sim_queries.go b/helpers/tests/simulator/sim_queries.go index 1e6bbcc6..2602660a 100644 --- a/helpers/tests/simulator/sim_queries.go +++ b/helpers/tests/simulator/sim_queries.go @@ -261,8 +261,8 @@ func (s *Simulator) QueryDistDelRewards(acc sdk.AccAddress) (res distribution.Qu return } -// QueryDistrValCommission queries current validator commission rewards. -func (s *Simulator) QueryDistrValCommission(val sdk.ValAddress) (res distribution.ValidatorAccumulatedCommission) { +// QueryDistValCommission queries current validator commission rewards. +func (s *Simulator) QueryDistValCommission(val sdk.ValAddress) (res distribution.ValidatorAccumulatedCommission) { resp := s.RunQuery( distribution.QueryValidatorCommissionParams{ ValidatorAddress: val, @@ -275,6 +275,20 @@ func (s *Simulator) QueryDistrValCommission(val sdk.ValAddress) (res distributio return res } +// QueryDistLockState queries current validator locked rewards state. +func (s *Simulator) QueryDistLockState(val sdk.ValAddress) (res distribution.QueryLockedRewardsStateResponse) { + resp := s.RunQuery( + distribution.QueryLockedRewardsStateParams{ + ValidatorAddress: val, + }, + "/custom/"+distribution.QuerierRoute+"/"+distribution.QueryLockedRewardsState, + &res, + ) + require.True(s.t, resp.IsOK()) + + return res +} + // QueryDistPool queries supply total supply. func (s *Simulator) QuerySupplyTotal() (res sdk.Coins) { resp := s.RunQuery( diff --git a/helpers/tests/simulator/sim_report.go b/helpers/tests/simulator/sim_report.go index 16f7aedf..2078e42a 100644 --- a/helpers/tests/simulator/sim_report.go +++ b/helpers/tests/simulator/sim_report.go @@ -25,6 +25,7 @@ type SimReportItem struct { // StakingBonded sdk.Int // bonded tokens (staking pool) StakingNotBonded sdk.Int // not bonded tokens (staking pool) + StakingLPs sdk.Int // bonded LP tokens (LPs pool) RedelegationsInProcess int // redelegations in progress // MintMinInflation sdk.Dec // annual min inflation @@ -39,20 +40,24 @@ type SimReportItem struct { // SupplyTotalMain sdk.Int // total supply [main denom] SupplyTotalStaking sdk.Int // total supply [staking denom] + SupplyTotalLP sdk.Int // total supply [LP denom] // StatsBondedRatio sdk.Dec // BondedTokens / TotalSupply ratio [staking denom] + StatsLPRatio sdk.Dec // BondedTokens / TotalSupply ratio [LP denom] // Counters Counter // - formatIntDecimals func(value sdk.Int) string - formatDecDecimals func(value sdk.Dec) string + formatIntDecimals func(value sdk.Int) string + formatDecDecimals func(value sdk.Dec) string + formatCoinsDecimals func(coins sdk.Coins) string } // NewReportOp captures report. func NewReportOp(period time.Duration, debug bool, writers ...SimReportWriter) *SimOperation { + id := "ReportOp" reportItemIdx := 1 - handler := func(s *Simulator) bool { + handler := func(s *Simulator) (bool, string) { // gather the data // simulation @@ -102,6 +107,7 @@ func NewReportOp(period time.Duration, debug bool, writers ...SimReportWriter) * // StakingBonded: stakingPool.BondedTokens, StakingNotBonded: stakingPool.NotBondedTokens, + StakingLPs: stakingPool.LiquidityTokens, RedelegationsInProcess: acitveRedelegations, // MintMinInflation: mintParams.InflationMin, @@ -116,6 +122,7 @@ func NewReportOp(period time.Duration, debug bool, writers ...SimReportWriter) * // SupplyTotalMain: totalSupply.AmountOf(s.mainDenom), SupplyTotalStaking: totalSupply.AmountOf(s.stakingDenom), + SupplyTotalLP: totalSupply.AmountOf(s.lpDenom), // Counters: s.counter, // @@ -125,10 +132,14 @@ func NewReportOp(period time.Duration, debug bool, writers ...SimReportWriter) * formatDecDecimals: func(value sdk.Dec) string { return s.FormatDecDecimals(value, s.mainAmountDecimalsRatio) }, + formatCoinsDecimals: func(coins sdk.Coins) string { + return s.FormatCoins(coins) + }, } // calculate statistics - item.StatsBondedRatio = sdk.NewDecFromInt(item.StakingBonded).Quo(sdk.NewDecFromInt(item.SupplyTotalStaking)) + item.StatsBondedRatio = sdk.NewDecFromInt(item.StakingBonded.Add(item.StakingNotBonded)).Quo(sdk.NewDecFromInt(item.SupplyTotalStaking)) + item.StatsLPRatio = sdk.NewDecFromInt(item.StakingLPs).Quo(sdk.NewDecFromInt(item.SupplyTotalLP)) reportItemIdx++ for _, writer := range writers { @@ -140,10 +151,10 @@ func NewReportOp(period time.Duration, debug bool, writers ...SimReportWriter) * fmt.Println(debugItem.String()) } - return true + return true, "" } - return NewSimOperation(period, NewPeriodicNextExecFn(), handler) + return NewSimOperation(id, period, NewPeriodicNextExecFn(), handler) } type SimReportConsoleWriter struct { @@ -175,15 +186,23 @@ func (w *SimReportConsoleWriter) Write(item SimReportItem) { str.WriteString(fmt.Sprintf(" Dist: HARP: %s\n", item.formatDecDecimals(item.DistHARP))) str.WriteString(fmt.Sprintf(" Supply: TotalMain: %s\n", item.formatIntDecimals(item.SupplyTotalMain))) str.WriteString(fmt.Sprintf(" Supply: TotalStaking: %s\n", item.formatIntDecimals(item.SupplyTotalStaking))) - str.WriteString(fmt.Sprintf(" Stats: Bonded/TotalSupply: %s\n", item.StatsBondedRatio)) + str.WriteString(fmt.Sprintf(" Supply: LPs: %s\n", item.formatIntDecimals(item.SupplyTotalLP))) + str.WriteString(fmt.Sprintf(" Stats: Bonded/TotalSupply [B]: %s\n", item.StatsBondedRatio)) + str.WriteString(fmt.Sprintf(" Stats: Bonded/TotalSupply [LP]: %s\n", item.StatsLPRatio)) str.WriteString(" Counters:\n") - str.WriteString(fmt.Sprintf(" Delegations: %d\n", item.Counters.Delegations)) - str.WriteString(fmt.Sprintf(" Redelegations: %d\n", item.Counters.Redelegations)) - str.WriteString(fmt.Sprintf(" Undelegations: %d\n", item.Counters.Undelegations)) + str.WriteString(" Bonding:\n") + str.WriteString(fmt.Sprintf(" Delegations: %d\n", item.Counters.BDelegations)) + str.WriteString(fmt.Sprintf(" Redelegations: %d\n", item.Counters.BRedelegations)) + str.WriteString(fmt.Sprintf(" Undelegations: %d\n", item.Counters.BUndelegations)) + str.WriteString(" LP:\n") + str.WriteString(fmt.Sprintf(" Delegations: %d\n", item.Counters.LPDelegations)) + str.WriteString(fmt.Sprintf(" Redelegations: %d\n", item.Counters.LPRedelegations)) + str.WriteString(fmt.Sprintf(" Undelegations: %d\n", item.Counters.LPUndelegations)) str.WriteString(fmt.Sprintf(" Rewards: %d\n", item.Counters.Rewards)) - str.WriteString(fmt.Sprintf(" RewardsCollected: %s\n", item.formatIntDecimals(item.Counters.RewardsCollected))) + str.WriteString(fmt.Sprintf(" RewardsCollected: %s\n", item.formatCoinsDecimals(item.Counters.RewardsCollected))) str.WriteString(fmt.Sprintf(" Commissions: %d\n", item.Counters.Commissions)) - str.WriteString(fmt.Sprintf(" CommissionsCollected: %s\n", item.formatIntDecimals(item.Counters.CommissionsCollected))) + str.WriteString(fmt.Sprintf(" CommissionsCollected: %s\n", item.formatCoinsDecimals(item.Counters.CommissionsCollected))) + str.WriteString(fmt.Sprintf(" Locked rewards: %d\n", item.Counters.LockedRewards)) fmt.Println(str.String()) } @@ -200,27 +219,22 @@ func NewSimReportConsoleWriter() *SimReportConsoleWriter { // 1.2.1 years -> 1 year, 2 months and 1 week // 5.30 hours -> 5 hours and 30 minutes func FormatDuration(dur time.Duration) string { - const ( - dayDur = 24 * time.Hour - weekDur = 7 * dayDur - monthDur = 4 * weekDur - yearDur = 12 * monthDur - ) - dur = dur.Round(time.Minute) - years := dur / yearDur - dur -= years * yearDur - months := dur / monthDur - dur -= months * monthDur - weeks := dur / weekDur - dur -= weeks * weekDur + years := dur / Year + dur -= years * Year + months := dur / Month + dur -= months * Month + weeks := dur / Week + dur -= weeks * Week + days := dur / Day + dur -= days * Day hours := dur / time.Hour dur -= hours * time.Hour mins := dur / time.Minute str := strings.Builder{} - str.WriteString(fmt.Sprintf("%d.%d.%d years ", years, months, weeks)) + str.WriteString(fmt.Sprintf("%d years %d months %d weeks %d days ", years, months, weeks, days)) str.WriteString(fmt.Sprintf("%d.%d hours", hours, mins)) return str.String() diff --git a/helpers/tests/simulator/sim_report_csv.go b/helpers/tests/simulator/sim_report_csv.go index 989c207c..c1a08aa7 100644 --- a/helpers/tests/simulator/sim_report_csv.go +++ b/helpers/tests/simulator/sim_report_csv.go @@ -24,6 +24,7 @@ var Headers = []string{ "Validators: Unbonded", "Staking: Bonded", "Staking: NotBonded", + "Staking: LPs", "Staking: ActiveRedelegations", "Mint: MinInflation", "Mint: MaxInflation", @@ -35,14 +36,20 @@ var Headers = []string{ "Dist: HARP", "Supply: Total [main]", "Supply: Total [staking]", - "Stats: Bonded/TotalSupply", - "Counters: Delegations:", - "Counters: Redelegations:", - "Counters: Undelegations:", - "Counters: Rewards:", - "Counters: RewardsCollected:", - "Counters: Commissions:", - "Counters: CommissionsCollected:", + "Supply: Total [LP]", + "Stats: Bonded/TotalSupply [bonding]", + "Stats: Bonded/TotalSupply [LPs]", + "Counters: Bonding: Delegations", + "Counters: Bonding: Redelegations", + "Counters: Bonding: Undelegations", + "Counters: LP: Delegations", + "Counters: LP: Redelegations", + "Counters: LP: Undelegations", + "Counters: Rewards", + "Counters: RewardsCollected", + "Counters: Commissions", + "Counters: CommissionsCollected", + "Counters: LockedRewards", } func NewSimReportCSVWriter(t *testing.T, filePath string) (*SimReportCSVWriter, CSVWriterClose) { @@ -74,6 +81,7 @@ func (w *SimReportCSVWriter) Write(item SimReportItem) { strconv.Itoa(item.ValidatorsUnbonded), item.StakingBonded.String(), item.StakingNotBonded.String(), + item.StakingLPs.String(), strconv.Itoa(item.RedelegationsInProcess), item.MintMinInflation.String(), item.MintMaxInflation.String(), @@ -85,14 +93,20 @@ func (w *SimReportCSVWriter) Write(item SimReportItem) { item.DistHARP.String(), item.SupplyTotalMain.String(), item.SupplyTotalStaking.String(), + item.SupplyTotalLP.String(), item.StatsBondedRatio.String(), - strconv.FormatInt(item.Counters.Delegations, 10), - strconv.FormatInt(item.Counters.Redelegations, 10), - strconv.FormatInt(item.Counters.Undelegations, 10), + item.StatsLPRatio.String(), + strconv.FormatInt(item.Counters.BDelegations, 10), + strconv.FormatInt(item.Counters.BRedelegations, 10), + strconv.FormatInt(item.Counters.BUndelegations, 10), + strconv.FormatInt(item.Counters.LPDelegations, 10), + strconv.FormatInt(item.Counters.LPRedelegations, 10), + strconv.FormatInt(item.Counters.LPUndelegations, 10), strconv.FormatInt(item.Counters.Rewards, 10), item.Counters.RewardsCollected.String(), strconv.FormatInt(item.Counters.Commissions, 10), item.Counters.CommissionsCollected.String(), + strconv.FormatInt(item.Counters.LockedRewards, 10), } _ = w.writer.Write(data) diff --git a/helpers/tests/simulator/sim_report_debug.go b/helpers/tests/simulator/sim_report_debug.go index acac0166..bc34e759 100644 --- a/helpers/tests/simulator/sim_report_debug.go +++ b/helpers/tests/simulator/sim_report_debug.go @@ -78,7 +78,7 @@ func BuildDebugReportItem(s *Simulator) SimDebugReportItem { }) } - for _, acc := range s.GetAccountsSortedByBalance(true) { + for _, acc := range s.GetAllAccounts().GetSortedByBalance(s.stakingDenom, true) { r.Accounts = append(r.Accounts, DebugAccoutData{ Address: acc.Address, MainCoinBalance: acc.Coins.AmountOf(s.mainDenom), diff --git a/helpers/tests/simulator/sim_txs.go b/helpers/tests/simulator/sim_txs.go index 277cbd95..d0f4acbd 100644 --- a/helpers/tests/simulator/sim_txs.go +++ b/helpers/tests/simulator/sim_txs.go @@ -47,6 +47,20 @@ func (s *Simulator) GenTx(msg sdk.Msg, simAcc *SimAccount) auth.StdTx { return s.GenTxAdvanced(msg, acc.GetAccountNumber(), acc.GetSequence(), simAcc.PublicKey, simAcc.PrivateKey) } +// CheckTx checks Tx and parses the result. +func (s *Simulator) CheckTx(tx auth.StdTx, responseValue interface{}) error { + _, res, err := s.app.Check(tx) + if err != nil { + return err + } + + if responseValue != nil { + s.cdc.MustUnmarshalJSON(res.Data, responseValue) + } + + return nil +} + // DeliverTx delivers Tx and parses the result. func (s *Simulator) DeliverTx(tx auth.StdTx, responseValue interface{}) { s.beginBlock() @@ -61,37 +75,48 @@ func (s *Simulator) DeliverTx(tx auth.StdTx, responseValue interface{}) { } } -// TxStakingCreateValidator creates a new validator operated by simAcc with min self delegation. -func (s *Simulator) TxStakingCreateValidator(simAcc *SimAccount, commissions staking.CommissionRates) { +// TxStakeCreateValidator creates a new validator operated by simAcc with min self delegation. +func (s *Simulator) TxStakeCreateValidator(simAcc *SimAccount, commissions staking.CommissionRates) { require.NotNil(s.t, simAcc) - selfDelegation := sdk.NewCoin(s.stakingDenom, s.minSelfDelegationLvl) msg := staking.NewMsgCreateValidator( simAcc.Address.Bytes(), simAcc.PublicKey, - selfDelegation, + s.minSelfDelegationCoin, staking.NewDescription(simAcc.Address.String(), "", "", "", ""), commissions, - s.minSelfDelegationLvl, + s.minSelfDelegationCoin.Amount, ) s.DeliverTx(s.GenTx(msg, simAcc), nil) } -// TxStakingDelegate delegates amount from delegator to validator. -func (s *Simulator) TxStakingDelegate(simAcc *SimAccount, validator *staking.Validator, amount sdk.Coin) { +// TxStakeDelegate delegates amount from delegator to validator. +func (s *Simulator) TxStakeDelegate(simAcc *SimAccount, simVal *SimValidator, amount sdk.Coin) (delegationsOverflow bool) { require.NotNil(s.t, simAcc) - require.NotNil(s.t, validator) + require.NotNil(s.t, simVal) msg := staking.MsgDelegate{ DelegatorAddress: simAcc.Address, - ValidatorAddress: validator.OperatorAddress, + ValidatorAddress: simVal.GetAddress(), Amount: amount, } - s.DeliverTx(s.GenTx(msg, simAcc), nil) + tx := s.GenTx(msg, simAcc) + + if err := s.CheckTx(tx, nil); err != nil { + if staking.ErrMaxDelegationsLimit.Is(err) { + delegationsOverflow = true + return + } + require.NoError(s.t, err) + } + + s.DeliverTx(tx, nil) + + return } -// TxStakingRedelegate redelegates amount from one validator to other. -func (s *Simulator) TxStakingRedelegate(simAcc *SimAccount, valSrc, valDst sdk.ValAddress, amount sdk.Coin) { +// TxStakeRedelegate redelegates amount from one validator to other. +func (s *Simulator) TxStakeRedelegate(simAcc *SimAccount, valSrc, valDst sdk.ValAddress, amount sdk.Coin) { require.NotNil(s.t, simAcc) msg := staking.MsgBeginRedelegate{ @@ -103,8 +128,8 @@ func (s *Simulator) TxStakingRedelegate(simAcc *SimAccount, valSrc, valDst sdk.V s.DeliverTx(s.GenTx(msg, simAcc), nil) } -// TxStakingUndelegate undelegates amount from validator. -func (s *Simulator) TxStakingUndelegate(simAcc *SimAccount, validatorAddr sdk.ValAddress, amount sdk.Coin) { +// TxStakeUndelegate undelegates amount from validator. +func (s *Simulator) TxStakeUndelegate(simAcc *SimAccount, validatorAddr sdk.ValAddress, amount sdk.Coin) { require.NotNil(s.t, simAcc) msg := staking.MsgUndelegate{ @@ -115,8 +140,8 @@ func (s *Simulator) TxStakingUndelegate(simAcc *SimAccount, validatorAddr sdk.Va s.DeliverTx(s.GenTx(msg, simAcc), nil) } -// TxDistributionReward taking reward. -func (s *Simulator) TxDistributionReward(simAcc *SimAccount, validatorAddr sdk.ValAddress) { +// TxDistDelegatorRewards withdraws delegator rewards. +func (s *Simulator) TxDistDelegatorRewards(simAcc *SimAccount, validatorAddr sdk.ValAddress) { require.NotNil(s.t, simAcc) msg := distribution.MsgWithdrawDelegatorReward{ @@ -127,13 +152,48 @@ func (s *Simulator) TxDistributionReward(simAcc *SimAccount, validatorAddr sdk.V s.DeliverTx(s.GenTx(msg, simAcc), nil) } -// TxDistributionReward taking reward. -func (s *Simulator) TxDistributionCommission(simAcc *SimAccount, validatorAddr sdk.ValAddress) { +// TxDistValidatorCommission withdraws validator commission rewards. +func (s *Simulator) TxDistValidatorCommission(simAcc *SimAccount, validatorAddr sdk.ValAddress) (noCommission bool) { require.NotNil(s.t, simAcc) msg := distribution.MsgWithdrawValidatorCommission{ ValidatorAddress: validatorAddr, } + tx := s.GenTx(msg, simAcc) + + if err := s.CheckTx(tx, nil); err != nil { + if distribution.ErrNoValidatorCommission.Is(err) { + noCommission = true + return + } + require.NoError(s.t, err) + } + + s.DeliverTx(s.GenTx(msg, simAcc), nil) + + return +} + +// TxDistLockRewards locks validator rewards. +func (s *Simulator) TxDistLockRewards(simAcc *SimAccount, validatorAddr sdk.ValAddress) { + require.NotNil(s.t, simAcc) + + msg := distribution.MsgLockValidatorRewards{ + ValidatorAddress: validatorAddr, + SenderAddress: simAcc.Address, + } + + s.DeliverTx(s.GenTx(msg, simAcc), nil) +} + +// TxDistDisableAutoRenewal disables validator rewards lock auto-renewal. +func (s *Simulator) TxDistDisableAutoRenewal(simAcc *SimAccount, validatorAddr sdk.ValAddress) { + require.NotNil(s.t, simAcc) + + msg := distribution.MsgDisableLockedRewardsAutoRenewal{ + ValidatorAddress: validatorAddr, + SenderAddress: simAcc.Address, + } s.DeliverTx(s.GenTx(msg, simAcc), nil) } diff --git a/helpers/tests/simulator/sim_validator.go b/helpers/tests/simulator/sim_validator.go index 2d0d017c..01756581 100644 --- a/helpers/tests/simulator/sim_validator.go +++ b/helpers/tests/simulator/sim_validator.go @@ -1,7 +1,138 @@ package simulator -import "github.com/cosmos/cosmos-sdk/x/staking" +import ( + "math/rand" + "sort" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/stretchr/testify/require" +) type SimValidatorConfig struct { Commission staking.CommissionRates } + +type SimValidator struct { + Validator staking.Validator + RewardsLockedUntil time.Time +} + +// GetAddress returns validator address. +func (v *SimValidator) GetAddress() sdk.ValAddress { + return v.Validator.OperatorAddress +} + +// GetOperatorAddress returns validator operator address. +func (v *SimValidator) GetOperatorAddress() sdk.AccAddress { + return sdk.AccAddress(v.Validator.OperatorAddress) +} + +// GetStatus returns validator bonding status. +func (v *SimValidator) GetStatus() sdk.BondStatus { + return v.Validator.Status +} + +// RewardsLocked check if validator rewards are locked. +func (v *SimValidator) RewardsLocked() bool { + return !v.RewardsLockedUntil.IsZero() +} + +// LockRewards updates locked rewards state. +func (v *SimValidator) LockRewards(until time.Time) { + v.RewardsLockedUntil = until +} + +// UnlockRewards updates locked rewards state. +func (v *SimValidator) UnlockRewards() { + v.RewardsLockedUntil = time.Time{} +} + +// NewSimValidator createes a new SimValidator object. +func NewSimValidator(val staking.Validator) *SimValidator { + return &SimValidator{ + Validator: val, + RewardsLockedUntil: time.Time{}, + } +} + +type SimValidators []*SimValidator + +// GetByAddress returns validator by address. +func (v SimValidators) GetByAddress(address sdk.ValAddress) *SimValidator { + for _, val := range v { + if val.GetAddress().Equals(address) { + return val + } + } + + return nil +} + +// GetShuffled returns random sorted validators list. +func (v SimValidators) GetShuffled() SimValidators { + tmpVal := make(SimValidators, len(v)) + copy(tmpVal, v) + + for i := range tmpVal { + j := rand.Intn(i + 1) + tmpVal[i], tmpVal[j] = tmpVal[j], tmpVal[i] + } + + return tmpVal +} + +// GetSortedByTokens returns validators list sorted by tokens amount. +func (v SimValidators) GetSortedByTokens(bondingTokens, desc bool) SimValidators { + tmpVals := make(SimValidators, len(v)) + copy(tmpVals, v) + + sort.Slice(tmpVals, func(i, j int) bool { + if bondingTokens { + if tmpVals[i].Validator.GetBondingTokens().GT(tmpVals[j].Validator.GetBondingTokens()) { + return desc + } + return !desc + } + + if tmpVals[i].Validator.GetLPTokens().GT(tmpVals[j].Validator.GetLPTokens()) { + return desc + } + return !desc + }) + + return tmpVals +} + +// GetLocked returns validators list with locked rewards. +func (v SimValidators) GetLocked() SimValidators { + tmpVals := make(SimValidators, 0, len(v)) + for _, val := range v { + if val.RewardsLocked() { + tmpVals = append(tmpVals, val) + } + } + + return tmpVals +} + +// UpdateValidator updates validator status. +func (s *Simulator) UpdateValidator(val *SimValidator) { + require.NotNil(s.t, val) + + updVal := s.QueryStakeValidator(val.GetAddress()) + val.Validator.Status = updVal.Status + val.Validator.Jailed = updVal.Jailed + val.Validator.Bonding = updVal.Bonding + val.Validator.LP = updVal.LP + val.Validator.UnbondingHeight = updVal.UnbondingHeight + val.Validator.UnbondingCompletionTime = updVal.UnbondingCompletionTime + + updLState := s.QueryDistLockState(val.GetAddress()) + if updLState.Enabled { + val.LockRewards(updLState.UnlocksAt) + } else { + val.UnlockRewards() + } +} diff --git a/x/ccstorage/internal/types/genesis.go b/x/ccstorage/internal/types/genesis.go index d17ac0b4..9737f607 100644 --- a/x/ccstorage/internal/types/genesis.go +++ b/x/ccstorage/internal/types/genesis.go @@ -72,6 +72,10 @@ func DefaultGenesisState() GenesisState { Denom: "btc", Decimals: 8, }, + { + Denom: "lpt", + Decimals: 18, + }, }, } diff --git a/x/core/ante_decor_common.go b/x/core/ante_decor_common.go index ef8b643e..a0171a0d 100644 --- a/x/core/ante_decor_common.go +++ b/x/core/ante_decor_common.go @@ -6,12 +6,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" "github.com/dfinance/dnode/x/vmauth" ) var ( - DefaultFees = sdk.Coins{sdk.NewCoin(config.MainDenom, sdk.NewInt(1))} + DefaultFees = sdk.Coins{sdk.NewCoin(defaults.MainDenom, sdk.NewInt(1))} // simulation signature values used to estimate gas consumption simSecp256k1Pubkey secp256k1.PubKeySecp256k1 simSecp256k1Sig [64]byte diff --git a/x/core/ante_decor_denom.go b/x/core/ante_decor_denom.go index 3133bd9d..97e99569 100644 --- a/x/core/ante_decor_denom.go +++ b/x/core/ante_decor_denom.go @@ -5,7 +5,7 @@ import ( sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/dfinance/dnode/cmd/config" + "github.com/dfinance/dnode/cmd/config/genesis/defaults" ) // DenomDecorator catches and prevents transactions without fees and fees not in "xfi" currency @@ -30,7 +30,7 @@ func (dd DenomDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, n } if !stdTx.Fee.Amount.DenomsSubsetOf(DefaultFees) { - return auth.SetGasMeter(simulate, ctx, 0), sdkErrors.Wrap(ErrWrongFeeDenom, config.MainDenom) + return auth.SetGasMeter(simulate, ctx, 0), sdkErrors.Wrap(ErrWrongFeeDenom, defaults.MainDenom) } }