diff --git a/tests/e2e/stakingplus/cli_test.go b/tests/e2e/stakingplus/cli_test.go new file mode 100644 index 0000000000..a9bcea56d0 --- /dev/null +++ b/tests/e2e/stakingplus/cli_test.go @@ -0,0 +1,30 @@ +//go:build e2e +// +build e2e + +package stakingplus + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/go-bip39" + + "github.com/Finschia/finschia-sdk/simapp" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) + + cfg.NumValidators = 1 + + entropySeed, err := bip39.NewEntropy(256) + require.NoError(t, err) + mnemonic, err := bip39.NewMnemonic(entropySeed) + require.NoError(t, err) + cfg.Mnemonics = []string{mnemonic} + + suite.Run(t, NewE2ETestSuite(cfg)) +} diff --git a/tests/e2e/stakingplus/grantee.json b/tests/e2e/stakingplus/grantee.json new file mode 100644 index 0000000000..d4c10aa7b7 --- /dev/null +++ b/tests/e2e/stakingplus/grantee.json @@ -0,0 +1,16 @@ +{ + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "l3eo4WwjQBtK1TrxPSXPs8mNUofgZm3XX+G2UOSWqq4=" + }, + "amount": "100stake", + "moniker": "grantee", + "identity": "optional identity signature (ex. UPort or Keybase)", + "website": "validator's (optional) website", + "security": "validator's (optional) security contact email", + "details": "validator's (optional) details", + "commission-rate": "0.1", + "commission-max-rate": "0.2", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" +} \ No newline at end of file diff --git a/tests/e2e/stakingplus/stranger.json b/tests/e2e/stakingplus/stranger.json new file mode 100644 index 0000000000..24fcfc91b3 --- /dev/null +++ b/tests/e2e/stakingplus/stranger.json @@ -0,0 +1,16 @@ +{ + "pubkey": { + "@type": "/cosmos.crypto.ed25519.PubKey", + "key": "4wf9IfLAIjtTFNEsBWLmzGFAhtw5w4FKsPJn4Dk2XPg=" + }, + "amount": "100stake", + "moniker": "stranger", + "identity": "optional identity signature (ex. UPort or Keybase)", + "website": "validator's (optional) website", + "security": "validator's (optional) security contact email", + "details": "validator's (optional) details", + "commission-rate": "0.1", + "commission-max-rate": "0.2", + "commission-max-change-rate": "0.01", + "min-self-delegation": "1" +} \ No newline at end of file diff --git a/tests/e2e/stakingplus/suite.go b/tests/e2e/stakingplus/suite.go new file mode 100644 index 0000000000..72c0e3bd4b --- /dev/null +++ b/tests/e2e/stakingplus/suite.go @@ -0,0 +1,181 @@ +package stakingplus + +import ( + "fmt" + "time" + + "github.com/stretchr/testify/suite" + + "cosmossdk.io/core/address" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client/flags" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" + bankcli "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/Finschia/finschia-sdk/x/foundation" + "github.com/Finschia/finschia-sdk/x/stakingplus" +) + +type E2ETestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + + grantee sdk.AccAddress + permanentMember sdk.AccAddress + stranger sdk.AccAddress + + addressCodec address.Codec + valAddressCodec address.Codec +} + +func NewE2ETestSuite(cfg network.Config) *E2ETestSuite { + return &E2ETestSuite{cfg: cfg} +} + +func (s *E2ETestSuite) SetupSuite() { + s.T().Log("setting up e2e test suite") + + s.addressCodec = addresscodec.NewBech32Codec("link") + s.valAddressCodec = addresscodec.NewBech32Codec("linkvaloper") + + genesisState := s.cfg.GenesisState + + var foundationData foundation.GenesisState + s.Require().NoError(s.cfg.Codec.UnmarshalJSON(genesisState[foundation.ModuleName], &foundationData)) + + // enable foundation tax + params := foundation.Params{ + FoundationTax: math.LegacyMustNewDecFromStr("0.2"), + } + foundationData.Params = params + + var strangerMnemonic string + var granteeMnemonic string + var permanentMemberMnemonic string + granteeMnemonic, s.grantee = s.createMnemonic("grantee") + strangerMnemonic, s.stranger = s.createMnemonic("stranger") + permanentMemberMnemonic, s.permanentMember = s.createMnemonic("permanentmember") + + foundationData.Members = []foundation.Member{ + { + Address: s.bytesToString(s.permanentMember), + Metadata: "permanent member", + }, + } + + info := foundation.DefaultFoundation() + info.TotalWeight = math.LegacyNewDecFromInt(math.NewInt(int64(len(foundationData.Members)))) + err := info.SetDecisionPolicy(&foundation.ThresholdDecisionPolicy{ + Threshold: math.LegacyOneDec(), + Windows: &foundation.DecisionPolicyWindows{ + VotingPeriod: 7 * 24 * time.Hour, + }, + }) + s.Require().NoError(err) + foundationData.Foundation = info + + // enable censorship + censorships := []foundation.Censorship{ + { + MsgTypeUrl: sdk.MsgTypeURL((*stakingtypes.MsgCreateValidator)(nil)), + Authority: foundation.CensorshipAuthorityFoundation, + }, + } + foundationData.Censorships = censorships + + val1 := getValidator(s.T(), s.T().TempDir(), s.cfg, 0) + for _, grantee := range []sdk.AccAddress{s.grantee, val1} { + ga := foundation.GrantAuthorization{ + Grantee: s.bytesToString(grantee), + }.WithAuthorization(&stakingplus.CreateValidatorAuthorization{ + ValidatorAddress: s.bytesToValString(grantee), + }) + s.Require().NotNil(ga) + foundationData.Authorizations = append(foundationData.Authorizations, *ga) + } + + foundationDataBz, err := s.cfg.Codec.MarshalJSON(&foundationData) + s.Require().NoError(err) + genesisState[foundation.ModuleName] = foundationDataBz + s.cfg.GenesisState = genesisState + + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + s.createAccount("grantee", granteeMnemonic) + s.createAccount("stranger", strangerMnemonic) + s.createAccount("permanentmember", permanentMemberMnemonic) +} + +func (s *E2ETestSuite) TearDownSuite() { + s.T().Log("tearing down e2e test suite") + s.network.Cleanup() +} + +func (s *E2ETestSuite) bytesToString(addr sdk.AccAddress) string { + str, err := s.addressCodec.BytesToString(addr) + s.Require().NoError(err) + return str +} + +func (s *E2ETestSuite) bytesToValString(addr sdk.AccAddress) string { + str, err := s.valAddressCodec.BytesToString(addr) + s.Require().NoError(err) + return str +} + +// creates an account +func (s *E2ETestSuite) createMnemonic(uid string) (string, sdk.AccAddress) { + cstore := keyring.NewInMemory(s.cfg.Codec) + info, mnemonic, err := cstore.NewMnemonic(uid, keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + + addr, err := info.GetAddress() + s.Require().NoError(err) + + return mnemonic, addr +} + +// creates an account and send some coins to it for the future transactions. +func (s *E2ETestSuite) createAccount(uid, mnemonic string) { + commonArgs := []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(100)))), + } + + val := s.network.Validators[0] + info, err := val.ClientCtx.Keyring.NewAccount(uid, mnemonic, keyring.DefaultBIP39Passphrase, sdk.FullFundraiserPath, hd.Secp256k1) + s.Require().NoError(err) + + addr, err := info.GetAddress() + s.Require().NoError(err) + + fee := sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(1000))) + args := append([]string{ + s.bytesToString(val.Address), + s.bytesToString(addr), + fee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.bytesToString(val.Address)), + }, commonArgs...) + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, bankcli.NewSendTxCmd(s.addressCodec), args) + s.Require().NoError(err) + + var res sdk.TxResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res), out.String()) + s.Require().Zero(res.Code, out.String()) + + s.Require().NoError(s.network.WaitForNextBlock()) +} diff --git a/tests/e2e/stakingplus/tx.go b/tests/e2e/stakingplus/tx.go new file mode 100644 index 0000000000..f99660c1f5 --- /dev/null +++ b/tests/e2e/stakingplus/tx.go @@ -0,0 +1,62 @@ +package stakingplus + +import ( + "fmt" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client/flags" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/client/cli" +) + +func (s *E2ETestSuite) TestNewTxCmdCreateValidator() { + val := s.network.Validators[0] + commonArgs := []string{ + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, math.NewInt(10)))), + } + + testCases := map[string]struct { + args []string + valid bool + }{ + "grantee msg": { + []string{ + "./grantee.json", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.bytesToString(s.grantee)), + }, + true, + }, + "stranger msg": { + []string{ + "./stranger.json", + fmt.Sprintf("--%s=%s", flags.FlagFrom, s.bytesToString(s.stranger)), + }, + false, + }, + } + + for name, tc := range testCases { + tc := tc + + s.Run(name, func() { + cmd := cli.NewCreateValidatorCmd(s.valAddressCodec) + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, append(tc.args, commonArgs...)) + s.Require().NoError(err) + + var res sdk.TxResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &res), out) + s.Require().Zero(res.Code, out) + + err = clitestutil.CheckTxCode(s.network, val.ClientCtx, res.TxHash, 0) + if !tc.valid { + s.Require().Error(err) + return + } + s.Require().NoError(err) + }) + } +} diff --git a/tests/e2e/stakingplus/validator_util.go b/tests/e2e/stakingplus/validator_util.go new file mode 100644 index 0000000000..0b22497b76 --- /dev/null +++ b/tests/e2e/stakingplus/validator_util.go @@ -0,0 +1,39 @@ +package stakingplus + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/network" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func getValidator(t *testing.T, baseDir string, cfg network.Config, i int) sdk.AccAddress { + buf := bufio.NewReader(os.Stdin) + nodeDirName := fmt.Sprintf("node%d", i) + clientDir := filepath.Join(baseDir, nodeDirName, "simcli") + + kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.Codec, cfg.KeyringOptions...) + require.NoError(t, err) + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) + require.NoError(t, err) + + var mnemonic string + if i < len(cfg.Mnemonics) { + mnemonic = cfg.Mnemonics[i] + } + + addr, _, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, mnemonic, true, algo) + require.NoError(t, err) + + return addr +} diff --git a/tests/go.mod b/tests/go.mod index 419d76cd06..895f09b3cf 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,7 +6,7 @@ require ( cosmossdk.io/api v0.7.2 // indirect cosmossdk.io/core v0.11.0 cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/errors v1.0.0 // indirect + cosmossdk.io/errors v1.0.0 cosmossdk.io/log v1.2.1 // indirect cosmossdk.io/math v1.2.0 cosmossdk.io/store v1.0.1 // indirect @@ -33,6 +33,8 @@ require ( require ( github.com/Finschia/finschia-sdk/simapp v0.0.0-00010101000000-000000000000 github.com/Finschia/finschia-sdk/x/foundation v0.0.0-00010101000000-000000000000 + github.com/Finschia/finschia-sdk/x/stakingplus v0.0.0-00010101000000-000000000000 + github.com/cosmos/go-bip39 v1.0.0 github.com/gogo/protobuf v1.3.2 ) @@ -42,7 +44,6 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.3 // indirect cloud.google.com/go/storage v1.30.1 // indirect - cosmossdk.io/client/v2 v2.0.0-beta.1 // indirect cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/x/circuit v0.1.0 // indirect filippo.io/edwards25519 v1.0.0 // indirect @@ -69,7 +70,6 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/cometbft/cometbft-db v0.9.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v1.0.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect @@ -213,9 +213,8 @@ replace ( ) replace ( + github.com/Finschia/finschia-sdk/x/foundation => ./../x/foundation + github.com/Finschia/finschia-sdk/x/stakingplus => ./../x/stakingplus github.com/cometbft/cometbft => github.com/Finschia/cometbft v0.0.0-20231127181424-2aacfbe9832d github.com/cosmos/cosmos-sdk => github.com/Finschia/cosmos-sdk v0.0.0-20231211060251-d8fb76d4c267 ) - -// transient in this PR -replace github.com/Finschia/finschia-sdk/x/foundation => ./../x/foundation diff --git a/tests/go.sum b/tests/go.sum index fc07295a21..484c1485a9 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -189,8 +189,6 @@ cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1V cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= cosmossdk.io/api v0.7.2 h1:BO3i5fvKMKvfaUiMkCznxViuBEfyWA/k6w2eAF6q1C4= cosmossdk.io/api v0.7.2/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38= -cosmossdk.io/client/v2 v2.0.0-beta.1 h1:XkHh1lhrLYIT9zKl7cIOXUXg2hdhtjTPBUfqERNA1/Q= -cosmossdk.io/client/v2 v2.0.0-beta.1/go.mod h1:JEUSu9moNZQ4kU3ir1DKD5eU4bllmAexrGWjmb9k8qU= cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s= cosmossdk.io/collections v0.4.0/go.mod h1:oa5lUING2dP+gdDquow+QjlF45eL1t4TJDypgGd+tv0= cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=