diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index f4d353411f8..8fef3f1a9ad 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -87,7 +87,8 @@ func NewAppWithOptions(cfg *AppOptions) (abci.Application, error) { // Construct keepers. acctKpr := auth.NewAccountKeeper(mainKey, ProtoGnoAccount) - bankKpr := bank.NewBankKeeper(acctKpr) + tckpr := bank.NewTotalCoinKeeper(mainKey) + bankKpr := bank.NewBankKeeper(acctKpr, tckpr) // XXX: Embed this ? stdlibsDir := filepath.Join(cfg.GnoRootDir, "gnovm", "stdlibs") diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index de58cd3e8ae..af6a5a2d3b8 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -37,7 +37,7 @@ func (bnk *SDKBanker) SendCoins(b32from, b32to crypto.Bech32Address, amt std.Coi } func (bnk *SDKBanker) TotalCoin(denom string) int64 { - panic("not yet implemented") + return bnk.vmk.bank.TotalCoin(bnk.ctx, denom) } func (bnk *SDKBanker) IssueCoin(b32addr crypto.Bech32Address, denom string, amount int64) { diff --git a/gno.land/pkg/sdk/vm/common_test.go b/gno.land/pkg/sdk/vm/common_test.go index 6dd8050d6b6..9e3ab012497 100644 --- a/gno.land/pkg/sdk/vm/common_test.go +++ b/gno.land/pkg/sdk/vm/common_test.go @@ -45,7 +45,8 @@ func _setupTestEnv(cacheStdlibs bool) testEnv { ctx := sdk.NewContext(sdk.RunTxModeDeliver, ms, &bft.Header{ChainID: "test-chain-id"}, log.NewNoopLogger()) acck := authm.NewAccountKeeper(iavlCapKey, std.ProtoBaseAccount) - bank := bankm.NewBankKeeper(acck) + tck := bankm.NewTotalCoinKeeper(iavlCapKey) + bank := bankm.NewBankKeeper(acck, tck) stdlibsDir := filepath.Join("..", "..", "..", "..", "gnovm", "stdlibs") vmk := NewVMKeeper(baseCapKey, iavlCapKey, acck, bank, stdlibsDir, 100_000_000) diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 3fea714b142..c71c5e8f8b9 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -547,17 +547,24 @@ func trimTrailingSpaces(result string) string { type testBanker struct { coinTable map[crypto.Bech32Address]std.Coins + totalCoin map[string]int64 } func newTestBanker(args ...interface{}) *testBanker { coinTable := make(map[crypto.Bech32Address]std.Coins) + totalCoin := make(map[string]int64) if len(args)%2 != 0 { panic("newTestBanker requires even number of arguments; addr followed by coins") } for i := 0; i < len(args); i += 2 { addr := args[i].(crypto.Bech32Address) - amount := args[i+1].(std.Coins) - coinTable[addr] = amount + coins := args[i+1].(std.Coins) + coinTable[addr] = coins + for _, coin := range coins { + if coin.IsValid() { + totalCoin[coin.Denom] += coin.Amount + } + } } return &testBanker{ coinTable: coinTable, @@ -591,17 +598,19 @@ func (tb *testBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { } func (tb *testBanker) TotalCoin(denom string) int64 { - panic("not yet implemented") + return tb.totalCoin[denom] } func (tb *testBanker) IssueCoin(addr crypto.Bech32Address, denom string, amt int64) { coins, _ := tb.coinTable[addr] sum := coins.Add(std.Coins{{denom, amt}}) tb.coinTable[addr] = sum + tb.totalCoin[denom] += amt } func (tb *testBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amt int64) { coins, _ := tb.coinTable[addr] rest := coins.Sub(std.Coins{{denom, amt}}) tb.coinTable[addr] = rest + tb.totalCoin[denom] -= amt } diff --git a/tm2/pkg/sdk/bank/common_test.go b/tm2/pkg/sdk/bank/common_test.go index 95b93157165..b7a99e79811 100644 --- a/tm2/pkg/sdk/bank/common_test.go +++ b/tm2/pkg/sdk/bank/common_test.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" "github.com/gnolang/gno/tm2/pkg/store/iavl" @@ -18,6 +19,7 @@ type testEnv struct { ctx sdk.Context bank BankKeeper acck auth.AccountKeeper + tck TotalCoinKeeper } func setupTestEnv() testEnv { @@ -34,7 +36,8 @@ func setupTestEnv() testEnv { authCapKey, std.ProtoBaseAccount, ) - bank := NewBankKeeper(acck) + tck := NewTotalCoinKeeper(authCapKey) + bank := NewBankKeeper(acck, tck) return testEnv{ctx: ctx, bank: bank, acck: acck} } diff --git a/tm2/pkg/sdk/bank/consts.go b/tm2/pkg/sdk/bank/consts.go index 4284a44c940..f721f1f0a07 100644 --- a/tm2/pkg/sdk/bank/consts.go +++ b/tm2/pkg/sdk/bank/consts.go @@ -2,4 +2,12 @@ package bank const ( ModuleName = "bank" + + // TotalCoinStoreKeyPrefix prefix for total-coin-by-denom store + TotalCoinStoreKeyPrefix = "/tc/" ) + +// TotalCoinStoreKey turn an denom to key used to get it from the total coin store +func TotalCoinStoreKey(denom string) []byte { + return []byte(TotalCoinStoreKeyPrefix + denom) +} diff --git a/tm2/pkg/sdk/bank/keeper.go b/tm2/pkg/sdk/bank/keeper.go index 5d3699c99ef..05cec9a3a73 100644 --- a/tm2/pkg/sdk/bank/keeper.go +++ b/tm2/pkg/sdk/bank/keeper.go @@ -4,10 +4,12 @@ import ( "fmt" "log/slog" + "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/sdk/auth" "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/store" ) // bank.Keeper defines a module interface that facilitates the transfer of @@ -31,13 +33,15 @@ type BankKeeper struct { ViewKeeper acck auth.AccountKeeper + tck TotalCoinKeeper } // NewBankKeeper returns a new BankKeeper. -func NewBankKeeper(acck auth.AccountKeeper) BankKeeper { +func NewBankKeeper(acck auth.AccountKeeper, tck TotalCoinKeeper) BankKeeper { return BankKeeper{ - ViewKeeper: NewViewKeeper(acck), + ViewKeeper: NewViewKeeper(acck, tck), acck: acck, + tck: tck, } } @@ -134,9 +138,18 @@ func (bank BankKeeper) SubtractCoins(ctx sdk.Context, addr crypto.Address, amt s ) return nil, err } + err := bank.SetCoins(ctx, addr, newCoins) + if err != nil { + return nil, err + } - return newCoins, err + err = bank.tck.decreaseTotalCoin(ctx, newCoins) + if err != nil { + return nil, err + } + + return newCoins, nil } // AddCoins adds amt to the coins at the addr. @@ -155,7 +168,16 @@ func (bank BankKeeper) AddCoins(ctx sdk.Context, addr crypto.Address, amt std.Co } err := bank.SetCoins(ctx, addr, newCoins) - return newCoins, err + if err != nil { + return nil, err + } + + err = bank.tck.increaseTotalCoin(ctx, newCoins) + if err != nil { + return nil, err + } + + return newCoins, nil } // SetCoins sets the coins at the addr. @@ -175,6 +197,7 @@ func (bank BankKeeper) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Co } bank.acck.SetAccount(ctx, acc) + return nil } @@ -186,6 +209,7 @@ func (bank BankKeeper) SetCoins(ctx sdk.Context, addr crypto.Address, amt std.Co type ViewKeeperI interface { GetCoins(ctx sdk.Context, addr crypto.Address) std.Coins HasCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool + TotalCoin(ctx sdk.Context, denom string) int64 } var _ ViewKeeperI = ViewKeeper{} @@ -193,11 +217,12 @@ var _ ViewKeeperI = ViewKeeper{} // ViewKeeper implements a read only keeper implementation of ViewKeeperI. type ViewKeeper struct { acck auth.AccountKeeper + tck TotalCoinKeeper } // NewViewKeeper returns a new ViewKeeper. -func NewViewKeeper(acck auth.AccountKeeper) ViewKeeper { - return ViewKeeper{acck: acck} +func NewViewKeeper(acck auth.AccountKeeper, tck TotalCoinKeeper) ViewKeeper { + return ViewKeeper{acck: acck, tck: tck} } // Logger returns a module-specific logger. @@ -218,3 +243,103 @@ func (view ViewKeeper) GetCoins(ctx sdk.Context, addr crypto.Address) std.Coins func (view ViewKeeper) HasCoins(ctx sdk.Context, addr crypto.Address, amt std.Coins) bool { return view.GetCoins(ctx, addr).IsAllGTE(amt) } + +func (view ViewKeeper) TotalCoin(ctx sdk.Context, denom string) int64 { + return view.tck.totalCoin(ctx, denom) +} + +// TotalCoinKeeper manages the total amount of coins for various denominations. +type TotalCoinKeeper struct { + key store.StoreKey +} + +// NewTotalCoinKeeper returns a new TotalCoinKeeper. +func NewTotalCoinKeeper(key store.StoreKey) TotalCoinKeeper { + return TotalCoinKeeper{ + key: key, + } +} + +// increaseTotalCoin increases the total coin amounts for the specified denominations. +func (tck TotalCoinKeeper) increaseTotalCoin(ctx sdk.Context, coins std.Coins) error { + stor := ctx.Store(tck.key) + + for _, coin := range coins { + bz := stor.Get(TotalCoinStoreKey(coin.Denom)) + if bz == nil { + continue + } + + oldTotalCoin := tck.decodeTotalCoin(bz) + + newTotalCoin := oldTotalCoin.Add(coin) + err := tck.setTotalCoin(ctx, newTotalCoin) + if err != nil { + return err + } + } + + return nil +} + +// decreaseTotalCoin decreases the total coin amounts for the specified denominations. +func (tck TotalCoinKeeper) decreaseTotalCoin(ctx sdk.Context, coins std.Coins) error { + stor := ctx.Store(tck.key) + + for _, coin := range coins { + bz := stor.Get(TotalCoinStoreKey(coin.Denom)) + if bz == nil { + return fmt.Errorf("denomination %s not found", coin.Denom) + } + + oldTotalCoin := tck.decodeTotalCoin(bz) + newTotalCoin := oldTotalCoin.SubUnsafe(coin) + if !newTotalCoin.IsValid() { + err := std.ErrInsufficientCoins( + fmt.Sprintf("insufficient account funds; %s < %s", oldTotalCoin, coin), + ) + return err + } + + err := tck.setTotalCoin(ctx, newTotalCoin) + if err != nil { + return err + } + } + + return nil +} + +// GetTotalCoin returns the total coin for a given denomination. +func (tck TotalCoinKeeper) totalCoin(ctx sdk.Context, denom string) int64 { + stor := ctx.Store(tck.key) + bz := stor.Get(TotalCoinStoreKey(denom)) + if bz == nil { + return 0 + } + + totalCoin := tck.decodeTotalCoin(bz) + + return totalCoin.Amount +} + +// SetTotalCoin sets the total coin amount for a given denomination. +func (tck TotalCoinKeeper) setTotalCoin(ctx sdk.Context, totalCoin std.Coin) error { + stor := ctx.Store(tck.key) + bz, err := amino.MarshalAny(totalCoin) + if err != nil { + return err + } + stor.Set(TotalCoinStoreKey(totalCoin.Denom), bz) + return nil +} + +// decodeTotalCoin decodes the total coin from a byte slice. +func (tck TotalCoinKeeper) decodeTotalCoin(bz []byte) std.Coin { + var coin std.Coin + err := amino.Unmarshal(bz, &coin) + if err != nil { + panic(err) + } + return coin +} diff --git a/tm2/pkg/sdk/bank/keeper_test.go b/tm2/pkg/sdk/bank/keeper_test.go index 59b4c12689c..c64136e3875 100644 --- a/tm2/pkg/sdk/bank/keeper_test.go +++ b/tm2/pkg/sdk/bank/keeper_test.go @@ -95,7 +95,7 @@ func TestBankKeeper(t *testing.T) { env := setupTestEnv() ctx := env.ctx - bank := NewBankKeeper(env.acck) + bank := NewBankKeeper(env.acck, env.tck) addr := crypto.AddressFromPreimage([]byte("addr1")) addr2 := crypto.AddressFromPreimage([]byte("addr2")) @@ -142,7 +142,7 @@ func TestViewKeeper(t *testing.T) { env := setupTestEnv() ctx := env.ctx - view := NewViewKeeper(env.acck) + view := NewViewKeeper(env.acck, env.tck) addr := crypto.AddressFromPreimage([]byte("addr1")) acc := env.acck.NewAccountWithAddress(ctx, addr)