diff --git a/x/precisebank/keeper/burn.go b/x/precisebank/keeper/burn.go index 38750270..3d846625 100644 --- a/x/precisebank/keeper/burn.go +++ b/x/precisebank/keeper/burn.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/kava-labs/kava/x/precisebank/types" ) @@ -52,12 +53,23 @@ func (k Keeper) BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) err } } - // No more processing required if no ExtendedCoinDenom - if extendedAmount.IsZero() { + // Only burn extended coin if the amount is positive + if extendedAmount.IsPositive() { + if err := k.burnExtendedCoin(ctx, moduleName, extendedAmount); err != nil { + return err + } + } + + fullEmissionCoin := types.SumExtendedCoin(amt) + if fullEmissionCoin.IsZero() { return nil } - return k.burnExtendedCoin(ctx, moduleName, extendedAmount) + ctx.EventManager().EmitEvent( + banktypes.NewCoinBurnEvent(acc.GetAddress(), sdk.NewCoins(fullEmissionCoin)), + ) + + return nil } // burnExtendedCoin burns the fractional amount of the ExtendedCoinDenom from the module account. diff --git a/x/precisebank/keeper/burn_integration_test.go b/x/precisebank/keeper/burn_integration_test.go index c9b5b67f..c569f69e 100644 --- a/x/precisebank/keeper/burn_integration_test.go +++ b/x/precisebank/keeper/burn_integration_test.go @@ -7,6 +7,7 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/kava-labs/kava/x/precisebank/keeper" "github.com/kava-labs/kava/x/precisebank/testutil" @@ -209,6 +210,28 @@ func (suite *burnIntegrationTestSuite) TestBurnCoins() { res, stop := allInvariantsFn(suite.Ctx) suite.Require().False(stop, "invariant should not be broken") suite.Require().Empty(res, "unexpected invariant message: %s", res) + + intCoinAmt := tt.burnCoins.AmountOf(types.IntegerCoinDenom). + Mul(types.ConversionFactor()) + + fraCoinAmt := tt.burnCoins.AmountOf(types.ExtendedCoinDenom) + + totalExtCoinAmt := intCoinAmt.Add(fraCoinAmt) + + events := suite.Ctx.EventManager().Events() + extendedEvent := banktypes.NewCoinBurnEvent( + recipientAddr, + sdk.NewCoins(sdk.NewCoin( + types.ExtendedCoinDenom, + totalExtCoinAmt, + )), + ) + + if totalExtCoinAmt.IsZero() { + suite.Require().NotContains(events, extendedEvent) + } else { + suite.Require().Contains(events, extendedEvent) + } }) } } diff --git a/x/precisebank/keeper/mint.go b/x/precisebank/keeper/mint.go index 98633a0f..878e6e91 100644 --- a/x/precisebank/keeper/mint.go +++ b/x/precisebank/keeper/mint.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/kava-labs/kava/x/precisebank/types" ) @@ -55,12 +56,23 @@ func (k Keeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) err } } - // No more processing required if no ExtendedCoinDenom - if extendedAmount.IsZero() { + // Only mint extended coin if the amount is positive + if extendedAmount.IsPositive() { + if err := k.mintExtendedCoin(ctx, moduleName, extendedAmount); err != nil { + return err + } + } + + fullEmissionCoin := types.SumExtendedCoin(amt) + if fullEmissionCoin.IsZero() { return nil } - return k.mintExtendedCoin(ctx, moduleName, extendedAmount) + ctx.EventManager().EmitEvent( + banktypes.NewCoinMintEvent(acc.GetAddress(), sdk.NewCoins(fullEmissionCoin)), + ) + + return nil } // mintExtendedCoin manages the minting of only extended coins. This also diff --git a/x/precisebank/keeper/mint_integration_test.go b/x/precisebank/keeper/mint_integration_test.go index 4b99b01c..c0442bea 100644 --- a/x/precisebank/keeper/mint_integration_test.go +++ b/x/precisebank/keeper/mint_integration_test.go @@ -334,6 +334,30 @@ func (suite *mintIntegrationTestSuite) TestMintCoins() { allInvariantsFn := keeper.AllInvariants(suite.Keeper) msg, stop := allInvariantsFn(suite.Ctx) suite.Require().Falsef(stop, "invariant should not be broken: %s", msg) + + // Get event for minted coins + intCoinAmt := mt.mintAmount.AmountOf(types.IntegerCoinDenom). + Mul(types.ConversionFactor()) + + fraCoinAmt := mt.mintAmount.AmountOf(types.ExtendedCoinDenom) + + totalExtCoinAmt := intCoinAmt.Add(fraCoinAmt) + + // Check for mint event + events := suite.Ctx.EventManager().Events() + extendedEvent := banktypes.NewCoinMintEvent( + recipientAddr, + sdk.NewCoins(sdk.NewCoin( + types.ExtendedCoinDenom, + totalExtCoinAmt, + )), + ) + + if totalExtCoinAmt.IsZero() { + suite.Require().NotContains(events, extendedEvent) + } else { + suite.Require().Contains(events, extendedEvent) + } } }) } diff --git a/x/precisebank/keeper/send.go b/x/precisebank/keeper/send.go index ea121301..c68b1b84 100644 --- a/x/precisebank/keeper/send.go +++ b/x/precisebank/keeper/send.go @@ -8,15 +8,18 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/kava-labs/kava/x/precisebank/types" ) // IsSendEnabledCoins uses the parent x/bank keeper to check the coins provided // and returns an ErrSendDisabled if any of the coins are not configured for -// sending. Returns nil if sending is enabled for all provided coin +// sending. Returns nil if sending is enabled for all provided coin. +// Note: This method is not used directly by x/evm, but is still required as +// part of authtypes.BankKeeper. x/evm uses auth methods that require this +// interface. func (k Keeper) IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error { - // TODO: This does not actually seem to be used by x/evm, so it should be - // removed from the expected_interface in x/evm. + // Simply pass through to x/bank return k.bk.IsSendEnabledCoins(ctx, coins...) } @@ -52,13 +55,35 @@ func (k Keeper) SendCoins( } } - // If there is no extended coin amount, we are done - if extendedCoinAmount.IsZero() { + // Send the extended coin amount through x/precisebank + if extendedCoinAmount.IsPositive() { + if err := k.sendExtendedCoins(ctx, from, to, extendedCoinAmount); err != nil { + return err + } + } + + // Get a full extended coin amount (passthrough integer + fractional) ONLY + // for event attributes. + fullEmissionCoin := types.SumExtendedCoin(amt) + + // If no passthrough integer nor fractional coins, then no event emission. + // We also want to emit the event with the whole equivalent extended coin + // if only integer coins are sent. + if fullEmissionCoin.IsZero() { return nil } - // Send the extended coin amount through x/precisebank - return k.sendExtendedCoins(ctx, from, to, extendedCoinAmount) + // Emit transfer event of extended denom for the FULL equivalent value. + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + banktypes.EventTypeTransfer, + sdk.NewAttribute(banktypes.AttributeKeyRecipient, to.String()), + sdk.NewAttribute(banktypes.AttributeKeySender, from.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, fullEmissionCoin.String()), + ), + }) + + return nil } // sendExtendedCoins transfers amt extended coins from a sending account to a diff --git a/x/precisebank/keeper/send_integration_test.go b/x/precisebank/keeper/send_integration_test.go index ee207a6a..ed62d2b2 100644 --- a/x/precisebank/keeper/send_integration_test.go +++ b/x/precisebank/keeper/send_integration_test.go @@ -5,6 +5,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/precisebank/keeper" @@ -400,25 +401,25 @@ func (suite *sendIntegrationTestSuite) TestSendCoins() { // Convert send amount coins to extended coins. i.e. if send coins // includes ukava, convert it so that its the equivalent akava // amount so its easier to compare. Compare extended coins only. - sendAmountExtended := tt.giveAmt + sendAmountFullExtended := tt.giveAmt sendAmountInteger := tt.giveAmt.AmountOf(types.IntegerCoinDenom) if !sendAmountInteger.IsZero() { integerCoin := sdk.NewCoin(types.IntegerCoinDenom, sendAmountInteger) - sendAmountExtended = sendAmountExtended.Sub(integerCoin) + sendAmountFullExtended = sendAmountFullExtended.Sub(integerCoin) // Add equivalent extended coin extendedCoinAmount := sendAmountInteger.Mul(types.ConversionFactor()) extendedCoin := sdk.NewCoin(types.ExtendedCoinDenom, extendedCoinAmount) - sendAmountExtended = sendAmountExtended.Add(extendedCoin) + sendAmountFullExtended = sendAmountFullExtended.Add(extendedCoin) } suite.Require().Equal( - senderBalBefore.Sub(sendAmountExtended...), + senderBalBefore.Sub(sendAmountFullExtended...), senderBalAfter, ) suite.Require().Equal( - recipientBalBefore.Add(sendAmountExtended...), + recipientBalBefore.Add(sendAmountFullExtended...), recipientBalAfter, ) @@ -426,6 +427,28 @@ func (suite *sendIntegrationTestSuite) TestSendCoins() { res, stop := invariantFn(suite.Ctx) suite.Require().False(stop, "invariants should not stop") suite.Require().Empty(res, "invariants should not return any messages") + + // Check events + + // FULL akava equivalent, including ukava only/mixed sends + sendExtendedAmount := sdk.NewCoin( + types.ExtendedCoinDenom, + sendAmountFullExtended.AmountOf(types.ExtendedCoinDenom), + ) + + // No extra events if not sending akava + if sendExtendedAmount.IsZero() { + return + } + + extendedEvent := sdk.NewEvent( + banktypes.EventTypeTransfer, + sdk.NewAttribute(banktypes.AttributeKeyRecipient, recipient.String()), + sdk.NewAttribute(banktypes.AttributeKeySender, sender.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, sendExtendedAmount.String()), + ) + + suite.Require().Contains(suite.Ctx.EventManager().Events(), extendedEvent) }) } } diff --git a/x/precisebank/types/extended_balance.go b/x/precisebank/types/extended_balance.go new file mode 100644 index 00000000..d4f674db --- /dev/null +++ b/x/precisebank/types/extended_balance.go @@ -0,0 +1,25 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SumExtendedCoin returns a sdk.Coin of extended coin denomination +// with all integer and fractional amounts combined. e.g. if amount contains +// both coins of integer denom and extended denom, this will return the total +// amount in extended coins. This is intended to get the full value to emit in +// events. +func SumExtendedCoin(amt sdk.Coins) sdk.Coin { + // ukava converted to akava + integerAmount := amt.AmountOf(IntegerCoinDenom).Mul(conversionFactor) + // akava as is + extendedAmount := amt.AmountOf(ExtendedCoinDenom) + + // total of ukava and akava amounts + fullEmissionAmount := integerAmount.Add(extendedAmount) + + return sdk.NewCoin( + ExtendedCoinDenom, + fullEmissionAmount, + ) +} diff --git a/x/precisebank/types/extended_balance_test.go b/x/precisebank/types/extended_balance_test.go new file mode 100644 index 00000000..9ce8ed21 --- /dev/null +++ b/x/precisebank/types/extended_balance_test.go @@ -0,0 +1,48 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/precisebank/types" + "github.com/stretchr/testify/require" +) + +func TestSumExtendedCoin(t *testing.T) { + tests := []struct { + name string + amt sdk.Coins + want sdk.Coin + }{ + { + "empty", + sdk.NewCoins(), + sdk.NewCoin(types.ExtendedCoinDenom, sdk.ZeroInt()), + }, + { + "only integer", + sdk.NewCoins(sdk.NewInt64Coin(types.IntegerCoinDenom, 100)), + sdk.NewCoin(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(100)), + }, + { + "only extended", + sdk.NewCoins(sdk.NewInt64Coin(types.ExtendedCoinDenom, 100)), + sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)), + }, + { + "integer and extended", + sdk.NewCoins( + sdk.NewInt64Coin(types.IntegerCoinDenom, 100), + sdk.NewInt64Coin(types.ExtendedCoinDenom, 100), + ), + sdk.NewCoin(types.ExtendedCoinDenom, types.ConversionFactor().MulRaw(100).AddRaw(100)), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + extVal := types.SumExtendedCoin(tt.amt) + require.Equal(t, tt.want, extVal) + }) + } +}