diff --git a/app/app.go b/app/app.go index 3c05641d..13bcb96b 100644 --- a/app/app.go +++ b/app/app.go @@ -77,7 +77,7 @@ type LaconicApp struct { // laconic keepers AuctionKeeper *auctionkeeper.Keeper // (Use * as per ProvideModule implementation) - BondKeeper bondkeeper.Keeper + BondKeeper *bondkeeper.Keeper RegistryKeeper registrykeeper.Keeper // RegistryRecordKeeper registrykeeper.RecordKeeper diff --git a/x/auction/expected_keepers.go b/x/auction/expected_keeper.go similarity index 100% rename from x/auction/expected_keepers.go rename to x/auction/expected_keeper.go diff --git a/x/bond/expected_keeper.go b/x/bond/expected_keeper.go new file mode 100644 index 00000000..bba62a12 --- /dev/null +++ b/x/bond/expected_keeper.go @@ -0,0 +1,19 @@ +package bond + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BondUsageKeeper keep track of bond usage in other modules. +// Used to, for example, prevent deletion of a bond that's in use. +type BondUsageKeeper interface { + ModuleName() string + UsesBond(ctx sdk.Context, bondId string) bool +} + +// BondHooksWrapper is a wrapper for modules to inject BondUsageKeeper using depinject. +// Reference: https://github.com/cosmos/cosmos-sdk/tree/v0.50.3/core/appmodule#resolving-circular-dependencies +type BondHooksWrapper struct{ BondUsageKeeper } + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (BondHooksWrapper) IsOnePerModuleType() {} diff --git a/x/bond/keeper/keeper.go b/x/bond/keeper/keeper.go index ae5ae6d2..59cc68ba 100644 --- a/x/bond/keeper/keeper.go +++ b/x/bond/keeper/keeper.go @@ -49,7 +49,7 @@ type Keeper struct { bankKeeper bank.Keeper // Track bond usage in other cosmos-sdk modules (more like a usage tracker). - // usageKeepers []types.BondUsageKeeper + usageKeepers []bondtypes.BondUsageKeeper // State management Schema collections.Schema @@ -63,8 +63,7 @@ func NewKeeper( storeService store.KVStoreService, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper, - // usageKeepers []types.BondUsageKeeper, -) Keeper { +) *Keeper { sb := collections.NewSchemaBuilder(storeService) k := Keeper{ cdc: cdc, @@ -72,7 +71,7 @@ func NewKeeper( bankKeeper: bankKeeper, Params: collections.NewItem(sb, bondtypes.ParamsPrefix, "params", codec.CollValue[bondtypes.Params](cdc)), Bonds: collections.NewIndexedMap(sb, bondtypes.BondsPrefix, "bonds", collections.StringKey, codec.CollValue[bondtypes.Bond](cdc), newBondIndexes(sb)), - // usageKeepers: usageKeepers, + usageKeepers: nil, } schema, err := sb.Build() @@ -82,7 +81,15 @@ func NewKeeper( k.Schema = schema - return k + return &k +} + +func (k *Keeper) SetUsageKeepers(usageKeepers []bondtypes.BondUsageKeeper) { + if k.usageKeepers != nil { + panic("cannot set bond hooks twice") + } + + k.usageKeepers = usageKeepers } // BondId simplifies generation of bond Ids. @@ -318,13 +325,12 @@ func (k Keeper) CancelBond(ctx sdk.Context, id string, ownerAddress sdk.AccAddre return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Bond owner mismatch.") } - // TODO // Check if bond is used in other modules. - // for _, usageKeeper := range k.usageKeepers { - // if usageKeeper.UsesBond(ctx, id) { - // return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprintf("Bond in use by the '%s' module.", usageKeeper.ModuleName())) - // } - // } + for _, usageKeeper := range k.usageKeepers { + if usageKeeper.UsesBond(ctx, id) { + return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, fmt.Sprintf("Bond in use by the '%s' module.", usageKeeper.ModuleName())) + } + } // Move funds from the bond into the account. err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, bondtypes.ModuleName, ownerAddress, bond.Balance) diff --git a/x/bond/keeper/msg_server.go b/x/bond/keeper/msg_server.go index 3827e73b..2a28cb67 100644 --- a/x/bond/keeper/msg_server.go +++ b/x/bond/keeper/msg_server.go @@ -11,11 +11,11 @@ import ( var _ bond.MsgServer = msgServer{} type msgServer struct { - k Keeper + k *Keeper } // NewMsgServerImpl returns an implementation of the module MsgServer interface. -func NewMsgServerImpl(keeper Keeper) bond.MsgServer { +func NewMsgServerImpl(keeper *Keeper) bond.MsgServer { return &msgServer{k: keeper} } diff --git a/x/bond/keeper/query_server.go b/x/bond/keeper/query_server.go index 9f9a1033..7c0066da 100644 --- a/x/bond/keeper/query_server.go +++ b/x/bond/keeper/query_server.go @@ -13,11 +13,11 @@ import ( var _ bondtypes.QueryServer = queryServer{} type queryServer struct { - k Keeper + k *Keeper } // NewQueryServerImpl returns an implementation of the module QueryServer. -func NewQueryServerImpl(k Keeper) bondtypes.QueryServer { +func NewQueryServerImpl(k *Keeper) bondtypes.QueryServer { return queryServer{k} } diff --git a/x/bond/module/depinject.go b/x/bond/module/depinject.go index db48f83d..01d07b3d 100644 --- a/x/bond/module/depinject.go +++ b/x/bond/module/depinject.go @@ -4,12 +4,14 @@ import ( "cosmossdk.io/core/appmodule" "cosmossdk.io/core/store" "cosmossdk.io/depinject" + "golang.org/x/exp/maps" "github.com/cosmos/cosmos-sdk/codec" auth "github.com/cosmos/cosmos-sdk/x/auth/keeper" bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" modulev1 "git.vdb.to/cerc-io/laconic2d/api/cerc/bond/module/v1" + "git.vdb.to/cerc-io/laconic2d/x/bond" "git.vdb.to/cerc-io/laconic2d/x/bond/keeper" ) @@ -25,6 +27,7 @@ func init() { appmodule.Register( &modulev1.Module{}, appmodule.Provide(ProvideModule), + appmodule.Invoke(InvokeSetBondHooks), ) } @@ -41,7 +44,7 @@ type ModuleInputs struct { type ModuleOutputs struct { depinject.Out - Keeper keeper.Keeper + Keeper *keeper.Keeper Module appmodule.AppModule } @@ -51,3 +54,25 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { return ModuleOutputs{Module: m, Keeper: k} } + +func InvokeSetBondHooks( + config *modulev1.Module, + keeper *keeper.Keeper, + bondHooks map[string]bond.BondHooksWrapper, +) error { + // all arguments to invokers are optional + if keeper == nil || config == nil { + return nil + } + + var usageKeepers []bond.BondUsageKeeper + + for _, modName := range maps.Keys(bondHooks) { + hook := bondHooks[modName] + usageKeepers = append(usageKeepers, hook) + } + + keeper.SetUsageKeepers(usageKeepers) + + return nil +} diff --git a/x/bond/module/module.go b/x/bond/module/module.go index 2fb5b834..fb566531 100644 --- a/x/bond/module/module.go +++ b/x/bond/module/module.go @@ -33,11 +33,11 @@ const ConsensusVersion = 1 type AppModule struct { cdc codec.Codec - keeper keeper.Keeper + keeper *keeper.Keeper } // NewAppModule creates a new AppModule object -func NewAppModule(cdc codec.Codec, keeper keeper.Keeper) AppModule { +func NewAppModule(cdc codec.Codec, keeper *keeper.Keeper) AppModule { return AppModule{ cdc: cdc, keeper: keeper, diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index d09bdc2a..3892a3e0 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -97,7 +97,7 @@ type Keeper struct { accountKeeper auth.AccountKeeper bankKeeper bank.Keeper - bondKeeper bondkeeper.Keeper + bondKeeper *bondkeeper.Keeper auctionKeeper *auctionkeeper.Keeper // state management @@ -116,7 +116,7 @@ func NewKeeper( storeService storetypes.KVStoreService, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper, - bondKeeper bondkeeper.Keeper, + bondKeeper *bondkeeper.Keeper, auctionKeeper *auctionkeeper.Keeper, ) Keeper { sb := collections.NewSchemaBuilder(storeService) diff --git a/x/registry/keeper/record_keeper.go b/x/registry/keeper/record_keeper.go index 890adf6e..03f7a259 100644 --- a/x/registry/keeper/record_keeper.go +++ b/x/registry/keeper/record_keeper.go @@ -46,12 +46,12 @@ func (rk RecordKeeper) ModuleName() string { } func (rk RecordKeeper) UsesAuction(ctx sdk.Context, auctionId string) bool { - if _, err := rk.k.Authorities.Indexes.AuctionId.MatchExact(ctx, auctionId); err != nil { - // ErrNotFound - return false + iter, err := rk.k.Authorities.Indexes.AuctionId.MatchExact(ctx, auctionId) + if err != nil { + panic(err) } - return true + return iter.Valid() } func (rk RecordKeeper) OnAuction(ctx sdk.Context, auctionId string) { @@ -133,6 +133,16 @@ func (rk RecordKeeper) OnAuctionWinnerSelected(ctx sdk.Context, auctionId string } } +// UsesBond returns true if the bond has associated records. +func (rk RecordKeeper) UsesBond(ctx sdk.Context, bondId string) bool { + iter, err := rk.k.Records.Indexes.BondId.MatchExact(ctx, bondId) + if err != nil { + panic(err) + } + + return iter.Valid() +} + // RenewRecord renews a record. func (k Keeper) RenewRecord(ctx sdk.Context, msg registrytypes.MsgRenewRecord) error { if has, err := k.HasRecord(ctx, msg.RecordId); !has { diff --git a/x/registry/module/depinject.go b/x/registry/module/depinject.go index e600a033..40e8428a 100644 --- a/x/registry/module/depinject.go +++ b/x/registry/module/depinject.go @@ -10,8 +10,9 @@ import ( bank "github.com/cosmos/cosmos-sdk/x/bank/keeper" modulev1 "git.vdb.to/cerc-io/laconic2d/api/cerc/registry/module/v1" - auction "git.vdb.to/cerc-io/laconic2d/x/auction" + "git.vdb.to/cerc-io/laconic2d/x/auction" auctionkeeper "git.vdb.to/cerc-io/laconic2d/x/auction/keeper" + "git.vdb.to/cerc-io/laconic2d/x/bond" bondkeeper "git.vdb.to/cerc-io/laconic2d/x/bond/keeper" "git.vdb.to/cerc-io/laconic2d/x/registry/keeper" ) @@ -40,7 +41,7 @@ type ModuleInputs struct { AccountKeeper auth.AccountKeeper BankKeeper bank.Keeper - BondKeeper bondkeeper.Keeper + BondKeeper *bondkeeper.Keeper AuctionKeeper *auctionkeeper.Keeper } @@ -51,6 +52,7 @@ type ModuleOutputs struct { Module appmodule.AppModule AuctionHooks auction.AuctionHooksWrapper + BondHooks bond.BondHooksWrapper } func ProvideModule(in ModuleInputs) ModuleOutputs { @@ -66,5 +68,9 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { recordKeeper := keeper.NewRecordKeeper(in.Cdc, &k, in.AuctionKeeper) - return ModuleOutputs{Module: m, Keeper: k, AuctionHooks: auction.AuctionHooksWrapper{AuctionUsageKeeper: recordKeeper}} + return ModuleOutputs{ + Module: m, Keeper: k, + AuctionHooks: auction.AuctionHooksWrapper{AuctionUsageKeeper: recordKeeper}, + BondHooks: bond.BondHooksWrapper{BondUsageKeeper: recordKeeper}, + } }