From 8dfeaeaf42f43f62581257e1703c26a3c6ab359f Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 31 Oct 2024 10:47:42 +0400 Subject: [PATCH 1/4] Add mint rewards custom message place-holder --- x/babylon/contract/in_message.go | 23 +++++++++++++++-------- x/babylon/keeper/handler_plugin.go | 6 +++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/x/babylon/contract/in_message.go b/x/babylon/contract/in_message.go index 2905d40..d7df60b 100644 --- a/x/babylon/contract/in_message.go +++ b/x/babylon/contract/in_message.go @@ -1,11 +1,18 @@ package contract -// CustomMsg is a message sent from a smart contract to the Babylon module -// TODO: implement -type CustomMsg struct { - Test *TestMsg `json:"test,omitempty"` -} +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" +) -type TestMsg struct { - Placeholder string `json:"placeholder,omitempty"` -} +// CustomMsg is a message sent from a smart contract to the Babylon module +type ( + CustomMsg struct { + MintRewards *MintRewardsMsg `json:"mint_rewards,omitempty"` + } + // MintRewardsMsg mints the specified number of block rewards, + // and sends them to the specified recipient (typically, the staking contract) + MintRewardsMsg struct { + Amount wasmvmtypes.Coin `json:"amount"` + Recipient string `json:"recipient"` + } +) diff --git a/x/babylon/keeper/handler_plugin.go b/x/babylon/keeper/handler_plugin.go index a7c655f..5a6534d 100644 --- a/x/babylon/keeper/handler_plugin.go +++ b/x/babylon/keeper/handler_plugin.go @@ -55,7 +55,7 @@ func (h CustomMsgHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre if err := json.Unmarshal(msg.Custom, &customMsg); err != nil { return nil, nil, nil, sdkerrors.ErrJSONUnmarshal.Wrap("custom message") } - if customMsg.Test == nil { + if customMsg.MintRewards == nil { // not our message type return nil, nil, nil, wasmtypes.ErrUnknownMsg } @@ -64,10 +64,10 @@ func (h CustomMsgHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre return nil, nil, nil, sdkerrors.ErrUnauthorized.Wrapf("contract has no permission for Babylon operations") } - return h.handleTestMsg(ctx, contractAddr, customMsg.Test) + return h.handleMintRewardsMsg(ctx, contractAddr, customMsg.MintRewards) } -func (h CustomMsgHandler) handleTestMsg(ctx sdk.Context, actor sdk.AccAddress, testMsg *contract.TestMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { +func (h CustomMsgHandler) handleMintRewardsMsg(ctx sdk.Context, actor sdk.AccAddress, mintRewardsMsg *contract.MintRewardsMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { return []sdk.Event{}, nil, nil, nil } From 31421e132888fda79a6004e6146a45b13611d3a7 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 31 Oct 2024 11:49:23 +0400 Subject: [PATCH 2/4] Mint rewards initial impl --- x/babylon/keeper/handler_plugin.go | 44 +++++++++++++++++++---- x/babylon/keeper/mint_rewards.go | 56 +++++++++++++++++++++++++++++ x/babylon/types/events.go | 1 + x/babylon/types/expected_keepers.go | 3 ++ 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 x/babylon/keeper/mint_rewards.go diff --git a/x/babylon/keeper/handler_plugin.go b/x/babylon/keeper/handler_plugin.go index 5a6534d..2a68cf7 100644 --- a/x/babylon/keeper/handler_plugin.go +++ b/x/babylon/keeper/handler_plugin.go @@ -1,8 +1,8 @@ package keeper import ( + sdkmath "cosmossdk.io/math" "encoding/json" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" @@ -22,10 +22,13 @@ type AuthSource interface { } // abstract keeper -type msKeeper interface{} +type babylonKeeper interface { + GetParams(ctx sdk.Context) types.Params + MintBlockRewards(ctx sdk.Context, recipient sdk.AccAddress, amount sdk.Coin) (sdkmath.Int, error) +} type CustomMsgHandler struct { - k msKeeper + k babylonKeeper auth AuthSource } @@ -36,7 +39,7 @@ func NewDefaultCustomMsgHandler(k *Keeper) *CustomMsgHandler { // NewCustomMsgHandler constructor to set up CustomMsgHandler with an individual auth source. // This is an extension point for non default contract authorization logic. -func NewCustomMsgHandler(k msKeeper, auth AuthSource) *CustomMsgHandler { +func NewCustomMsgHandler(k babylonKeeper, auth AuthSource) *CustomMsgHandler { return &CustomMsgHandler{k: k, auth: auth} } @@ -67,8 +70,37 @@ func (h CustomMsgHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre return h.handleMintRewardsMsg(ctx, contractAddr, customMsg.MintRewards) } -func (h CustomMsgHandler) handleMintRewardsMsg(ctx sdk.Context, actor sdk.AccAddress, mintRewardsMsg *contract.MintRewardsMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { - return []sdk.Event{}, nil, nil, nil +func (h CustomMsgHandler) handleMintRewardsMsg(ctx sdk.Context, actor sdk.AccAddress, mintMsg *contract.MintRewardsMsg) ([]sdk.Event, [][]byte, [][]*codectypes.Any, error) { + coin, err := wasmkeeper.ConvertWasmCoinToSdkCoin(mintMsg.Amount) + if err != nil { + return nil, nil, nil, err + } + params := h.k.GetParams(ctx) + // Validate actor + if actor.String() != params.BtcFinalityContractAddress { + return nil, nil, nil, sdkerrors.ErrUnauthorized.Wrapf("minter must be the finality contract") + } + + // Define recipient + recipient, err := sdk.AccAddressFromBech32(mintMsg.Recipient) + if err != nil { + return nil, nil, nil, err + } + if err != nil { + return nil, nil, nil, err + } + + rewards, err := h.k.MintBlockRewards(ctx, recipient, coin) + if err != nil { + return nil, nil, nil, err + } + + return []sdk.Event{sdk.NewEvent( + types.EventTypeMintRewards, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, actor.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, rewards.String()), + )}, nil, nil, nil } // AuthSourceFn is helper for simple AuthSource types diff --git a/x/babylon/keeper/mint_rewards.go b/x/babylon/keeper/mint_rewards.go new file mode 100644 index 0000000..0d4b79e --- /dev/null +++ b/x/babylon/keeper/mint_rewards.go @@ -0,0 +1,56 @@ +package keeper + +import ( + sdkmath "cosmossdk.io/math" + "github.com/babylonlabs-io/babylon-sdk/x/babylon/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" +) + +// MintBlockRewards mints new "virtual" bonding tokens and sends them to the staking contract for distribution. +// The amount minted is removed from the SupplyOffset (so that it will become negative), when supported. +// Authorization of the actor/recipient should be handled before entering this method. +// The amount is computed based on the finality inflation rate and the total staking token supply, in the bonded denom +func (k Keeper) MintBlockRewards(pCtx sdk.Context, recipient sdk.AccAddress, amt sdk.Coin) (sdkmath.Int, error) { + if amt.Amount.IsNil() || amt.Amount.IsZero() || amt.Amount.IsNegative() { + return sdkmath.ZeroInt(), errors.ErrInvalidRequest.Wrap("amount") + } + + // Ensure staking constraints + bondDenom, err := k.Staking.BondDenom(pCtx) + if err != nil { + return sdkmath.ZeroInt(), err + } + if amt.Denom != bondDenom { + return sdkmath.ZeroInt(), errors.ErrInvalidRequest.Wrapf("invalid coin denomination: got %s, expected %s", amt.Denom, bondDenom) + } + // FIXME? Remove this constraint for flexibility + params := k.GetParams(pCtx) + if recipient.String() != params.BtcStakingContractAddress { + return sdkmath.ZeroInt(), errors.ErrUnauthorized.Wrapf("invalid recipient: got %s, expected staking contract (%s)", + recipient, params.BtcStakingContractAddress) + } + + // TODO?: Ensure Babylon constraints + + cacheCtx, done := pCtx.CacheContext() // work in a cached store as Osmosis (safety net?) + + // Mint rewards tokens + coins := sdk.NewCoins(amt) + err = k.bank.MintCoins(cacheCtx, types.ModuleName, coins) + if err != nil { + return sdkmath.ZeroInt(), err + } + + // FIXME: Confirm we want this supply offset enabled for rewards, i.e. + // as virtual coins that do not count to the total supply + //k.bank.AddSupplyOffset(cacheCtx, bondDenom, amt.Amount.Neg()) + + err = k.bank.SendCoinsFromModuleToAccount(cacheCtx, types.ModuleName, recipient, coins) + if err != nil { + return sdkmath.ZeroInt(), err + } + + done() + return amt.Amount, err +} diff --git a/x/babylon/types/events.go b/x/babylon/types/events.go index 3343914..b54895c 100644 --- a/x/babylon/types/events.go +++ b/x/babylon/types/events.go @@ -12,6 +12,7 @@ const ( EventTypeMaxCapLimitUpdated = "max_cap_limit_updated" EventTypeUnbond = "instant_unbond" EventTypeDelegate = "instant_delegate" + EventTypeMintRewards = "mint_rewards" ) const ( diff --git a/x/babylon/types/expected_keepers.go b/x/babylon/types/expected_keepers.go index 56fe6e7..d7b4e53 100644 --- a/x/babylon/types/expected_keepers.go +++ b/x/babylon/types/expected_keepers.go @@ -2,6 +2,7 @@ package types import ( context "context" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -18,6 +19,8 @@ type BankKeeper interface { // StakingKeeper expected staking keeper. type StakingKeeper interface { + BondDenom(ctx context.Context) (string, error) + StakingTokenSupply(ctx context.Context) (sdkmath.Int, error) } // AccountKeeper interface contains functions for getting accounts and the module address From 4b02612272b7dd6d43a5fa0087b74a3f30e19d19 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Thu, 31 Oct 2024 12:07:22 +0400 Subject: [PATCH 3/4] Fix / improve comments --- x/babylon/keeper/mint_rewards.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x/babylon/keeper/mint_rewards.go b/x/babylon/keeper/mint_rewards.go index 0d4b79e..eb742d9 100644 --- a/x/babylon/keeper/mint_rewards.go +++ b/x/babylon/keeper/mint_rewards.go @@ -7,10 +7,10 @@ import ( "github.com/cosmos/cosmos-sdk/types/errors" ) -// MintBlockRewards mints new "virtual" bonding tokens and sends them to the staking contract for distribution. -// The amount minted is removed from the SupplyOffset (so that it will become negative), when supported. -// Authorization of the actor/recipient should be handled before entering this method. -// The amount is computed based on the finality inflation rate and the total staking token supply, in the bonded denom +// MintBlockRewards mints new tokens and sends them to the staking contract for distribution. +// Authorization of the actor should be handled before entering this method. +// Authorization of the recipient is being handled within the method for safety, but can +// be removed for flexibility func (k Keeper) MintBlockRewards(pCtx sdk.Context, recipient sdk.AccAddress, amt sdk.Coin) (sdkmath.Int, error) { if amt.Amount.IsNil() || amt.Amount.IsZero() || amt.Amount.IsNegative() { return sdkmath.ZeroInt(), errors.ErrInvalidRequest.Wrap("amount") From 752dec2343cbeabddd0dd26defa9d862d2f99507 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Fri, 8 Nov 2024 11:05:35 +0700 Subject: [PATCH 4/4] Remove redundant mint event --- x/babylon/keeper/handler_plugin.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/x/babylon/keeper/handler_plugin.go b/x/babylon/keeper/handler_plugin.go index 2a68cf7..f14ec34 100644 --- a/x/babylon/keeper/handler_plugin.go +++ b/x/babylon/keeper/handler_plugin.go @@ -86,21 +86,9 @@ func (h CustomMsgHandler) handleMintRewardsMsg(ctx sdk.Context, actor sdk.AccAdd if err != nil { return nil, nil, nil, err } - if err != nil { - return nil, nil, nil, err - } - - rewards, err := h.k.MintBlockRewards(ctx, recipient, coin) - if err != nil { - return nil, nil, nil, err - } - return []sdk.Event{sdk.NewEvent( - types.EventTypeMintRewards, - sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), - sdk.NewAttribute(sdk.AttributeKeySender, actor.String()), - sdk.NewAttribute(sdk.AttributeKeyAmount, rewards.String()), - )}, nil, nil, nil + _, err = h.k.MintBlockRewards(ctx, recipient, coin) + return nil, nil, nil, err } // AuthSourceFn is helper for simple AuthSource types