diff --git a/tests/e2e/dao/pop/selection_suite.go b/tests/e2e/dao/pop/selection_suite.go index f390c63..70f46af 100644 --- a/tests/e2e/dao/pop/selection_suite.go +++ b/tests/e2e/dao/pop/selection_suite.go @@ -10,9 +10,11 @@ import ( "strconv" "time" + sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/planetmint/planetmint-go/lib" "github.com/planetmint/planetmint-go/monitor" "github.com/planetmint/planetmint-go/testutil" @@ -85,6 +87,17 @@ func (s *SelectionE2ETestSuite) SetupSuite() { daoGenState.Params.ClaimAddress = valAddr.String() s.cfg.GenesisState[daotypes.ModuleName] = s.cfg.Codec.MustMarshalJSON(&daoGenState) + // setting up stagedClaims that are not part of PoP issuance (i.e.: past unresolved claims) + machineBalances := []banktypes.Balance{ + {Address: machines[0].address, Coins: sdk.NewCoins(sdk.NewCoin(daoGenState.Params.StagedDenom, sdkmath.NewInt(10000)))}, + {Address: machines[1].address, Coins: sdk.NewCoins(sdk.NewCoin(daoGenState.Params.StagedDenom, sdkmath.NewInt(10000)))}, + } + + var bankGenState banktypes.GenesisState + s.cfg.Codec.MustUnmarshalJSON(s.cfg.GenesisState[banktypes.ModuleName], &bankGenState) + bankGenState.Balances = append(bankGenState.Balances, machineBalances...) + s.cfg.GenesisState[banktypes.ModuleName] = s.cfg.Codec.MustMarshalJSON(&bankGenState) + s.network = network.Load(s.T(), s.cfg) } @@ -198,7 +211,7 @@ func (s *SelectionE2ETestSuite) VerifyTokens(token string) { }) s.Require().NoError(err) assert.Contains(s.T(), out.String(), token) - assert.Equal(s.T(), "amount: \"18279452050\"\ndenom: "+token+"\n", out.String()) // Total supply 2 * 7990867578 (total supply) + 1 * 1997716894 (challenger) + 3 * 100000000 (validator) = 17979452050 + assert.Equal(s.T(), "amount: \"18279472050\"\ndenom: "+token+"\n", out.String()) // Total supply 2 * 7990867578 (total supply) + 1 * 1997716894 (challenger) + 3 * 100000000 (validator) + 2 * 10000 (past unresolved claims) = 17979472050 out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{ machines[0].address, @@ -206,7 +219,7 @@ func (s *SelectionE2ETestSuite) VerifyTokens(token string) { }) s.Require().NoError(err) assert.Contains(s.T(), out.String(), token) - assert.Equal(s.T(), "amount: \"5993150682\"\ndenom: "+token+"\n", out.String()) // 3 * 1997716894 = 5993150682 + assert.Equal(s.T(), "amount: \"5993160682\"\ndenom: "+token+"\n", out.String()) // 3 * 1997716894 + 1 * 10000= 5993160682 out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{ machines[1].address, @@ -214,7 +227,7 @@ func (s *SelectionE2ETestSuite) VerifyTokens(token string) { }) s.Require().NoError(err) assert.Contains(s.T(), out.String(), token) - assert.Equal(s.T(), "amount: \"11986301368\"\ndenom: "+token+"\n", out.String()) // 2 * 5993150684 = 11986301368 + assert.Equal(s.T(), "amount: \"11986311368\"\ndenom: "+token+"\n", out.String()) // 2 * 5993150684 + 1 * 10000 = 11986311368 out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, bank.GetBalancesCmd(), []string{ val.Address.String(), @@ -320,11 +333,11 @@ func (s *SelectionE2ETestSuite) TestTokenRedeemClaim() { // QueryRedeemClaim qOut, err := clitestutil.ExecTestCLICmd(val.ClientCtx, daocli.CmdShowRedeemClaim(), []string{"liquidAddress", "0"}) s.Require().NoError(err) - assert.Equal(s.T(), "redeemClaim:\n amount: \"5993150682\"\n beneficiary: liquidAddress\n confirmed: true\n creator: plmnt1kp93kns6hs2066d8qw0uz84fw3vlthewt2ck6p\n id: \"0\"\n liquidTxHash: \"0000000000000000000000000000000000000000000000000000000000000000\"\n", qOut.String()) + assert.Equal(s.T(), "redeemClaim:\n amount: \"5993160682\"\n beneficiary: liquidAddress\n confirmed: true\n creator: plmnt1kp93kns6hs2066d8qw0uz84fw3vlthewt2ck6p\n id: \"0\"\n liquidTxHash: \"0000000000000000000000000000000000000000000000000000000000000000\"\n", qOut.String()) qOut, err = clitestutil.ExecTestCLICmd(val.ClientCtx, daocli.CmdRedeemClaimByLiquidTxHash(), []string{"0000000000000000000000000000000000000000000000000000000000000000"}) s.Require().NoError(err) - assert.Equal(s.T(), "redeemClaim:\n amount: \"5993150682\"\n beneficiary: liquidAddress\n confirmed: true\n creator: plmnt1kp93kns6hs2066d8qw0uz84fw3vlthewt2ck6p\n id: \"0\"\n liquidTxHash: \"0000000000000000000000000000000000000000000000000000000000000000\"\n", qOut.String()) + assert.Equal(s.T(), "redeemClaim:\n amount: \"5993160682\"\n beneficiary: liquidAddress\n confirmed: true\n creator: plmnt1kp93kns6hs2066d8qw0uz84fw3vlthewt2ck6p\n id: \"0\"\n liquidTxHash: \"0000000000000000000000000000000000000000000000000000000000000000\"\n", qOut.String()) // Make sure "Publish" has been called with PoPInit cmnd calls := mocks.GetCallLog() diff --git a/x/dao/keeper/msg_server_distribution_result.go b/x/dao/keeper/msg_server_distribution_result.go index 18ce3dd..872bb68 100644 --- a/x/dao/keeper/msg_server_distribution_result.go +++ b/x/dao/keeper/msg_server_distribution_result.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "sort" "strconv" errorsmod "cosmossdk.io/errors" @@ -26,6 +27,10 @@ func (k msgServer) DistributionResult(goCtx context.Context, msg *types.MsgDistr distribution.EarlyInvAddr = msg.EarlyInvestorTxID distribution.StrategicTxID = msg.StrategicTxID + if err := k.clearUnresolvedClaims(ctx, distribution.FirstPop); err != nil { + util.GetAppLogger().Error(ctx, "error while clearing unresolved claims for heights %d-%d: %v", distribution.FirstPop, distribution.LastPop, err) + } + err := k.resolveStagedClaims(ctx, distribution.FirstPop, distribution.LastPop) if err != nil { util.GetAppLogger().Error(ctx, "%s for provided PoP heights: %d %d", types.ErrResolvingStagedClaims.Error(), distribution.FirstPop, distribution.LastPop) @@ -37,14 +42,48 @@ func (k msgServer) DistributionResult(goCtx context.Context, msg *types.MsgDistr return &types.MsgDistributionResultResponse{}, nil } -func (k msgServer) resolveStagedClaims(ctx sdk.Context, start int64, end int64) (err error) { - // lookup all challenges since the last distribution - challenges, err := k.GetChallengeRange(ctx, start, end) +// clearUnresolvedClaims checks for all Challenge participants starting from a given height. +// An accounts stagedDenom amount should always be 0 except for claims that have not yet been reissued. +// Calculate the difference for a set of participants and clear out all past unresolved staged claims. +func (k msgServer) clearUnresolvedClaims(ctx sdk.Context, start int64) (err error) { + // calculate total amounts for current and future claims + currentAmounts, err := k.getClaims(ctx, start, ctx.BlockHeight()) if err != nil { return err } - popParticipants := make(map[string]uint64) + totalAmounts := make(map[string]uint64) + for participantAddress := range currentAmounts { + stagedBalance := k.bankKeeper.GetBalance(ctx, sdk.MustAccAddressFromBech32(participantAddress), k.GetParams(ctx).StagedDenom) + totalAmounts[participantAddress] = stagedBalance.Amount.Uint64() + } + + // calculate difference to account balance + for participantAddress := range totalAmounts { + totalAmounts[participantAddress] -= currentAmounts[participantAddress] + } + + return k.convertOrderedClaim(ctx, totalAmounts) +} + +// resolveStagedClaims converts staged claims to claims in an ordered fashion for a given range +func (k msgServer) resolveStagedClaims(ctx sdk.Context, start int64, end int64) (err error) { + popParticipantAmounts, err := k.getClaims(ctx, start, end) + if err != nil { + return err + } + + return k.convertOrderedClaim(ctx, popParticipantAmounts) +} + +func (k msgServer) getClaims(ctx sdk.Context, start int64, end int64) (claims map[string]uint64, err error) { + // lookup all challenges for a given range + challenges, err := k.GetChallengeRange(ctx, start, end) + if err != nil { + return + } + + claims = make(map[string]uint64) for _, challenge := range challenges { // if challenge not finished nobody has claims @@ -52,9 +91,9 @@ func (k msgServer) resolveStagedClaims(ctx sdk.Context, start int64, end int64) continue } _, challengerAmt, challengeeAmt := util.GetPopReward(challenge.Height, k.GetParams(ctx).PopEpochs) - popParticipants[challenge.Challenger] += challengerAmt + claims[challenge.Challenger] += challengerAmt if challenge.GetSuccess() { - popParticipants[challenge.Challengee] += challengeeAmt + claims[challenge.Challengee] += challengeeAmt } initiatorAddr, err := sdk.AccAddressFromBech32(challenge.Initiator) if err != nil { @@ -64,16 +103,22 @@ func (k msgServer) resolveStagedClaims(ctx sdk.Context, start int64, end int64) if !found { util.GetAppLogger().Error(ctx, "No PoP initiator reward found for height %v", challenge.GetHeight()) } - popParticipants[initiatorAddr.String()] += validatorPopReward + claims[initiatorAddr.String()] += validatorPopReward } + return +} + +func (k msgServer) convertOrderedClaim(ctx sdk.Context, claims map[string]uint64) (err error) { // second data structure because map iteration order is not guaranteed in GO keys := make([]string, 0) - for p := range popParticipants { - keys = append(keys, p) + for accountAddress := range claims { + keys = append(keys, accountAddress) } - for _, p := range keys { - err = k.convertAccountClaim(ctx, p, popParticipants[p]) + + sort.Strings(keys) + for _, accountAddress := range keys { + err = k.convertAccountClaim(ctx, accountAddress, claims[accountAddress]) if err != nil { return err }