Skip to content

Commit

Permalink
feat(x/precisebank): Return full balances in GetBalance(), add Spenda…
Browse files Browse the repository at this point in the history
…bleCoin method (#1957)

Change GetBalance() to return full balances instead of spendable to align behavior with x/bank. Add SpendableCoin() method with support of akava for use in x/evm.
  • Loading branch information
drklee3 authored Jun 29, 2024
1 parent 60a8073 commit 23ce7d8
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 27 deletions.
35 changes: 31 additions & 4 deletions x/precisebank/keeper/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,42 @@ func (k Keeper) GetBalance(
return k.bk.GetBalance(ctx, addr, denom)
}

// x/bank for integer balance - spendable balance only
spendableCoins := k.bk.SpendableCoins(ctx, addr)
integerAmount := spendableCoins.AmountOf(types.IntegerCoinDenom)
// x/bank for integer balance - full balance, including locked
integerCoins := k.bk.GetBalance(ctx, addr, types.IntegerCoinDenom)

// x/precisebank for fractional balance
fractionalAmount := k.GetFractionalBalance(ctx, addr)

// (Integer * ConversionFactor) + Fractional
fullAmount := integerAmount.
fullAmount := integerCoins.
Amount.
Mul(types.ConversionFactor()).
Add(fractionalAmount)

return sdk.NewCoin(types.ExtendedCoinDenom, fullAmount)
}

// SpendableCoins returns the total balances of spendable coins for an account
// by address. If the account has no spendable coins, an empty Coins slice is
// returned.
func (k Keeper) SpendableCoin(
ctx sdk.Context,
addr sdk.AccAddress,
denom string,
) sdk.Coin {
// Pass through to x/bank for denoms except ExtendedCoinDenom
if denom != types.ExtendedCoinDenom {
return k.bk.SpendableCoin(ctx, addr, denom)
}

// x/bank for integer balance - excluding locked
integerCoin := k.bk.SpendableCoin(ctx, addr, types.IntegerCoinDenom)

// x/precisebank for fractional balance
fractionalAmount := k.GetFractionalBalance(ctx, addr)

// Spendable = (Integer * ConversionFactor) + Fractional
fullAmount := integerCoin.Amount.
Mul(types.ConversionFactor()).
Add(fractionalAmount)

Expand Down
131 changes: 131 additions & 0 deletions x/precisebank/keeper/view_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"

"github.com/kava-labs/kava/x/precisebank/testutil"
"github.com/kava-labs/kava/x/precisebank/types"
"github.com/stretchr/testify/suite"
)

type viewIntegrationTestSuite struct {
testutil.Suite
}

func (suite *viewIntegrationTestSuite) SetupTest() {
suite.Suite.SetupTest()
}

func TestViewIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(viewIntegrationTestSuite))
}

func (suite *viewIntegrationTestSuite) TestKeeper_SpendableCoin() {
tests := []struct {
name string
giveDenom string // queried denom for balance

giveBankBal sdk.Coins // full balance
giveFractionalBal sdkmath.Int // stored fractional balance for giveAddr
giveLockedCoins sdk.Coins // locked coins

wantSpendableBal sdk.Coin
}{
{
"extended denom, no fractional - locked coins",
types.ExtendedCoinDenom,
// queried bank balance in ukava when querying for akava
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
sdkmath.ZeroInt(),
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
// (integer + fractional) - locked
sdk.NewCoin(
types.ExtendedCoinDenom,
types.ConversionFactor().MulRaw(1000-10),
),
},
{
"extended denom, with fractional - locked coins",
types.ExtendedCoinDenom,
// queried bank balance in ukava when querying for akava
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
sdkmath.NewInt(5000),
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
sdk.NewCoin(
types.ExtendedCoinDenom,
// (integer - locked) + fractional
types.ConversionFactor().MulRaw(1000-10).AddRaw(5000),
),
},
{
"non-extended denom - ukava returns ukava",
types.IntegerCoinDenom,
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
sdkmath.ZeroInt(),
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(990)),
},
{
"non-extended denom, with fractional - ukava returns ukava",
types.IntegerCoinDenom,
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(1000))),
// does not affect balance
sdkmath.NewInt(100),
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(10))),
sdk.NewCoin(types.IntegerCoinDenom, sdk.NewInt(990)),
},
}

for _, tt := range tests {
suite.Run(tt.name, func() {
suite.SetupTest()

addr := sdk.AccAddress([]byte("test-address"))

suite.MintToAccount(addr, tt.giveBankBal)

// Set fractional balance in store before query
suite.Keeper.SetFractionalBalance(suite.Ctx, addr, tt.giveFractionalBal)

// Add some locked coins
acc := suite.AccountKeeper.GetAccount(suite.Ctx, addr)
if acc == nil {
acc = authtypes.NewBaseAccount(addr, nil, 0, 0)
}

vestingAcc := vestingtypes.NewPeriodicVestingAccount(
acc.(*authtypes.BaseAccount),
tt.giveLockedCoins,
suite.Ctx.BlockTime().Unix(),
vestingtypes.Periods{
vestingtypes.Period{
Length: 100,
Amount: tt.giveLockedCoins,
},
},
)
suite.AccountKeeper.SetAccount(suite.Ctx, vestingAcc)

fetchedLockedCoins := vestingAcc.LockedCoins(suite.Ctx.BlockTime())
suite.Require().Equal(
tt.giveLockedCoins,
fetchedLockedCoins,
"locked coins should be matching at current block time",
)

spendableCoinsWithLocked := suite.Keeper.SpendableCoin(suite.Ctx, addr, tt.giveDenom)

suite.Require().Equalf(
tt.wantSpendableBal,
spendableCoinsWithLocked,
"expected spendable coins of denom %s",
tt.giveDenom,
)
})
}
}
115 changes: 112 additions & 3 deletions x/precisebank/keeper/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ func TestKeeper_GetBalance(t *testing.T) {
if tt.giveDenom == types.ExtendedCoinDenom {
// No balance pass through
tk.bk.EXPECT().
SpendableCoins(tk.ctx, addr).
RunAndReturn(func(_ sdk.Context, _ sdk.AccAddress) sdk.Coins {
return tt.giveBankBal
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 {
Expand All @@ -115,3 +116,111 @@ func TestKeeper_GetBalance(t *testing.T) {
})
}
}

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 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)
})
}
}
2 changes: 1 addition & 1 deletion x/precisebank/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type BankKeeper interface {
IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error
GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin
GetSupply(ctx sdk.Context, denom string) sdk.Coin
SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins
SpendableCoin(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin

BlockedAddr(addr sdk.AccAddress) bool

Expand Down
37 changes: 18 additions & 19 deletions x/precisebank/types/mocks/MockBankKeeper.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 23ce7d8

Please sign in to comment.