mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 10:37:26 +00:00 
			
		
		
		
	Module reserve represents fractional balances, so it should be hidden to consumers to not have a misleading total balance that doubles the fractional balances. This modifies GetBalance() and SpendableCoin() to always return zero coins when fetching the reserve address balance for fractional amounts.
		
			
				
	
	
		
			297 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package keeper_test
 | 
						|
 | 
						|
import (
 | 
						|
	"testing"
 | 
						|
 | 
						|
	sdkmath "cosmossdk.io/math"
 | 
						|
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						|
	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 | 
						|
	"github.com/kava-labs/kava/x/precisebank/types"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
)
 | 
						|
 | 
						|
func TestKeeper_GetBalance(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name      string
 | 
						|
		giveDenom string // queried denom for balance
 | 
						|
 | 
						|
		giveBankBal       sdk.Coins   // mocked bank balance for giveAddr
 | 
						|
		giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
 | 
						|
 | 
						|
		wantBal sdk.Coin
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			"extended denom - no fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			// queried bank balance in ukava when querying for akava
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdkmath.ZeroInt(),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - with fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_100)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - only fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			// no coins in bank, only fractional balance
 | 
						|
			sdk.NewCoins(),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - max fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			types.ConversionFactor().SubRaw(1),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_999_999_999_999)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"non-extended denom - ukava returns ukava",
 | 
						|
			types.IntegerCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdk.ZeroInt(),
 | 
						|
			sdk.NewCoin("ukava", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"non-extended denom - unaffected by fractional balance",
 | 
						|
			"ukava",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin("ukava", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"unrelated denom - no fractional",
 | 
						|
			"busd",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
 | 
						|
			sdk.ZeroInt(),
 | 
						|
			sdk.NewCoin("busd", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"unrelated denom - unaffected by fractional balance",
 | 
						|
			"busd",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin("busd", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			tk := NewMockedTestData(t)
 | 
						|
			addr := sdk.AccAddress([]byte("test-address"))
 | 
						|
 | 
						|
			// Set fractional balance in store before query
 | 
						|
			tk.keeper.SetFractionalBalance(tk.ctx, addr, tt.giveFractionalBal)
 | 
						|
 | 
						|
			// Checks address if its a reserve denom
 | 
						|
			if tt.giveDenom == types.ExtendedCoinDenom {
 | 
						|
				tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
 | 
						|
					Return(authtypes.NewModuleAddress(types.ModuleName)).
 | 
						|
					Once()
 | 
						|
			}
 | 
						|
 | 
						|
			if tt.giveDenom == types.ExtendedCoinDenom {
 | 
						|
				// No balance pass through
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					GetBalance(tk.ctx, addr, types.IntegerCoinDenom).
 | 
						|
					RunAndReturn(func(_ sdk.Context, _ sdk.AccAddress, _ string) sdk.Coin {
 | 
						|
						amt := tt.giveBankBal.AmountOf(types.IntegerCoinDenom)
 | 
						|
						return sdk.NewCoin(types.IntegerCoinDenom, amt)
 | 
						|
					}).
 | 
						|
					Once()
 | 
						|
			} else {
 | 
						|
				// Pass through to x/bank for denoms except ExtendedCoinDenom
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					GetBalance(tk.ctx, addr, tt.giveDenom).
 | 
						|
					RunAndReturn(func(ctx sdk.Context, aa sdk.AccAddress, s string) sdk.Coin {
 | 
						|
						require.Equal(t, s, tt.giveDenom, "unexpected denom passed to x/bank.GetBalance")
 | 
						|
 | 
						|
						return sdk.NewCoin(tt.giveDenom, tt.giveBankBal.AmountOf(s))
 | 
						|
					}).
 | 
						|
					Once()
 | 
						|
			}
 | 
						|
 | 
						|
			bal := tk.keeper.GetBalance(tk.ctx, addr, tt.giveDenom)
 | 
						|
			require.Equal(t, tt.wantBal, bal)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestKeeper_SpendableCoin(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name      string
 | 
						|
		giveDenom string // queried denom for balance
 | 
						|
 | 
						|
		giveBankBal       sdk.Coins   // mocked bank balance for giveAddr
 | 
						|
		giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
 | 
						|
 | 
						|
		wantBal sdk.Coin
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			"extended denom - no fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			// queried bank balance in ukava when querying for akava
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdkmath.ZeroInt(),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - with fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_000_000_000_100)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - only fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			// no coins in bank, only fractional balance
 | 
						|
			sdk.NewCoins(),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(100)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"extended denom - max fractional balance",
 | 
						|
			types.ExtendedCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			types.ConversionFactor().SubRaw(1),
 | 
						|
			// integer + fractional
 | 
						|
			sdk.NewCoin(types.ExtendedCoinDenom, sdk.NewInt(1000_999_999_999_999)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"non-extended denom - ukava returns ukava",
 | 
						|
			types.IntegerCoinDenom,
 | 
						|
			sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
 | 
						|
			sdk.ZeroInt(),
 | 
						|
			sdk.NewCoin("ukava", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"non-extended denom - unaffected by fractional balance",
 | 
						|
			"ukava",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin("ukava", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"unrelated denom - no fractional",
 | 
						|
			"busd",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
 | 
						|
			sdk.ZeroInt(),
 | 
						|
			sdk.NewCoin("busd", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
		{
 | 
						|
			"unrelated denom - unaffected by fractional balance",
 | 
						|
			"busd",
 | 
						|
			sdk.NewCoins(sdk.NewCoin("busd", sdk.NewInt(1000))),
 | 
						|
			sdkmath.NewInt(100),
 | 
						|
			sdk.NewCoin("busd", sdk.NewInt(1000)),
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			tk := NewMockedTestData(t)
 | 
						|
			addr := sdk.AccAddress([]byte("test-address"))
 | 
						|
 | 
						|
			// Set fractional balance in store before query
 | 
						|
			tk.keeper.SetFractionalBalance(tk.ctx, addr, tt.giveFractionalBal)
 | 
						|
 | 
						|
			// If its a reserve denom, module address is checked
 | 
						|
			if tt.giveDenom == types.ExtendedCoinDenom {
 | 
						|
				tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
 | 
						|
					Return(authtypes.NewModuleAddress(types.ModuleName)).
 | 
						|
					Once()
 | 
						|
			}
 | 
						|
 | 
						|
			if tt.giveDenom == types.ExtendedCoinDenom {
 | 
						|
				// No balance pass through
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					SpendableCoin(tk.ctx, addr, types.IntegerCoinDenom).
 | 
						|
					RunAndReturn(func(_ sdk.Context, _ sdk.AccAddress, _ string) sdk.Coin {
 | 
						|
						amt := tt.giveBankBal.AmountOf(types.IntegerCoinDenom)
 | 
						|
						return sdk.NewCoin(types.IntegerCoinDenom, amt)
 | 
						|
					}).
 | 
						|
					Once()
 | 
						|
			} else {
 | 
						|
				// Pass through to x/bank for denoms except ExtendedCoinDenom
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					SpendableCoin(tk.ctx, addr, tt.giveDenom).
 | 
						|
					RunAndReturn(func(ctx sdk.Context, aa sdk.AccAddress, s string) sdk.Coin {
 | 
						|
						require.Equal(t, s, tt.giveDenom, "unexpected denom passed to x/bank.GetBalance")
 | 
						|
 | 
						|
						return sdk.NewCoin(tt.giveDenom, tt.giveBankBal.AmountOf(s))
 | 
						|
					}).
 | 
						|
					Once()
 | 
						|
			}
 | 
						|
 | 
						|
			bal := tk.keeper.SpendableCoin(tk.ctx, addr, tt.giveDenom)
 | 
						|
			require.Equal(t, tt.wantBal, bal)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestHiddenReserve(t *testing.T) {
 | 
						|
	// Reserve balances should not be shown to consumers of x/precisebank, as it
 | 
						|
	// represents the fractional balances of accounts.
 | 
						|
 | 
						|
	tk := NewMockedTestData(t)
 | 
						|
 | 
						|
	moduleAddr := authtypes.NewModuleAddress(types.ModuleName)
 | 
						|
 | 
						|
	// No mock bankkeeper expectations, which means the zero coin is returned
 | 
						|
	// directly for reserve address. So the mock bankkeeper doesn't need to have
 | 
						|
	// a handler for getting underlying balance.
 | 
						|
 | 
						|
	tests := []struct {
 | 
						|
		name            string
 | 
						|
		denom           string
 | 
						|
		expectedBalance sdk.Coin
 | 
						|
	}{
 | 
						|
		{"akava", types.ExtendedCoinDenom, sdk.NewCoin(types.ExtendedCoinDenom, sdkmath.ZeroInt())},
 | 
						|
		{"ukava", types.IntegerCoinDenom, sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(1))},
 | 
						|
		{"unrelated denom", "cat", sdk.NewCoin("cat", sdkmath.ZeroInt())},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			// 2 calls for GetBalance and SpendableCoin, only for reserve coins
 | 
						|
			if tt.denom == "akava" {
 | 
						|
				tk.ak.EXPECT().GetModuleAddress(types.ModuleName).
 | 
						|
					Return(moduleAddr).
 | 
						|
					Twice()
 | 
						|
			} else {
 | 
						|
				// Passthrough to x/bank for non-reserve denoms
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					GetBalance(tk.ctx, moduleAddr, tt.denom).
 | 
						|
					Return(sdk.NewCoin(tt.denom, sdkmath.ZeroInt())).
 | 
						|
					Once()
 | 
						|
 | 
						|
				tk.bk.EXPECT().
 | 
						|
					SpendableCoin(tk.ctx, moduleAddr, tt.denom).
 | 
						|
					Return(sdk.NewCoin(tt.denom, sdkmath.ZeroInt())).
 | 
						|
					Once()
 | 
						|
			}
 | 
						|
 | 
						|
			// GetBalance should return zero balance for reserve address
 | 
						|
			coin := tk.keeper.GetBalance(tk.ctx, moduleAddr, tt.denom)
 | 
						|
			require.Equal(t, tt.denom, coin.Denom)
 | 
						|
			require.Equal(t, sdkmath.ZeroInt(), coin.Amount)
 | 
						|
 | 
						|
			// SpendableCoin should return zero balance for reserve address
 | 
						|
			spendableCoin := tk.keeper.SpendableCoin(tk.ctx, moduleAddr, tt.denom)
 | 
						|
			require.Equal(t, tt.denom, spendableCoin.Denom)
 | 
						|
			require.Equal(t, sdkmath.ZeroInt(), spendableCoin.Amount)
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |