mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 00:47:27 +00:00 
			
		
		
		
	feat(x/precisebank): Add keeper methods for store (#1912)
- Add store methods to get/set/delete/etc account fractional balances & remainder amount - Add invariants to ensure stored state is correct
This commit is contained in:
		
							parent
							
								
									d66b7d2705
								
							
						
					
					
						commit
						4ff43eb270
					
				
							
								
								
									
										109
									
								
								x/precisebank/keeper/fractional_balance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								x/precisebank/keeper/fractional_balance.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/store/prefix"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetFractionalBalance returns the fractional balance for an address.
 | 
			
		||||
func (k *Keeper) GetFractionalBalance(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	address sdk.AccAddress,
 | 
			
		||||
) (sdkmath.Int, bool) {
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
 | 
			
		||||
 | 
			
		||||
	bz := store.Get(types.FractionalBalanceKey(address))
 | 
			
		||||
	if bz == nil {
 | 
			
		||||
		return sdkmath.ZeroInt(), false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var bal sdkmath.Int
 | 
			
		||||
	if err := bal.Unmarshal(bz); err != nil {
 | 
			
		||||
		panic(fmt.Errorf("failed to unmarshal fractional balance: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return bal, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFractionalBalance sets the fractional balance for an address.
 | 
			
		||||
func (k *Keeper) SetFractionalBalance(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	address sdk.AccAddress,
 | 
			
		||||
	amount sdkmath.Int,
 | 
			
		||||
) {
 | 
			
		||||
	if address.Empty() {
 | 
			
		||||
		panic(errors.New("address cannot be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if amount.IsZero() {
 | 
			
		||||
		k.DeleteFractionalBalance(ctx, address)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the fractional balance is valid before setting it. Use the
 | 
			
		||||
	// NewFractionalAmountFromInt wrapper to use its Validate() method.
 | 
			
		||||
	if err := types.NewFractionalAmountFromInt(amount).Validate(); err != nil {
 | 
			
		||||
		panic(fmt.Errorf("amount is invalid: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
 | 
			
		||||
 | 
			
		||||
	amountBytes, err := amount.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Errorf("failed to marshal fractional balance: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store.Set(types.FractionalBalanceKey(address), amountBytes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteFractionalBalance deletes the fractional balance for an address.
 | 
			
		||||
func (k *Keeper) DeleteFractionalBalance(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	address sdk.AccAddress,
 | 
			
		||||
) {
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
 | 
			
		||||
	store.Delete(types.FractionalBalanceKey(address))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IterateFractionalBalances iterates over all fractional balances in the store
 | 
			
		||||
// and performs a callback function.
 | 
			
		||||
func (k *Keeper) IterateFractionalBalances(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	cb func(address sdk.AccAddress, amount sdkmath.Int) (stop bool),
 | 
			
		||||
) {
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(k.storeKey), types.FractionalBalancePrefix)
 | 
			
		||||
 | 
			
		||||
	iterator := store.Iterator(nil, nil)
 | 
			
		||||
	defer iterator.Close()
 | 
			
		||||
 | 
			
		||||
	for ; iterator.Valid(); iterator.Next() {
 | 
			
		||||
		address := sdk.AccAddress(iterator.Key())
 | 
			
		||||
 | 
			
		||||
		var amount sdkmath.Int
 | 
			
		||||
		if err := amount.Unmarshal(iterator.Value()); err != nil {
 | 
			
		||||
			panic(fmt.Errorf("failed to unmarshal fractional balance: %w", err))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if cb(address, amount) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetTotalSumFractionalBalances returns the sum of all fractional balances.
 | 
			
		||||
func (k *Keeper) GetTotalSumFractionalBalances(ctx sdk.Context) sdkmath.Int {
 | 
			
		||||
	sum := sdkmath.ZeroInt()
 | 
			
		||||
 | 
			
		||||
	k.IterateFractionalBalances(ctx, func(_ sdk.AccAddress, amount sdkmath.Int) bool {
 | 
			
		||||
		sum = sum.Add(amount)
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return sum
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										189
									
								
								x/precisebank/keeper/fractional_balance_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								x/precisebank/keeper/fractional_balance_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,189 @@
 | 
			
		||||
package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSetGetFractionalBalance(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	addr := sdk.AccAddress([]byte("test-address"))
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
		address     sdk.AccAddress
 | 
			
		||||
		amount      sdkmath.Int
 | 
			
		||||
		setPanicMsg string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"valid - min amount",
 | 
			
		||||
			addr,
 | 
			
		||||
			sdkmath.NewInt(1),
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - positive amount",
 | 
			
		||||
			addr,
 | 
			
		||||
			sdkmath.NewInt(100),
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - max amount",
 | 
			
		||||
			addr,
 | 
			
		||||
			types.ConversionFactor().SubRaw(1),
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - zero amount (deletes)",
 | 
			
		||||
			addr,
 | 
			
		||||
			sdkmath.ZeroInt(),
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - negative amount",
 | 
			
		||||
			addr,
 | 
			
		||||
			sdkmath.NewInt(-1),
 | 
			
		||||
			"amount is invalid: non-positive amount -1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - over max amount",
 | 
			
		||||
			addr,
 | 
			
		||||
			types.ConversionFactor(),
 | 
			
		||||
			"amount is invalid: amount 1000000000000 exceeds max of 999999999999",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt := tt
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if tt.setPanicMsg != "" {
 | 
			
		||||
				require.PanicsWithError(t, tt.setPanicMsg, func() {
 | 
			
		||||
					k.SetFractionalBalance(ctx, tt.address, tt.amount)
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			require.NotPanics(t, func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, tt.address, tt.amount)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			// If its zero balance, check it was deleted
 | 
			
		||||
			if tt.amount.IsZero() {
 | 
			
		||||
				_, exists := k.GetFractionalBalance(ctx, tt.address)
 | 
			
		||||
				require.False(t, exists)
 | 
			
		||||
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			gotAmount, exists := k.GetFractionalBalance(ctx, tt.address)
 | 
			
		||||
			require.True(t, exists)
 | 
			
		||||
			require.Equal(t, tt.amount, gotAmount)
 | 
			
		||||
 | 
			
		||||
			// Delete balance
 | 
			
		||||
			k.DeleteFractionalBalance(ctx, tt.address)
 | 
			
		||||
 | 
			
		||||
			_, exists = k.GetFractionalBalance(ctx, tt.address)
 | 
			
		||||
			require.False(t, exists)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetFractionalBalance_InvalidAddr(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	require.PanicsWithError(
 | 
			
		||||
		t,
 | 
			
		||||
		"address cannot be empty",
 | 
			
		||||
		func() {
 | 
			
		||||
			k.SetFractionalBalance(ctx, sdk.AccAddress{}, sdkmath.NewInt(100))
 | 
			
		||||
		},
 | 
			
		||||
		"setting balance with empty address should panic",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetFractionalBalance_ZeroDeletes(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	addr := sdk.AccAddress([]byte("test-address"))
 | 
			
		||||
 | 
			
		||||
	// Set balance
 | 
			
		||||
	k.SetFractionalBalance(ctx, addr, sdkmath.NewInt(100))
 | 
			
		||||
 | 
			
		||||
	bal, exists := k.GetFractionalBalance(ctx, addr)
 | 
			
		||||
	require.True(t, exists)
 | 
			
		||||
	require.Equal(t, sdkmath.NewInt(100), bal)
 | 
			
		||||
 | 
			
		||||
	// Set zero balance
 | 
			
		||||
	k.SetFractionalBalance(ctx, addr, sdkmath.ZeroInt())
 | 
			
		||||
 | 
			
		||||
	_, exists = k.GetFractionalBalance(ctx, addr)
 | 
			
		||||
	require.False(t, exists)
 | 
			
		||||
 | 
			
		||||
	// Set zero balance again on non-existent balance
 | 
			
		||||
	require.NotPanics(
 | 
			
		||||
		t,
 | 
			
		||||
		func() {
 | 
			
		||||
			k.SetFractionalBalance(ctx, addr, sdkmath.ZeroInt())
 | 
			
		||||
		},
 | 
			
		||||
		"deleting non-existent balance should not panic",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIterateFractionalBalances(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	addrs := []sdk.AccAddress{}
 | 
			
		||||
 | 
			
		||||
	for i := 1; i < 10; i++ {
 | 
			
		||||
		addr := sdk.AccAddress([]byte{byte(i)})
 | 
			
		||||
		addrs = append(addrs, addr)
 | 
			
		||||
 | 
			
		||||
		// Set balance same as their address byte
 | 
			
		||||
		k.SetFractionalBalance(ctx, addr, sdkmath.NewInt(int64(i)))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	seenAddrs := []sdk.AccAddress{}
 | 
			
		||||
 | 
			
		||||
	k.IterateFractionalBalances(ctx, func(addr sdk.AccAddress, bal sdkmath.Int) bool {
 | 
			
		||||
		seenAddrs = append(seenAddrs, addr)
 | 
			
		||||
 | 
			
		||||
		// Balance is same as first address byte
 | 
			
		||||
		require.Equal(t, int64(addr.Bytes()[0]), bal.Int64())
 | 
			
		||||
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	require.ElementsMatch(t, addrs, seenAddrs, "all addresses should be seen")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetAggregateSumFractionalBalances(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	// Set balances from 1 to 10
 | 
			
		||||
	sum := sdkmath.ZeroInt()
 | 
			
		||||
	for i := 1; i < 10; i++ {
 | 
			
		||||
		addr := sdk.AccAddress([]byte{byte(i)})
 | 
			
		||||
		amt := sdkmath.NewInt(int64(i))
 | 
			
		||||
 | 
			
		||||
		sum = sum.Add(amt)
 | 
			
		||||
 | 
			
		||||
		require.NotPanics(t, func() {
 | 
			
		||||
			k.SetFractionalBalance(ctx, addr, amt)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gotSum := k.GetTotalSumFractionalBalances(ctx)
 | 
			
		||||
	require.Equal(t, sum, gotSum)
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,9 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
)
 | 
			
		||||
@ -11,11 +14,110 @@ func RegisterInvariants(
 | 
			
		||||
	k Keeper,
 | 
			
		||||
	bk types.BankKeeper,
 | 
			
		||||
) {
 | 
			
		||||
	ir.RegisterRoute(types.ModuleName, "balance-remainder-total", BalancedFractionalTotalInvariant(k))
 | 
			
		||||
	ir.RegisterRoute(types.ModuleName, "valid-fractional-balances", ValidFractionalAmountsInvariant(k))
 | 
			
		||||
	ir.RegisterRoute(types.ModuleName, "valid-remainder-amount", ValidRemainderAmountInvariant(k))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllInvariants runs all invariants of the X/precisebank module.
 | 
			
		||||
func AllInvariants(k Keeper) sdk.Invariant {
 | 
			
		||||
	return func(ctx sdk.Context) (string, bool) {
 | 
			
		||||
		res, stop := BalancedFractionalTotalInvariant(k)(ctx)
 | 
			
		||||
		if stop {
 | 
			
		||||
			return res, stop
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		res, stop = ValidFractionalAmountsInvariant(k)(ctx)
 | 
			
		||||
		if stop {
 | 
			
		||||
			return res, stop
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		res, stop = ValidRemainderAmountInvariant(k)(ctx)
 | 
			
		||||
		if stop {
 | 
			
		||||
			return res, stop
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidFractionalAmountsInvariant checks that all individual fractional
 | 
			
		||||
// balances are valid.
 | 
			
		||||
func ValidFractionalAmountsInvariant(k Keeper) sdk.Invariant {
 | 
			
		||||
	return func(ctx sdk.Context) (string, bool) {
 | 
			
		||||
		var (
 | 
			
		||||
			msg   string
 | 
			
		||||
			count int
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		k.IterateFractionalBalances(ctx, func(addr sdk.AccAddress, amount sdkmath.Int) bool {
 | 
			
		||||
			if err := types.NewFractionalAmountFromInt(amount).Validate(); err != nil {
 | 
			
		||||
				count++
 | 
			
		||||
				msg += fmt.Sprintf("\t%s has an invalid fractional amount of %s\n", addr, amount)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		broken := count != 0
 | 
			
		||||
 | 
			
		||||
		return sdk.FormatInvariant(
 | 
			
		||||
			types.ModuleName, "valid-fractional-balances",
 | 
			
		||||
			fmt.Sprintf("amount of invalid fractional balances found %d\n%s", count, msg),
 | 
			
		||||
		), broken
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidRemainderAmountInvariant checks that the remainder amount is valid.
 | 
			
		||||
func ValidRemainderAmountInvariant(k Keeper) sdk.Invariant {
 | 
			
		||||
	return func(ctx sdk.Context) (string, bool) {
 | 
			
		||||
		var (
 | 
			
		||||
			msg    string
 | 
			
		||||
			broken bool
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		remainderAmount := k.GetRemainderAmount(ctx)
 | 
			
		||||
 | 
			
		||||
		if !remainderAmount.IsZero() {
 | 
			
		||||
			// Only validate if non-zero, as zero is default value
 | 
			
		||||
			if err := types.NewFractionalAmountFromInt(remainderAmount).Validate(); err != nil {
 | 
			
		||||
				broken = true
 | 
			
		||||
				msg = fmt.Sprintf("remainder amount is invalid: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return sdk.FormatInvariant(
 | 
			
		||||
			types.ModuleName, "valid-remainder-amount",
 | 
			
		||||
			msg,
 | 
			
		||||
		), broken
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BalancedFractionalTotalInvariant checks that the sum of fractional balances
 | 
			
		||||
// and the remainder amount is divisible by the conversion factor without any
 | 
			
		||||
// leftover amount.
 | 
			
		||||
func BalancedFractionalTotalInvariant(k Keeper) sdk.Invariant {
 | 
			
		||||
	return func(ctx sdk.Context) (string, bool) {
 | 
			
		||||
		fractionalBalSum := k.GetTotalSumFractionalBalances(ctx)
 | 
			
		||||
		remainderAmount := k.GetRemainderAmount(ctx)
 | 
			
		||||
 | 
			
		||||
		total := fractionalBalSum.Add(remainderAmount)
 | 
			
		||||
		fractionalAmount := total.Mod(types.ConversionFactor())
 | 
			
		||||
 | 
			
		||||
		broken := false
 | 
			
		||||
		msg := ""
 | 
			
		||||
 | 
			
		||||
		if !fractionalAmount.IsZero() {
 | 
			
		||||
			broken = true
 | 
			
		||||
			msg = fmt.Sprintf(
 | 
			
		||||
				"(sum(FractionalBalances) + remainder) %% conversionFactor should be 0 but got %v",
 | 
			
		||||
				fractionalAmount,
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return sdk.FormatInvariant(
 | 
			
		||||
			types.ModuleName, "balance-remainder-total",
 | 
			
		||||
			msg,
 | 
			
		||||
		), broken
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										170
									
								
								x/precisebank/keeper/invariants_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								x/precisebank/keeper/invariants_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
			
		||||
package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/store/prefix"
 | 
			
		||||
	storetypes "github.com/cosmos/cosmos-sdk/store/types"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/keeper"
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestBalancedFractionalTotalInvariant(t *testing.T) {
 | 
			
		||||
	var ctx sdk.Context
 | 
			
		||||
	var k keeper.Keeper
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		setupFn    func()
 | 
			
		||||
		wantBroken bool
 | 
			
		||||
		wantMsg    string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"valid - empty state",
 | 
			
		||||
			func() {},
 | 
			
		||||
			false,
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - balances, 0 remainder",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - balances, non-zero remainder",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(1))
 | 
			
		||||
 | 
			
		||||
				k.SetRemainderAmount(ctx, sdkmath.OneInt())
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - balances, 0 remainder",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(1))
 | 
			
		||||
			},
 | 
			
		||||
			true,
 | 
			
		||||
			"precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 999999999999\n",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - invalid balances, non-zero (insufficient) remainder",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(2))
 | 
			
		||||
				k.SetRemainderAmount(ctx, sdkmath.OneInt())
 | 
			
		||||
			},
 | 
			
		||||
			true,
 | 
			
		||||
			"precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 999999999999\n",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - invalid balances, non-zero (excess) remainder",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2).SubRaw(2))
 | 
			
		||||
				k.SetRemainderAmount(ctx, sdkmath.NewInt(5))
 | 
			
		||||
			},
 | 
			
		||||
			true,
 | 
			
		||||
			"precisebank: balance-remainder-total invariant\n(sum(FractionalBalances) + remainder) % conversionFactor should be 0 but got 3\n",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			// Reset each time
 | 
			
		||||
			tk := NewTestKeeper()
 | 
			
		||||
			ctx, k = tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
			tt.setupFn()
 | 
			
		||||
 | 
			
		||||
			invariantFn := keeper.BalancedFractionalTotalInvariant(k)
 | 
			
		||||
			msg, broken := invariantFn(ctx)
 | 
			
		||||
 | 
			
		||||
			if tt.wantBroken {
 | 
			
		||||
				require.True(t, broken, "invariant should be broken but is not")
 | 
			
		||||
				require.Equal(t, tt.wantMsg, msg)
 | 
			
		||||
			} else {
 | 
			
		||||
				require.False(t, broken, "invariant should not be broken but is")
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidFractionalAmountsInvariant(t *testing.T) {
 | 
			
		||||
	var ctx sdk.Context
 | 
			
		||||
	var k keeper.Keeper
 | 
			
		||||
	var storeKey storetypes.StoreKey
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		setupFn    func()
 | 
			
		||||
		wantBroken bool
 | 
			
		||||
		wantMsg    string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"valid - empty state",
 | 
			
		||||
			func() {},
 | 
			
		||||
			false,
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid - valid balances",
 | 
			
		||||
			func() {
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
				k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
			"",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid - exceeds max balance",
 | 
			
		||||
			func() {
 | 
			
		||||
				// Requires manual store manipulation so it is unlikely to have
 | 
			
		||||
				// invalid state in practice. SetFractionalBalance will validate
 | 
			
		||||
				// before setting.
 | 
			
		||||
				addr := sdk.AccAddress{1}
 | 
			
		||||
				amount := types.ConversionFactor()
 | 
			
		||||
 | 
			
		||||
				store := prefix.NewStore(ctx.KVStore(storeKey), types.FractionalBalancePrefix)
 | 
			
		||||
 | 
			
		||||
				amountBytes, err := amount.Marshal()
 | 
			
		||||
				require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
				store.Set(types.FractionalBalanceKey(addr), amountBytes)
 | 
			
		||||
			},
 | 
			
		||||
			true,
 | 
			
		||||
			"precisebank: valid-fractional-balances invariant\namount of invalid fractional balances found 1\n\tkava1qy0xn7za has an invalid fractional amount of 1000000000000\n\n",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			// Reset each time
 | 
			
		||||
			tk := NewTestKeeper()
 | 
			
		||||
			ctx, k, storeKey = tk.ctx, tk.keeper, tk.storeKey
 | 
			
		||||
 | 
			
		||||
			tt.setupFn()
 | 
			
		||||
 | 
			
		||||
			invariantFn := keeper.ValidFractionalAmountsInvariant(k)
 | 
			
		||||
			msg, broken := invariantFn(ctx)
 | 
			
		||||
 | 
			
		||||
			if tt.wantBroken {
 | 
			
		||||
				require.True(t, broken, "invariant should be broken but is not")
 | 
			
		||||
				require.Equal(t, tt.wantMsg, msg)
 | 
			
		||||
			} else {
 | 
			
		||||
				require.False(t, broken, "invariant should not be broken but is")
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								x/precisebank/keeper/keeper_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								x/precisebank/keeper/keeper_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	storetypes "github.com/cosmos/cosmos-sdk/store/types"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/testutil"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/app"
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/keeper"
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// testKeeper defines necessary fields for testing keeper store methods that
 | 
			
		||||
// don't require a full app setup.
 | 
			
		||||
type testKeeper struct {
 | 
			
		||||
	ctx      sdk.Context
 | 
			
		||||
	keeper   keeper.Keeper
 | 
			
		||||
	storeKey *storetypes.KVStoreKey
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewTestKeeper() testKeeper {
 | 
			
		||||
	storeKey := sdk.NewKVStoreKey(types.ModuleName)
 | 
			
		||||
	// Not required by module, but needs to be non-nil for context
 | 
			
		||||
	tKey := sdk.NewTransientStoreKey("transient_test")
 | 
			
		||||
	ctx := testutil.DefaultContext(storeKey, tKey)
 | 
			
		||||
 | 
			
		||||
	tApp := app.NewTestApp()
 | 
			
		||||
	cdc := tApp.AppCodec()
 | 
			
		||||
	k := keeper.NewKeeper(cdc, storeKey)
 | 
			
		||||
 | 
			
		||||
	return testKeeper{
 | 
			
		||||
		ctx:      ctx,
 | 
			
		||||
		keeper:   k,
 | 
			
		||||
		storeKey: storeKey,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								x/precisebank/keeper/remainder_amount.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								x/precisebank/keeper/remainder_amount.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetRemainderAmount returns the internal remainder amount.
 | 
			
		||||
func (k *Keeper) GetRemainderAmount(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
) sdkmath.Int {
 | 
			
		||||
	store := ctx.KVStore(k.storeKey)
 | 
			
		||||
 | 
			
		||||
	bz := store.Get(types.RemainderBalanceKey)
 | 
			
		||||
	if bz == nil {
 | 
			
		||||
		return sdkmath.ZeroInt()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var bal sdkmath.Int
 | 
			
		||||
	if err := bal.Unmarshal(bz); err != nil {
 | 
			
		||||
		panic(fmt.Errorf("failed to unmarshal remainder amount: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return bal
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetRemainderAmount sets the internal remainder amount.
 | 
			
		||||
func (k *Keeper) SetRemainderAmount(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	amount sdkmath.Int,
 | 
			
		||||
) {
 | 
			
		||||
	// Prevent storing zero amounts. In practice, the remainder amount should
 | 
			
		||||
	// only be non-zero during transactions as mint and burns should net zero
 | 
			
		||||
	// due to only being used for EVM transfers.
 | 
			
		||||
	if amount.IsZero() {
 | 
			
		||||
		k.DeleteRemainderAmount(ctx)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the remainder is valid before setting it. Follows the same
 | 
			
		||||
	// validation as FractionalBalance with the same value range.
 | 
			
		||||
	if err := types.NewFractionalAmountFromInt(amount).Validate(); err != nil {
 | 
			
		||||
		panic(fmt.Errorf("remainder amount is invalid: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store := ctx.KVStore(k.storeKey)
 | 
			
		||||
 | 
			
		||||
	amountBytes, err := amount.Marshal()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(fmt.Errorf("failed to marshal remainder amount: %w", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store.Set(types.RemainderBalanceKey, amountBytes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteRemainderAmount deletes the internal remainder amount.
 | 
			
		||||
func (k *Keeper) DeleteRemainderAmount(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
) {
 | 
			
		||||
	store := ctx.KVStore(k.storeKey)
 | 
			
		||||
	store.Delete(types.RemainderBalanceKey)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								x/precisebank/keeper/remainder_amount_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								x/precisebank/keeper/remainder_amount_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetSetRemainderAmount(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k, storeKey := tk.ctx, tk.keeper, tk.storeKey
 | 
			
		||||
 | 
			
		||||
	// Set amount
 | 
			
		||||
	k.SetRemainderAmount(ctx, sdkmath.NewInt(100))
 | 
			
		||||
 | 
			
		||||
	amt := k.GetRemainderAmount(ctx)
 | 
			
		||||
	require.Equal(t, sdkmath.NewInt(100), amt)
 | 
			
		||||
 | 
			
		||||
	// Set zero balance
 | 
			
		||||
	k.SetRemainderAmount(ctx, sdkmath.ZeroInt())
 | 
			
		||||
 | 
			
		||||
	amt = k.GetRemainderAmount(ctx)
 | 
			
		||||
	require.Equal(t, sdkmath.ZeroInt(), amt)
 | 
			
		||||
 | 
			
		||||
	// Get directly from store to make sure it was actually deleted
 | 
			
		||||
	store := ctx.KVStore(storeKey)
 | 
			
		||||
	bz := store.Get(types.RemainderBalanceKey)
 | 
			
		||||
	require.Nil(t, bz)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInvalidRemainderAmount(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k := tk.ctx, tk.keeper
 | 
			
		||||
 | 
			
		||||
	// Set negative amount
 | 
			
		||||
	require.PanicsWithError(t, "remainder amount is invalid: non-positive amount -1", func() {
 | 
			
		||||
		k.SetRemainderAmount(ctx, sdkmath.NewInt(-1))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Set amount over max
 | 
			
		||||
	require.PanicsWithError(t, "remainder amount is invalid: amount 1000000000000 exceeds max of 999999999999", func() {
 | 
			
		||||
		k.SetRemainderAmount(ctx, types.ConversionFactor())
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteRemainderAmount(t *testing.T) {
 | 
			
		||||
	tk := NewTestKeeper()
 | 
			
		||||
	ctx, k, storeKey := tk.ctx, tk.keeper, tk.storeKey
 | 
			
		||||
 | 
			
		||||
	require.NotPanics(t, func() {
 | 
			
		||||
		k.DeleteRemainderAmount(ctx)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Set amount
 | 
			
		||||
	k.SetRemainderAmount(ctx, sdkmath.NewInt(100))
 | 
			
		||||
 | 
			
		||||
	amt := k.GetRemainderAmount(ctx)
 | 
			
		||||
	require.Equal(t, sdkmath.NewInt(100), amt)
 | 
			
		||||
 | 
			
		||||
	// Delete amount
 | 
			
		||||
	k.DeleteRemainderAmount(ctx)
 | 
			
		||||
 | 
			
		||||
	amt = k.GetRemainderAmount(ctx)
 | 
			
		||||
	require.Equal(t, sdkmath.ZeroInt(), amt)
 | 
			
		||||
 | 
			
		||||
	store := ctx.KVStore(storeKey)
 | 
			
		||||
	bz := store.Get(types.RemainderBalanceKey)
 | 
			
		||||
	require.Nil(t, bz)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								x/precisebank/types/fractional_amount.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								x/precisebank/types/fractional_amount.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	fmt "fmt"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FractionalAmount represents a fractional amount between the valid range of 1
 | 
			
		||||
// and maxFractionalAmount. This wraps an sdkmath.Int to provide additional
 | 
			
		||||
// validation methods so it can be re-used in multiple places.
 | 
			
		||||
type FractionalAmount struct {
 | 
			
		||||
	sdkmath.Int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFractionalAmountFromInt creates a new FractionalAmount from an sdkmath.Int.
 | 
			
		||||
func NewFractionalAmountFromInt(i sdkmath.Int) FractionalAmount {
 | 
			
		||||
	return FractionalAmount{i}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFractionalAmount creates a new FractionalAmount from an int64.
 | 
			
		||||
func NewFractionalAmount(i int64) FractionalAmount {
 | 
			
		||||
	return FractionalAmount{sdkmath.NewInt(i)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate checks if the FractionalAmount is valid.
 | 
			
		||||
func (f FractionalAmount) Validate() error {
 | 
			
		||||
	if f.IsNil() {
 | 
			
		||||
		return fmt.Errorf("nil amount")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !f.IsPositive() {
 | 
			
		||||
		return fmt.Errorf("non-positive amount %v", f)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.GT(maxFractionalAmount) {
 | 
			
		||||
		return fmt.Errorf("amount %v exceeds max of %v", f, maxFractionalAmount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@ -1,8 +1,6 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	fmt "fmt"
 | 
			
		||||
 | 
			
		||||
	sdkmath "cosmossdk.io/math"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
)
 | 
			
		||||
@ -40,17 +38,6 @@ func (fb FractionalBalance) Validate() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fb.Amount.IsNil() {
 | 
			
		||||
		return fmt.Errorf("nil amount")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !fb.Amount.IsPositive() {
 | 
			
		||||
		return fmt.Errorf("non-positive amount %v", fb.Amount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if fb.Amount.GT(maxFractionalAmount) {
 | 
			
		||||
		return fmt.Errorf("amount %v exceeds max of %v", fb.Amount, maxFractionalAmount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	// Validate the amount with the FractionalAmount wrapper
 | 
			
		||||
	return NewFractionalAmountFromInt(fb.Amount).Validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// ModuleName name that will be used throughout the module
 | 
			
		||||
	ModuleName = "precisebank"
 | 
			
		||||
@ -9,3 +11,18 @@ const (
 | 
			
		||||
	// RouterKey Top level router key
 | 
			
		||||
	RouterKey = ModuleName
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// key prefixes for store
 | 
			
		||||
var (
 | 
			
		||||
	FractionalBalancePrefix = []byte{0x01} // address -> fractional balance
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Keys for store that are not prefixed
 | 
			
		||||
var (
 | 
			
		||||
	RemainderBalanceKey = []byte{0x02} // fractional balance remainder
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FractionalBalanceKey returns a key from an address
 | 
			
		||||
func FractionalBalanceKey(address sdk.AccAddress) []byte {
 | 
			
		||||
	return address.Bytes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								x/precisebank/types/keys_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								x/precisebank/types/keys_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
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 TestFractionalBalanceKey(t *testing.T) {
 | 
			
		||||
	addr := sdk.AccAddress([]byte("test-address"))
 | 
			
		||||
 | 
			
		||||
	key := types.FractionalBalanceKey(addr)
 | 
			
		||||
	require.Equal(t, addr.Bytes(), key)
 | 
			
		||||
	require.Equal(t, addr, sdk.AccAddress(key), "key should be able to be converted back to address")
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user