diff --git a/types/denom.go b/types/denom.go index 0e8d716a2..33f42a917 100644 --- a/types/denom.go +++ b/types/denom.go @@ -53,6 +53,14 @@ func GetBaseDenom() (string, error) { return baseDenom, nil } +func MustGetBaseDenom() string { + bd, err := GetBaseDenom() + if err != nil { + panic(err) + } + return bd +} + // ConvertCoin attempts to convert a coin to a given denomination. If the given // denomination is invalid or if neither denomination is registered, an error // is returned. diff --git a/x/bank/keeper/invariants.go b/x/bank/keeper/invariants.go index aa5751da0..12ebf782a 100644 --- a/x/bank/keeper/invariants.go +++ b/x/bank/keeper/invariants.go @@ -37,6 +37,14 @@ func NonnegativeBalanceInvariant(k ViewKeeper) sdk.Invariant { return false }) + k.IterateAllWeiBalances(ctx, func(addr sdk.AccAddress, balance sdk.Int) bool { + if balance.IsNegative() { + count++ + msg += fmt.Sprintf("\t%s has a negative wei balance of %s\n", addr, balance) + } + + return false + }) broken := count != 0 @@ -51,6 +59,7 @@ func NonnegativeBalanceInvariant(k ViewKeeper) sdk.Invariant { func TotalSupply(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { expectedTotal := sdk.Coins{} + weiTotal := sdk.NewInt(0) supply, _, err := k.GetPaginatedTotalSupply(ctx, &query.PageRequest{Limit: query.MaxLimit}) if err != nil { @@ -67,6 +76,23 @@ func TotalSupply(k Keeper) sdk.Invariant { expectedTotal = expectedTotal.Add(coin) return false }) + k.IterateAllWeiBalances(ctx, func(addr sdk.AccAddress, balance sdk.Int) bool { + weiTotal = weiTotal.Add(balance) + return false + }) + weiInUsei, weiRemainder := SplitUseiWeiAmount(weiTotal) + if !weiRemainder.IsZero() { + return sdk.FormatInvariant(types.ModuleName, "total supply", + fmt.Sprintf( + "\twei remainder: %v\n", + weiRemainder)), true + } + baseDenom, err := sdk.GetBaseDenom() + if err == nil { + expectedTotal = expectedTotal.Add(sdk.NewCoin(baseDenom, weiInUsei)) + } else if !weiInUsei.IsZero() { + return sdk.FormatInvariant(types.ModuleName, "total supply", "non-zero wei balance without base denom"), true + } broken := !expectedTotal.IsEqual(supply) diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 25600f3f7..72d4060cf 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -76,7 +76,6 @@ func (suite *IntegrationTestSuite) initKeepersWithmAccPerms(blockedAddrs map[str appCodec := simapp.MakeTestEncodingConfig().Marshaler maccPerms[holder] = nil - maccPerms[types.WeiEscrowName] = nil maccPerms[authtypes.Burner] = []string{authtypes.Burner} maccPerms[authtypes.Minter] = []string{authtypes.Minter} maccPerms[multiPerm] = []string{authtypes.Burner, authtypes.Minter, authtypes.Staking} @@ -112,7 +111,8 @@ func (suite *IntegrationTestSuite) SetupTest() { func (suite *IntegrationTestSuite) TestSendCoinsAndWei() { ctx := suite.ctx require := suite.Require() - authKeeper, keeper := suite.initKeepersWithmAccPerms(make(map[string]bool)) + sdk.RegisterDenom(sdk.DefaultBondDenom, sdk.OneDec()) + _, keeper := suite.initKeepersWithmAccPerms(make(map[string]bool)) amt := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100))) require.NoError(keeper.MintCoins(ctx, authtypes.Minter, amt)) addr1 := sdk.AccAddress([]byte("addr1_______________")) @@ -120,42 +120,35 @@ func (suite *IntegrationTestSuite) TestSendCoinsAndWei() { addr3 := sdk.AccAddress([]byte("addr3_______________")) require.NoError(keeper.SendCoinsFromModuleToAccount(ctx, authtypes.Minter, addr1, amt)) // should no-op if sending zero - require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr2, nil, sdk.DefaultBondDenom, sdk.ZeroInt(), sdk.ZeroInt())) + require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr2, sdk.ZeroInt(), sdk.ZeroInt())) require.Equal(sdk.ZeroInt(), keeper.GetWeiBalance(ctx, addr1)) require.Equal(sdk.ZeroInt(), keeper.GetWeiBalance(ctx, addr2)) require.Equal(sdk.NewInt(100), keeper.GetBalance(ctx, addr1, sdk.DefaultBondDenom).Amount) require.Equal(sdk.ZeroInt(), keeper.GetBalance(ctx, addr2, sdk.DefaultBondDenom).Amount) - require.Equal(sdk.ZeroInt(), keeper.GetBalance(ctx, authKeeper.GetModuleAddress(types.WeiEscrowName), sdk.DefaultBondDenom).Amount) // should just do usei send if wei is zero - require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, nil, sdk.DefaultBondDenom, sdk.NewInt(50), sdk.ZeroInt())) + require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, sdk.NewInt(50), sdk.ZeroInt())) require.Equal(sdk.ZeroInt(), keeper.GetWeiBalance(ctx, addr1)) require.Equal(sdk.ZeroInt(), keeper.GetWeiBalance(ctx, addr3)) require.Equal(sdk.NewInt(50), keeper.GetBalance(ctx, addr1, sdk.DefaultBondDenom).Amount) require.Equal(sdk.NewInt(50), keeper.GetBalance(ctx, addr3, sdk.DefaultBondDenom).Amount) - require.Equal(sdk.ZeroInt(), keeper.GetBalance(ctx, authKeeper.GetModuleAddress(types.WeiEscrowName), sdk.DefaultBondDenom).Amount) - // should return error if wei amount overflows - require.Error(keeper.SendCoinsAndWei(ctx, addr1, addr2, nil, sdk.DefaultBondDenom, sdk.ZeroInt(), sdk.NewInt(1_000_000_000_000))) // sender gets escrowed one usei, recipient does not get redeemed - require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr2, nil, sdk.DefaultBondDenom, sdk.NewInt(1), sdk.NewInt(1))) + require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr2, sdk.NewInt(1), sdk.NewInt(1))) require.Equal(sdk.NewInt(999_999_999_999), keeper.GetWeiBalance(ctx, addr1)) require.Equal(sdk.OneInt(), keeper.GetWeiBalance(ctx, addr2)) require.Equal(sdk.NewInt(48), keeper.GetBalance(ctx, addr1, sdk.DefaultBondDenom).Amount) require.Equal(sdk.OneInt(), keeper.GetBalance(ctx, addr2, sdk.DefaultBondDenom).Amount) - require.Equal(sdk.OneInt(), keeper.GetBalance(ctx, authKeeper.GetModuleAddress(types.WeiEscrowName), sdk.DefaultBondDenom).Amount) // sender does not get escrowed due to sufficient wei balance, recipient does not get redeemed - require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, nil, sdk.DefaultBondDenom, sdk.NewInt(1), sdk.NewInt(999_999_999_999))) + require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, sdk.NewInt(1), sdk.NewInt(999_999_999_999))) require.Equal(sdk.ZeroInt(), keeper.GetWeiBalance(ctx, addr1)) require.Equal(sdk.NewInt(999_999_999_999), keeper.GetWeiBalance(ctx, addr3)) require.Equal(sdk.NewInt(47), keeper.GetBalance(ctx, addr1, sdk.DefaultBondDenom).Amount) require.Equal(sdk.NewInt(51), keeper.GetBalance(ctx, addr3, sdk.DefaultBondDenom).Amount) - require.Equal(sdk.OneInt(), keeper.GetBalance(ctx, authKeeper.GetModuleAddress(types.WeiEscrowName), sdk.DefaultBondDenom).Amount) // sender gets escrowed and recipient gets redeemed - require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, nil, sdk.DefaultBondDenom, sdk.NewInt(1), sdk.NewInt(2))) + require.NoError(keeper.SendCoinsAndWei(ctx, addr1, addr3, sdk.NewInt(1), sdk.NewInt(2))) require.Equal(sdk.NewInt(999_999_999_998), keeper.GetWeiBalance(ctx, addr1)) require.Equal(sdk.NewInt(1), keeper.GetWeiBalance(ctx, addr3)) require.Equal(sdk.NewInt(45), keeper.GetBalance(ctx, addr1, sdk.DefaultBondDenom).Amount) require.Equal(sdk.NewInt(53), keeper.GetBalance(ctx, addr3, sdk.DefaultBondDenom).Amount) - require.Equal(sdk.OneInt(), keeper.GetBalance(ctx, authKeeper.GetModuleAddress(types.WeiEscrowName), sdk.DefaultBondDenom).Amount) } func (suite *IntegrationTestSuite) TestSupply() { diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index b08728e05..c9fe7a410 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -1,8 +1,6 @@ package keeper import ( - "errors" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" "github.com/cosmos/cosmos-sdk/telemetry" @@ -20,11 +18,11 @@ type SendKeeper interface { InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsWithoutAccCreation(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int, wei sdk.Int) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error SubUnlockedCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, checkNeg bool) error AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, checkNeg bool) error - SubWei(ctx sdk.Context, addr sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int) error - AddWei(ctx sdk.Context, addr sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int) error + SubWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int) error + AddWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int) error GetParams(ctx sdk.Context) types.Params SetParams(ctx sdk.Context, params types.Params) @@ -36,7 +34,7 @@ type SendKeeper interface { } var _ SendKeeper = (*BaseSendKeeper)(nil) -var MaxWeiBalance sdk.Int = sdk.NewInt(1_000_000_000_000) +var OneUseiInWei sdk.Int = sdk.NewInt(1_000_000_000_000) // BaseSendKeeper only allows transfers between accounts without the possibility of // creating coins. It implements the SendKeeper interface. @@ -332,75 +330,59 @@ func (k BaseSendKeeper) BlockedAddr(addr sdk.AccAddress) bool { return k.blockedAddrs[addr.String()] } -func (k BaseSendKeeper) SubWei(ctx sdk.Context, addr sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int) error { +func (k BaseSendKeeper) SubWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int) error { if amt.Equal(sdk.ZeroInt()) { return nil } - if amt.GTE(MaxWeiBalance) { - return errors.New("cannot send more than 10^12 wei") + currentWeiBalance := k.GetWeiBalance(ctx, addr) + if amt.LTE(currentWeiBalance) { + // no need to change usei balance + return k.setWeiBalance(ctx, addr, currentWeiBalance.Sub(amt)) } - escrow := customEscrow - if escrow == nil { - escrow = k.ak.GetModuleAddress(types.WeiEscrowName) + currentUseiBalance := k.GetBalance(ctx, addr, sdk.MustGetBaseDenom()).Amount + currentAggregatedBalance := currentUseiBalance.Mul(OneUseiInWei).Add(currentWeiBalance) + postAggregatedbalance := currentAggregatedBalance.Sub(amt) + if postAggregatedbalance.IsNegative() { + return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "%swei is smaller than %swei", currentAggregatedBalance, amt) } - currentWeiBalance := k.GetWeiBalance(ctx, addr) - postWeiBalance := currentWeiBalance.Sub(amt) - if postWeiBalance.GTE(sdk.ZeroInt()) { - if err := k.setWeiBalance(ctx, addr, postWeiBalance); err != nil { - return err - } - } else { - if err := k.setWeiBalance(ctx, addr, MaxWeiBalance.Add(postWeiBalance)); err != nil { - // postWeiBalanceFrom is negative - return err - } - // need to send one sei to escrow because wei balance is insufficient - if err := k.sendCoinsWithoutAccCreation(ctx, addr, escrow, sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt())), false); err != nil { - return err - } + useiBalance, weiBalance := SplitUseiWeiAmount(postAggregatedbalance) + if err := k.setBalance(ctx, addr, sdk.NewCoin(sdk.MustGetBaseDenom(), useiBalance), true); err != nil { + return err } - return nil + return k.setWeiBalance(ctx, addr, weiBalance) } -func (k BaseSendKeeper) AddWei(ctx sdk.Context, addr sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int) error { +func (k BaseSendKeeper) AddWei(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Int) error { if amt.Equal(sdk.ZeroInt()) { return nil } - if amt.GTE(MaxWeiBalance) { - return errors.New("cannot send more than 10^12 wei") - } - escrow := customEscrow - if escrow == nil { - escrow = k.ak.GetModuleAddress(types.WeiEscrowName) - } currentWeiBalance := k.GetWeiBalance(ctx, addr) postWeiBalance := currentWeiBalance.Add(amt) - if postWeiBalance.LT(MaxWeiBalance) { - if err := k.setWeiBalance(ctx, addr, postWeiBalance); err != nil { - return err - } - } else { - if err := k.setWeiBalance(ctx, addr, postWeiBalance.Sub(MaxWeiBalance)); err != nil { - return err - } - // need to redeem one sei from escrow because wei balance overflowed - one := sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt())) - if err := k.sendCoinsWithoutAccCreation(ctx, escrow, addr, one, false); err != nil { - return err - } + if postWeiBalance.LT(OneUseiInWei) { + // no need to change usei balance + return k.setWeiBalance(ctx, addr, postWeiBalance) } - return nil + currentUseiBalance := k.GetBalance(ctx, addr, sdk.MustGetBaseDenom()).Amount + useiCredit, weiBalance := SplitUseiWeiAmount(postWeiBalance) + if err := k.setBalance(ctx, addr, sdk.NewCoin(sdk.MustGetBaseDenom(), currentUseiBalance.Add(useiCredit)), true); err != nil { + return err + } + return k.setWeiBalance(ctx, addr, weiBalance) } -func (k BaseSendKeeper) SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, customEscrow sdk.AccAddress, denom string, amt sdk.Int, wei sdk.Int) error { - if err := k.SubWei(ctx, from, customEscrow, denom, wei); err != nil { +func (k BaseSendKeeper) SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error { + if err := k.SubWei(ctx, from, wei); err != nil { return err } - if err := k.AddWei(ctx, to, customEscrow, denom, wei); err != nil { + if err := k.AddWei(ctx, to, wei); err != nil { return err } if amt.GT(sdk.ZeroInt()) { - return k.SendCoinsWithoutAccCreation(ctx, from, to, sdk.NewCoins(sdk.NewCoin(denom, amt))) + return k.SendCoinsWithoutAccCreation(ctx, from, to, sdk.NewCoins(sdk.NewCoin(sdk.MustGetBaseDenom(), amt))) } return nil } + +func SplitUseiWeiAmount(amt sdk.Int) (sdk.Int, sdk.Int) { + return amt.Quo(OneUseiInWei), amt.Mod(OneUseiInWei) +} diff --git a/x/bank/keeper/view.go b/x/bank/keeper/view.go index 7f835ab2b..3dbe99fd5 100644 --- a/x/bank/keeper/view.go +++ b/x/bank/keeper/view.go @@ -30,6 +30,7 @@ type ViewKeeper interface { IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(coin sdk.Coin) (stop bool)) IterateAllBalances(ctx sdk.Context, cb func(address sdk.AccAddress, coin sdk.Coin) (stop bool)) + IterateAllWeiBalances(ctx sdk.Context, cb func(sdk.AccAddress, sdk.Int) bool) } // BaseViewKeeper implements a read only keeper implementation of ViewKeeper. @@ -247,3 +248,21 @@ func (k BaseViewKeeper) GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk. } return *res } + +func (k BaseViewKeeper) IterateAllWeiBalances(ctx sdk.Context, cb func(sdk.AccAddress, sdk.Int) bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.WeiBalancesPrefix) + + iterator := store.Iterator(nil, nil) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + val := new(sdk.Int) + if err := val.Unmarshal(iterator.Value()); err != nil { + // should never happen + panic(err) + } + if cb(iterator.Key(), *val) { + break + } + } +} diff --git a/x/bank/types/key.go b/x/bank/types/key.go index 146b30de1..97b8f1268 100644 --- a/x/bank/types/key.go +++ b/x/bank/types/key.go @@ -21,8 +21,6 @@ const ( // QuerierRoute defines the module's query routing key QuerierRoute = ModuleName - - WeiEscrowName = "weiescrow" ) // KVStore keys