feat(x/precisebank): Emit events for send/mint/burn (#1955)

Emits the **total** akava amount for both ukava and akava send/mint/burns. If both akava,ukava are sent (not possible via x/evm nor cosmos messages but still an edge case), then the sum is emitted. No other denoms are emitted by x/precisebank as they will be emitted by the underlying x/bank
This commit is contained in:
drklee3 2024-06-27 19:40:17 -07:00 committed by GitHub
parent 2d07988994
commit 60a8073574
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 210 additions and 18 deletions

View File

@ -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.

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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)
}
}
})
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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,
)
}

View File

@ -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)
})
}
}