github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/keeper/deposit_test.go (about) 1 package keeper_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 9 "cosmossdk.io/collections" 10 sdkmath "cosmossdk.io/math" 11 12 "github.com/cosmos/cosmos-sdk/codec/address" 13 simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" 14 sdk "github.com/cosmos/cosmos-sdk/types" 15 authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 16 disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" 17 v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 18 ) 19 20 const ( 21 baseDepositTestAmount = 100 22 baseDepositTestPercent = 25 23 ) 24 25 func TestDeposits(t *testing.T) { 26 testcases := []struct { 27 name string 28 expedited bool 29 }{ 30 { 31 name: "regular", 32 }, 33 { 34 name: "expedited", 35 expedited: true, 36 }, 37 } 38 39 for _, tc := range testcases { 40 t.Run(tc.name, func(t *testing.T) { 41 govKeeper, authKeeper, bankKeeper, stakingKeeper, distKeeper, _, ctx := setupGovKeeper(t) 42 trackMockBalances(bankKeeper, distKeeper) 43 44 // With expedited proposals the minimum deposit is higher, so we must 45 // initialize and deposit an amount depositMultiplier times larger 46 // than the regular min deposit amount. 47 depositMultiplier := int64(1) 48 if tc.expedited { 49 depositMultiplier = v1.DefaultMinExpeditedDepositTokensRatio 50 } 51 52 TestAddrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 2, sdkmath.NewInt(10000000*depositMultiplier)) 53 authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes() 54 55 tp := TestProposal 56 proposal, err := govKeeper.SubmitProposal(ctx, tp, "", "title", "summary", TestAddrs[0], tc.expedited) 57 require.NoError(t, err) 58 proposalID := proposal.Id 59 60 fourStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 4*depositMultiplier))) 61 fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 5*depositMultiplier))) 62 63 addr0Initial := bankKeeper.GetAllBalances(ctx, TestAddrs[0]) 64 addr1Initial := bankKeeper.GetAllBalances(ctx, TestAddrs[1]) 65 66 require.True(t, sdk.NewCoins(proposal.TotalDeposit...).Equal(sdk.NewCoins())) 67 68 // Check no deposits at beginning 69 _, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1])) 70 require.ErrorIs(t, err, collections.ErrNotFound) 71 proposal, err = govKeeper.Proposals.Get(ctx, proposalID) 72 require.Nil(t, err) 73 require.Nil(t, proposal.VotingStartTime) 74 75 // Check first deposit 76 votingStarted, err := govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fourStake) 77 require.NoError(t, err) 78 require.False(t, votingStarted) 79 deposit, err := govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[0])) 80 require.Nil(t, err) 81 require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...)) 82 require.Equal(t, TestAddrs[0].String(), deposit.Depositor) 83 proposal, err = govKeeper.Proposals.Get(ctx, proposalID) 84 require.Nil(t, err) 85 require.Equal(t, fourStake, sdk.NewCoins(proposal.TotalDeposit...)) 86 require.Equal(t, addr0Initial.Sub(fourStake...), bankKeeper.GetAllBalances(ctx, TestAddrs[0])) 87 88 // Check a second deposit from same address 89 votingStarted, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fiveStake) 90 require.NoError(t, err) 91 require.False(t, votingStarted) 92 deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[0])) 93 require.Nil(t, err) 94 require.Equal(t, fourStake.Add(fiveStake...), sdk.NewCoins(deposit.Amount...)) 95 require.Equal(t, TestAddrs[0].String(), deposit.Depositor) 96 proposal, err = govKeeper.Proposals.Get(ctx, proposalID) 97 require.Nil(t, err) 98 require.Equal(t, fourStake.Add(fiveStake...), sdk.NewCoins(proposal.TotalDeposit...)) 99 require.Equal(t, addr0Initial.Sub(fourStake...).Sub(fiveStake...), bankKeeper.GetAllBalances(ctx, TestAddrs[0])) 100 101 // Check third deposit from a new address 102 votingStarted, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[1], fourStake) 103 require.NoError(t, err) 104 require.True(t, votingStarted) 105 deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1])) 106 require.Nil(t, err) 107 require.Equal(t, TestAddrs[1].String(), deposit.Depositor) 108 require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...)) 109 proposal, err = govKeeper.Proposals.Get(ctx, proposalID) 110 require.Nil(t, err) 111 require.Equal(t, fourStake.Add(fiveStake...).Add(fourStake...), sdk.NewCoins(proposal.TotalDeposit...)) 112 require.Equal(t, addr1Initial.Sub(fourStake...), bankKeeper.GetAllBalances(ctx, TestAddrs[1])) 113 114 // Check that proposal moved to voting period 115 proposal, err = govKeeper.Proposals.Get(ctx, proposalID) 116 require.Nil(t, err) 117 require.True(t, proposal.VotingStartTime.Equal(ctx.BlockHeader().Time)) 118 119 // Test deposit iterator 120 // NOTE order of deposits is determined by the addresses 121 var deposits v1.Deposits 122 err = govKeeper.Deposits.Walk(ctx, nil, func(_ collections.Pair[uint64, sdk.AccAddress], deposit v1.Deposit) (bool, error) { 123 deposits = append(deposits, &deposit) 124 return false, nil 125 }) 126 require.NoError(t, err) 127 require.Len(t, deposits, 2) 128 propDeposits, _ := govKeeper.GetDeposits(ctx, proposalID) 129 require.Equal(t, deposits, propDeposits) 130 require.Equal(t, TestAddrs[0].String(), deposits[0].Depositor) 131 require.Equal(t, fourStake.Add(fiveStake...), sdk.NewCoins(deposits[0].Amount...)) 132 require.Equal(t, TestAddrs[1].String(), deposits[1].Depositor) 133 require.Equal(t, fourStake, sdk.NewCoins(deposits[1].Amount...)) 134 135 // Test Refund Deposits 136 deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1])) 137 require.Nil(t, err) 138 require.Equal(t, fourStake, sdk.NewCoins(deposit.Amount...)) 139 govKeeper.RefundAndDeleteDeposits(ctx, proposalID) 140 deposit, err = govKeeper.Deposits.Get(ctx, collections.Join(proposalID, TestAddrs[1])) 141 require.ErrorIs(t, err, collections.ErrNotFound) 142 require.Equal(t, addr0Initial, bankKeeper.GetAllBalances(ctx, TestAddrs[0])) 143 require.Equal(t, addr1Initial, bankKeeper.GetAllBalances(ctx, TestAddrs[1])) 144 145 // Test delete and burn deposits 146 proposal, err = govKeeper.SubmitProposal(ctx, tp, "", "title", "summary", TestAddrs[0], true) 147 require.NoError(t, err) 148 proposalID = proposal.Id 149 _, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fourStake) 150 require.NoError(t, err) 151 govKeeper.DeleteAndBurnDeposits(ctx, proposalID) 152 deposits, _ = govKeeper.GetDeposits(ctx, proposalID) 153 require.Len(t, deposits, 0) 154 require.Equal(t, addr0Initial.Sub(fourStake...), bankKeeper.GetAllBalances(ctx, TestAddrs[0])) 155 }) 156 } 157 } 158 159 func TestDepositAmount(t *testing.T) { 160 testcases := []struct { 161 name string 162 deposit sdk.Coins 163 minDepositRatio string 164 err string 165 }{ 166 { 167 name: "good amount and denoms", 168 deposit: sdk.NewCoins(sdk.NewInt64Coin("stake", 10000)), 169 minDepositRatio: "0.001", 170 }, 171 { 172 name: "good amount and denoms but not enough balance for zcoin", 173 deposit: sdk.NewCoins(sdk.NewInt64Coin("stake", 10000), sdk.NewInt64Coin("zcoin", 1)), 174 minDepositRatio: "0.001", 175 err: "not enough balance", 176 }, 177 { 178 name: "too small amount", 179 deposit: sdk.NewCoins(sdk.NewInt64Coin("stake", 10)), 180 minDepositRatio: "0.001", 181 err: "received 10stake but need at least one of the following: 10000stake,10zcoin: minimum deposit is too small", 182 }, 183 { 184 name: "too small amount with another coin", 185 deposit: sdk.NewCoins(sdk.NewInt64Coin("zcoin", 1)), 186 minDepositRatio: "0.001", 187 err: "received 1zcoin but need at least one of the following: 10000stake,10zcoin: minimum deposit is too small", 188 }, 189 { 190 name: "bad denom", 191 deposit: sdk.NewCoins(sdk.NewInt64Coin("euro", 10000)), 192 minDepositRatio: "0.001", 193 err: "deposited 10000euro, but gov accepts only the following denom(s): [stake zcoin]: invalid deposit denom", 194 }, 195 { 196 name: "mix containing bad and good denom", 197 deposit: sdk.NewCoins(sdk.NewInt64Coin("stake", 10000), sdk.NewInt64Coin("euro", 10000)), 198 minDepositRatio: "0.001", 199 err: "deposited 10000euro,10000stake, but gov accepts only the following denom(s): [stake zcoin]: invalid deposit denom", 200 }, 201 { 202 name: "minDepositRatio is zero", 203 deposit: sdk.NewCoins(sdk.NewInt64Coin("stake", 10)), 204 minDepositRatio: "0.0", 205 }, 206 } 207 208 for _, tc := range testcases { 209 t.Run(tc.name, func(t *testing.T) { 210 govKeeper, authKeeper, bankKeeper, stakingKeeper, distrKeeper, _, ctx := setupGovKeeper(t) 211 trackMockBalances(bankKeeper, distrKeeper) 212 213 testAddrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 2, sdkmath.NewInt(1000000000000000)) 214 authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes() 215 216 params, _ := govKeeper.Params.Get(ctx) 217 params.MinDepositRatio = tc.minDepositRatio 218 params.MinDeposit = sdk.NewCoins(params.MinDeposit...).Add(sdk.NewCoin("zcoin", sdkmath.NewInt(10000))) // coins must be sorted by denom 219 err := govKeeper.Params.Set(ctx, params) 220 require.NoError(t, err) 221 222 tp := TestProposal 223 proposal, err := govKeeper.SubmitProposal(ctx, tp, "", "title", "summary", testAddrs[0], false) 224 require.NoError(t, err) 225 proposalID := proposal.Id 226 227 _, err = govKeeper.AddDeposit(ctx, proposalID, testAddrs[0], tc.deposit) 228 if tc.err != "" { 229 require.Error(t, err) 230 require.Equal(t, tc.err, err.Error()) 231 } else { 232 require.NoError(t, err) 233 } 234 }) 235 } 236 } 237 238 func TestValidateInitialDeposit(t *testing.T) { 239 testcases := map[string]struct { 240 minDeposit sdk.Coins 241 minInitialDepositPercent int64 242 initialDeposit sdk.Coins 243 expedited bool 244 245 expectError bool 246 }{ 247 "min deposit * initial percent == initial deposit: success": { 248 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 249 minInitialDepositPercent: baseDepositTestPercent, 250 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100))), 251 }, 252 "min deposit * initial percent < initial deposit: success": { 253 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 254 minInitialDepositPercent: baseDepositTestPercent, 255 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100+1))), 256 }, 257 "min deposit * initial percent > initial deposit: error": { 258 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 259 minInitialDepositPercent: baseDepositTestPercent, 260 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100-1))), 261 262 expectError: true, 263 }, 264 "min deposit * initial percent == initial deposit (non-base values and denom): success": { 265 minDeposit: sdk.NewCoins(sdk.NewCoin("uosmo", sdkmath.NewInt(56912))), 266 minInitialDepositPercent: 50, 267 initialDeposit: sdk.NewCoins(sdk.NewCoin("uosmo", sdkmath.NewInt(56912/2+10))), 268 }, 269 "min deposit * initial percent == initial deposit but different denoms: error": { 270 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 271 minInitialDepositPercent: baseDepositTestPercent, 272 initialDeposit: sdk.NewCoins(sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100))), 273 274 expectError: true, 275 }, 276 "min deposit * initial percent == initial deposit (multiple coins): success": { 277 minDeposit: sdk.NewCoins( 278 sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount)), 279 sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*2))), 280 minInitialDepositPercent: baseDepositTestPercent, 281 initialDeposit: sdk.NewCoins( 282 sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100)), 283 sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*2*baseDepositTestPercent/100)), 284 ), 285 }, 286 "min deposit * initial percent > initial deposit (multiple coins): error": { 287 minDeposit: sdk.NewCoins( 288 sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount)), 289 sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*2))), 290 minInitialDepositPercent: baseDepositTestPercent, 291 initialDeposit: sdk.NewCoins( 292 sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100)), 293 sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*2*baseDepositTestPercent/100-1)), 294 ), 295 296 expectError: true, 297 }, 298 "min deposit * initial percent < initial deposit (multiple coins - coin not required by min deposit): success": { 299 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 300 minInitialDepositPercent: baseDepositTestPercent, 301 initialDeposit: sdk.NewCoins( 302 sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100)), 303 sdk.NewCoin("uosmo", sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100-1)), 304 ), 305 }, 306 "0 initial percent: success": { 307 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 308 minInitialDepositPercent: 0, 309 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100))), 310 }, 311 "expedited min deposit * initial percent == initial deposit: success": { 312 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 313 minInitialDepositPercent: baseDepositTestPercent, 314 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100))), 315 expedited: true, 316 }, 317 "expedited - 0 initial percent: success": { 318 minDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount))), 319 minInitialDepositPercent: 0, 320 initialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(baseDepositTestAmount*baseDepositTestPercent/100))), 321 expedited: true, 322 }, 323 } 324 325 for name, tc := range testcases { 326 t.Run(name, func(t *testing.T) { 327 govKeeper, _, _, _, _, _, ctx := setupGovKeeper(t) 328 329 params := v1.DefaultParams() 330 if tc.expedited { 331 params.ExpeditedMinDeposit = tc.minDeposit 332 } else { 333 params.MinDeposit = tc.minDeposit 334 } 335 params.MinInitialDepositRatio = sdkmath.LegacyNewDec(tc.minInitialDepositPercent).Quo(sdkmath.LegacyNewDec(100)).String() 336 337 govKeeper.Params.Set(ctx, params) 338 339 err := govKeeper.ValidateInitialDeposit(ctx, tc.initialDeposit, tc.expedited) 340 341 if tc.expectError { 342 require.Error(t, err) 343 return 344 } 345 require.NoError(t, err) 346 }) 347 } 348 } 349 350 func TestChargeDeposit(t *testing.T) { 351 testCases := []struct { 352 name string 353 proposalCancelRatio string 354 proposalCancelDestAddress string 355 expectError bool 356 }{ 357 { 358 name: "Success: CancelRatio=0", 359 proposalCancelRatio: "0", 360 proposalCancelDestAddress: "", 361 expectError: false, 362 }, 363 { 364 name: "Success: CancelRatio=0.5", 365 proposalCancelRatio: "0.5", 366 proposalCancelDestAddress: "", 367 expectError: false, 368 }, 369 { 370 name: "Success: CancelRatio=1", 371 proposalCancelRatio: "1", 372 proposalCancelDestAddress: "", 373 expectError: false, 374 }, 375 } 376 377 for _, tc := range testCases { 378 for i := 0; i < 3; i++ { 379 testName := func(i int) string { 380 if i == 0 { 381 return fmt.Sprintf("%s and dest address is %s", tc.name, "nil") 382 } else if i == 1 { 383 return fmt.Sprintf("%s and dest address is normal address", tc.name) 384 } 385 return fmt.Sprintf("%s and dest address is community address", tc.name) 386 } 387 388 t.Run(testName(i), func(t *testing.T) { 389 govKeeper, authKeeper, bankKeeper, stakingKeeper, _, _, ctx := setupGovKeeper(t) 390 params := v1.DefaultParams() 391 params.ProposalCancelRatio = tc.proposalCancelRatio 392 TestAddrs := simtestutil.AddTestAddrsIncremental(bankKeeper, stakingKeeper, ctx, 2, sdkmath.NewInt(10000000000)) 393 authKeeper.EXPECT().AddressCodec().Return(address.NewBech32Codec("cosmos")).AnyTimes() 394 395 switch i { 396 case 0: 397 // no dest address for cancel proposal, total cancellation charges will be burned 398 params.ProposalCancelDest = "" 399 case 1: 400 // normal account address for proposal cancel dest address 401 params.ProposalCancelDest = TestAddrs[1].String() 402 default: 403 // community address for proposal cancel dest address 404 params.ProposalCancelDest = authtypes.NewModuleAddress(disttypes.ModuleName).String() 405 } 406 407 err := govKeeper.Params.Set(ctx, params) 408 require.NoError(t, err) 409 410 tp := TestProposal 411 proposal, err := govKeeper.SubmitProposal(ctx, tp, "", "title", "summary", TestAddrs[0], false) 412 require.NoError(t, err) 413 proposalID := proposal.Id 414 // deposit to proposal 415 fiveStake := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, stakingKeeper.TokensFromConsensusPower(ctx, 300))) 416 _, err = govKeeper.AddDeposit(ctx, proposalID, TestAddrs[0], fiveStake) 417 require.NoError(t, err) 418 419 codec := address.NewBech32Codec("cosmos") 420 // get balances of dest address 421 var prevBalance sdk.Coin 422 if len(params.ProposalCancelDest) != 0 { 423 accAddr, err := codec.StringToBytes(params.ProposalCancelDest) 424 require.NoError(t, err) 425 prevBalance = bankKeeper.GetBalance(ctx, accAddr, sdk.DefaultBondDenom) 426 } 427 428 // get the deposits 429 allDeposits, _ := govKeeper.GetDeposits(ctx, proposalID) 430 431 // charge cancellation charges for cancel proposal 432 err = govKeeper.ChargeDeposit(ctx, proposalID, TestAddrs[0].String(), params.ProposalCancelRatio) 433 if tc.expectError { 434 require.Error(t, err) 435 return 436 } 437 require.NoError(t, err) 438 439 if len(params.ProposalCancelDest) != 0 { 440 accAddr, err := codec.StringToBytes(params.ProposalCancelDest) 441 require.NoError(t, err) 442 newBalanceAfterCancelProposal := bankKeeper.GetBalance(ctx, accAddr, sdk.DefaultBondDenom) 443 cancellationCharges := sdkmath.NewInt(0) 444 for _, deposits := range allDeposits { 445 for _, deposit := range deposits.Amount { 446 burnAmount := sdkmath.LegacyNewDecFromInt(deposit.Amount).Mul(sdkmath.LegacyMustNewDecFromStr(params.MinInitialDepositRatio)).TruncateInt() 447 cancellationCharges = cancellationCharges.Add(burnAmount) 448 } 449 } 450 require.True(t, newBalanceAfterCancelProposal.Equal(prevBalance.Add(sdk.NewCoin(sdk.DefaultBondDenom, cancellationCharges)))) 451 } 452 }) 453 } 454 } 455 }