github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/keeper/internal/keeper_test.go (about)

     1  package internal_test
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  	"github.com/stretchr/testify/suite"
     9  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    10  
    11  	"github.com/Finschia/finschia-sdk/crypto/keys/secp256k1"
    12  	"github.com/Finschia/finschia-sdk/simapp"
    13  	"github.com/Finschia/finschia-sdk/testutil/testdata"
    14  	sdk "github.com/Finschia/finschia-sdk/types"
    15  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    16  	"github.com/Finschia/finschia-sdk/x/foundation"
    17  	keeper "github.com/Finschia/finschia-sdk/x/foundation/keeper"
    18  	"github.com/Finschia/finschia-sdk/x/foundation/keeper/internal"
    19  	govtypes "github.com/Finschia/finschia-sdk/x/gov/types"
    20  	minttypes "github.com/Finschia/finschia-sdk/x/mint/types"
    21  )
    22  
    23  type KeeperTestSuite struct {
    24  	suite.Suite
    25  
    26  	deterministic bool
    27  
    28  	ctx sdk.Context
    29  
    30  	bankKeeper foundation.BankKeeper
    31  	keeper     keeper.Keeper
    32  	impl       internal.Keeper
    33  
    34  	queryServer     foundation.QueryServer
    35  	msgServer       foundation.MsgServer
    36  	proposalHandler govtypes.Handler
    37  
    38  	authority sdk.AccAddress
    39  	members   []sdk.AccAddress
    40  	stranger  sdk.AccAddress
    41  
    42  	activeProposal    uint64
    43  	votedProposal     uint64
    44  	withdrawnProposal uint64
    45  	invalidProposal   uint64
    46  	noHandlerProposal uint64
    47  	nextProposal      uint64
    48  
    49  	balance sdk.Int
    50  }
    51  
    52  func newMsgCreateDog(name string) sdk.Msg {
    53  	return &testdata.MsgCreateDog{
    54  		Dog: &testdata.Dog{
    55  			Name: name,
    56  		},
    57  	}
    58  }
    59  
    60  func (s *KeeperTestSuite) createAddresses(accNum int) []sdk.AccAddress {
    61  	if s.deterministic {
    62  		addresses := make([]sdk.AccAddress, accNum)
    63  		for i := range addresses {
    64  			addresses[i] = sdk.AccAddress(fmt.Sprintf("address%d", i))
    65  		}
    66  		return addresses
    67  	} else {
    68  		seenAddresses := make(map[string]bool, accNum)
    69  		addresses := make([]sdk.AccAddress, accNum)
    70  		for i := range addresses {
    71  			var address sdk.AccAddress
    72  			for {
    73  				pk := secp256k1.GenPrivKey().PubKey()
    74  				address = sdk.AccAddress(pk.Address())
    75  				if !seenAddresses[address.String()] {
    76  					seenAddresses[address.String()] = true
    77  					break
    78  				}
    79  			}
    80  			addresses[i] = address
    81  		}
    82  		return addresses
    83  	}
    84  }
    85  
    86  func (s *KeeperTestSuite) SetupTest() {
    87  	checkTx := false
    88  	app := simapp.Setup(checkTx)
    89  	testdata.RegisterInterfaces(app.InterfaceRegistry())
    90  	testdata.RegisterMsgServer(app.MsgServiceRouter(), testdata.MsgServerImpl{})
    91  
    92  	s.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{})
    93  	s.bankKeeper = app.BankKeeper
    94  	s.keeper = app.FoundationKeeper
    95  	s.impl = internal.NewKeeper(
    96  		app.AppCodec(),
    97  		app.GetKey(foundation.ModuleName),
    98  		app.MsgServiceRouter(),
    99  		app.AccountKeeper,
   100  		app.BankKeeper,
   101  		authtypes.FeeCollectorName,
   102  		foundation.DefaultConfig(),
   103  		foundation.DefaultAuthority().String(),
   104  		app.GetSubspace(foundation.ModuleName),
   105  	)
   106  
   107  	s.queryServer = keeper.NewQueryServer(s.keeper)
   108  	s.msgServer = keeper.NewMsgServer(s.keeper)
   109  
   110  	s.proposalHandler = keeper.NewFoundationProposalsHandler(s.keeper)
   111  
   112  	s.impl.SetParams(s.ctx, foundation.Params{
   113  		FoundationTax: sdk.OneDec(),
   114  	})
   115  
   116  	s.impl.SetCensorship(s.ctx, foundation.Censorship{
   117  		MsgTypeUrl: sdk.MsgTypeURL((*foundation.MsgWithdrawFromTreasury)(nil)),
   118  		Authority:  foundation.CensorshipAuthorityFoundation,
   119  	})
   120  
   121  	s.authority = sdk.MustAccAddressFromBech32(s.impl.GetAuthority())
   122  
   123  	numMembers := 10
   124  	addresses := s.createAddresses(numMembers + 1)
   125  	s.members = addresses[:numMembers]
   126  	for i := range s.members {
   127  		member := foundation.Member{
   128  			Address: s.members[i].String(),
   129  		}
   130  		s.impl.SetMember(s.ctx, member)
   131  	}
   132  	s.stranger = addresses[len(addresses)-1]
   133  
   134  	info := foundation.DefaultFoundation()
   135  	info.TotalWeight = sdk.NewDec(int64(len(s.members)))
   136  	err := info.SetDecisionPolicy(workingPolicy())
   137  	s.Require().NoError(err)
   138  	s.impl.SetFoundationInfo(s.ctx, info)
   139  
   140  	s.balance = sdk.NewInt(987654321)
   141  	s.impl.SetPool(s.ctx, foundation.Pool{
   142  		Treasury: sdk.NewDecCoinsFromCoins(sdk.NewCoin(sdk.DefaultBondDenom, s.balance)),
   143  	})
   144  	holders := []sdk.AccAddress{
   145  		s.stranger,
   146  		app.AccountKeeper.GetModuleAccount(s.ctx, foundation.TreasuryName).GetAddress(),
   147  		app.AccountKeeper.GetModuleAccount(s.ctx, authtypes.FeeCollectorName).GetAddress(),
   148  	}
   149  	for _, holder := range holders {
   150  		amount := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, s.balance))
   151  
   152  		// using minttypes here introduces dependency on x/mint
   153  		// the work around would be registering a new module account on this suite
   154  		// because x/bank already has dependency on x/mint, and we must have dependency
   155  		// on x/bank, it's OK to use x/mint here.
   156  		minterName := minttypes.ModuleName
   157  		err := app.BankKeeper.MintCoins(s.ctx, minterName, amount)
   158  		s.Require().NoError(err)
   159  
   160  		minter := app.AccountKeeper.GetModuleAccount(s.ctx, minterName).GetAddress()
   161  		err = app.BankKeeper.SendCoins(s.ctx, minter, holder, amount)
   162  		s.Require().NoError(err)
   163  	}
   164  
   165  	// create an active proposal, voted yes by all members except the first member
   166  	activeProposal, err := s.impl.SubmitProposal(s.ctx, []string{s.members[0].String()}, "", []sdk.Msg{
   167  		&foundation.MsgWithdrawFromTreasury{
   168  			Authority: s.authority.String(),
   169  			To:        s.stranger.String(),
   170  			Amount:    sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, s.balance)),
   171  		},
   172  	})
   173  	s.Require().NoError(err)
   174  	s.activeProposal = *activeProposal
   175  
   176  	for _, member := range s.members[1:] {
   177  		err := s.impl.Vote(s.ctx, foundation.Vote{
   178  			ProposalId: s.activeProposal,
   179  			Voter:      member.String(),
   180  			Option:     foundation.VOTE_OPTION_YES,
   181  		})
   182  		s.Require().NoError(err)
   183  	}
   184  
   185  	// create a proposal voted no by all members
   186  	votedProposal, err := s.impl.SubmitProposal(s.ctx, []string{s.members[0].String()}, "", []sdk.Msg{newMsgCreateDog("shiba1")})
   187  	s.Require().NoError(err)
   188  	s.votedProposal = *votedProposal
   189  
   190  	for _, member := range s.members {
   191  		err := s.impl.Vote(s.ctx, foundation.Vote{
   192  			ProposalId: s.votedProposal,
   193  			Voter:      member.String(),
   194  			Option:     foundation.VOTE_OPTION_NO,
   195  		})
   196  		s.Require().NoError(err)
   197  	}
   198  
   199  	// create an withdrawn proposal
   200  	withdrawnProposal, err := s.impl.SubmitProposal(s.ctx, []string{s.members[0].String()}, "", []sdk.Msg{newMsgCreateDog("shiba2")})
   201  	s.Require().NoError(err)
   202  	s.withdrawnProposal = *withdrawnProposal
   203  
   204  	err = s.impl.WithdrawProposal(s.ctx, s.withdrawnProposal)
   205  	s.Require().NoError(err)
   206  
   207  	// create an invalid proposal which contains invalid message
   208  	invalidProposal, err := s.impl.SubmitProposal(s.ctx, []string{s.members[0].String()}, "", []sdk.Msg{
   209  		&foundation.MsgWithdrawFromTreasury{
   210  			Authority: s.authority.String(),
   211  			To:        s.stranger.String(),
   212  			Amount:    sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, s.balance.Add(sdk.OneInt()))),
   213  		},
   214  	})
   215  	s.Require().NoError(err)
   216  	s.invalidProposal = *invalidProposal
   217  
   218  	for _, member := range s.members {
   219  		err := s.impl.Vote(s.ctx, foundation.Vote{
   220  			ProposalId: s.invalidProposal,
   221  			Voter:      member.String(),
   222  			Option:     foundation.VOTE_OPTION_YES,
   223  		})
   224  		s.Require().NoError(err)
   225  	}
   226  
   227  	// create an invalid proposal which contains invalid message
   228  	noHandlerProposal, err := s.impl.SubmitProposal(s.ctx, []string{s.members[0].String()}, "", []sdk.Msg{testdata.NewTestMsg(s.authority)})
   229  	s.Require().NoError(err)
   230  	s.noHandlerProposal = *noHandlerProposal
   231  
   232  	for _, member := range s.members {
   233  		err := s.impl.Vote(s.ctx, foundation.Vote{
   234  			ProposalId: s.noHandlerProposal,
   235  			Voter:      member.String(),
   236  			Option:     foundation.VOTE_OPTION_YES,
   237  		})
   238  		s.Require().NoError(err)
   239  	}
   240  
   241  	// next proposal is the proposal id for the upcoming proposal
   242  	s.nextProposal = s.noHandlerProposal + 1
   243  
   244  	// grant stranger to receive foundation treasury
   245  	err = s.impl.Grant(s.ctx, s.stranger, &foundation.ReceiveFromTreasuryAuthorization{})
   246  	s.Require().NoError(err)
   247  }
   248  
   249  func TestKeeperTestSuite(t *testing.T) {
   250  	for _, deterministic := range []bool{
   251  		false,
   252  		true,
   253  	} {
   254  		suite.Run(t, &KeeperTestSuite{deterministic: deterministic})
   255  	}
   256  }
   257  
   258  func TestNewKeeper(t *testing.T) {
   259  	createAddress := func() sdk.AccAddress {
   260  		return sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
   261  	}
   262  	authority := foundation.DefaultAuthority()
   263  
   264  	testCases := map[string]struct {
   265  		authority sdk.AccAddress
   266  		panics    bool
   267  	}{
   268  		"default authority": {
   269  			authority: authority,
   270  		},
   271  		"invalid account": {
   272  			panics: true,
   273  		},
   274  		"not the default authority": {
   275  			authority: createAddress(),
   276  			panics:    true,
   277  		},
   278  	}
   279  
   280  	for name, tc := range testCases {
   281  		tc := tc
   282  		t.Run(name, func(t *testing.T) {
   283  			newKeeper := func() keeper.Keeper {
   284  				app := simapp.Setup(false)
   285  				return keeper.NewKeeper(app.AppCodec(), sdk.NewKVStoreKey(foundation.StoreKey), app.MsgServiceRouter(), app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, foundation.DefaultConfig(), tc.authority.String(), app.GetSubspace(foundation.ModuleName))
   286  			}
   287  
   288  			if tc.panics {
   289  				require.Panics(t, func() { newKeeper() })
   290  				return
   291  			}
   292  			require.NotPanics(t, func() { newKeeper() })
   293  
   294  			k := newKeeper()
   295  			require.Equal(t, authority.String(), k.GetAuthority())
   296  		})
   297  	}
   298  }