From 5008a8f78059d879521f673a468c5a1b2cd4a10c Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 21 May 2024 13:26:46 +0300 Subject: [PATCH 01/16] CNS-960: make rewards module ibc middleware --- app/app.go | 2 + testutil/keeper/keepers_init.go | 2 +- testutil/keeper/rewards.go | 1 + x/rewards/ibc_middleware.go | 108 ++++++++++++++++++++++++++++++ x/rewards/keeper/ibc_callbacks.go | 13 ++++ x/rewards/keeper/keeper.go | 24 +++++++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 x/rewards/ibc_middleware.go create mode 100644 x/rewards/keeper/ibc_callbacks.go diff --git a/app/app.go b/app/app.go index 1bacc28d1c..4707e78764 100644 --- a/app/app.go +++ b/app/app.go @@ -557,6 +557,7 @@ func New( authtypes.FeeCollectorName, app.TimerStoreKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + app.IBCKeeper.ChannelKeeper, ) rewardsModule := rewardsmodule.NewAppModule(appCodec, app.RewardsKeeper, app.AccountKeeper, app.BankKeeper) @@ -712,6 +713,7 @@ func New( packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp, // refund timeout ) + transferStack = rewardsmodule.NewIBCMiddleware(transferStack, app.RewardsKeeper) ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) // this line is used by starport scaffolding # ibc/app/router diff --git a/testutil/keeper/keepers_init.go b/testutil/keeper/keepers_init.go index e364ca4a6d..4c3f0557ba 100644 --- a/testutil/keeper/keepers_init.go +++ b/testutil/keeper/keepers_init.go @@ -264,7 +264,7 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { ks.Projects = *projectskeeper.NewKeeper(cdc, projectsStoreKey, projectsMemStoreKey, projectsparamsSubspace, ks.Epochstorage, ks.FixationStoreKeeper) ks.Protocol = *protocolkeeper.NewKeeper(cdc, protocolStoreKey, protocolMemStoreKey, protocolparamsSubspace, authtypes.NewModuleAddress(govtypes.ModuleName).String()) ks.Downtime = downtimekeeper.NewKeeper(cdc, downtimeKey, downtimeParamsSubspace, ks.Epochstorage) - ks.Rewards = *rewardskeeper.NewKeeper(cdc, rewardsStoreKey, rewardsMemStoreKey, rewardsparamsSubspace, ks.BankKeeper, ks.AccountKeeper, ks.Spec, ks.Epochstorage, ks.Downtime, ks.StakingKeeper, ks.Dualstaking, ks.Distribution, authtypes.FeeCollectorName, ks.TimerStoreKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + ks.Rewards = *rewardskeeper.NewKeeper(cdc, rewardsStoreKey, rewardsMemStoreKey, rewardsparamsSubspace, ks.BankKeeper, ks.AccountKeeper, ks.Spec, ks.Epochstorage, ks.Downtime, ks.StakingKeeper, ks.Dualstaking, ks.Distribution, authtypes.FeeCollectorName, ks.TimerStoreKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), nil) ks.Subscription = *subscriptionkeeper.NewKeeper(cdc, subscriptionStoreKey, subscriptionMemStoreKey, subscriptionparamsSubspace, &ks.BankKeeper, &ks.AccountKeeper, &ks.Epochstorage, ks.Projects, ks.Plans, ks.Dualstaking, ks.Rewards, ks.FixationStoreKeeper, ks.TimerStoreKeeper, ks.StakingKeeper) ks.Pairing = *pairingkeeper.NewKeeper(cdc, pairingStoreKey, pairingMemStoreKey, pairingparamsSubspace, &ks.BankKeeper, &ks.AccountKeeper, ks.Spec, &ks.Epochstorage, ks.Projects, ks.Subscription, ks.Plans, ks.Downtime, ks.Dualstaking, &ks.StakingKeeper, ks.FixationStoreKeeper, ks.TimerStoreKeeper) ks.ParamsKeeper = paramsKeeper diff --git a/testutil/keeper/rewards.go b/testutil/keeper/rewards.go index dcaf8a43d5..b22e8b0fff 100644 --- a/testutil/keeper/rewards.go +++ b/testutil/keeper/rewards.go @@ -94,6 +94,7 @@ func RewardsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { authtypes.FeeCollectorName, timerstorekeeper.NewKeeper(cdc), authtypes.NewModuleAddress(govtypes.ModuleName).String(), + nil, ) // Initialize params diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go new file mode 100644 index 0000000000..137c6ffb27 --- /dev/null +++ b/x/rewards/ibc_middleware.go @@ -0,0 +1,108 @@ +package rewards + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/lavanet/lava/x/rewards/keeper" +) + +var _ porttypes.Middleware = &IBCMiddleware{} + +// IBCMiddleware implements the ICS26 callbacks for the transfer middleware given +// the rewards keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule // will be the transfer stack + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} + +// IBCModule interface implementation. Only OnRecvPacket() calls a callback from the keeper. The rest have default implementations + +func (im IBCMiddleware) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, version) +} + +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (version string, err error) { + return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, chanCap, counterparty, counterpartyVersion) +} + +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +func (im IBCMiddleware) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +func (im IBCMiddleware) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +func (im IBCMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + return im.keeper.OnRecvPacket(ctx, packet, relayer) +} + +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + return im.app.OnTimeoutPacket(ctx, packet, relayer) +} + +// ICS4Wrapper interface (default implementations) +func (im IBCMiddleware) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (sequence uint64, err error) { + return im.keeper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) +} + +func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, + ack exported.Acknowledgement) error { + return im.keeper.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return im.keeper.GetAppVersion(ctx, portID, channelID) +} diff --git a/x/rewards/keeper/ibc_callbacks.go b/x/rewards/keeper/ibc_callbacks.go new file mode 100644 index 0000000000..d6ccf51b36 --- /dev/null +++ b/x/rewards/keeper/ibc_callbacks.go @@ -0,0 +1,13 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/lavanet/lava/utils" +) + +func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + utils.LogLavaEvent(ctx, k.Logger(ctx), "oren", nil, "you did it!!!") + return nil +} diff --git a/x/rewards/keeper/keeper.go b/x/rewards/keeper/keeper.go index 2b3334e001..ca40591688 100644 --- a/x/rewards/keeper/keeper.go +++ b/x/rewards/keeper/keeper.go @@ -8,7 +8,11 @@ import ( "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" "github.com/lavanet/lava/x/rewards/types" timerstoretypes "github.com/lavanet/lava/x/timerstore/types" ) @@ -41,6 +45,9 @@ type ( // the address capable of executing a MsgSetIprpcData message. Typically, this // should be the x/gov module account. authority string + + // ICS4 wrapper that lets the rewards module be an IBC middleware module + ics4Wrapper porttypes.ICS4Wrapper } ) @@ -60,6 +67,7 @@ func NewKeeper( feeCollectorName string, timerStoreKeeper types.TimerStoreKeeper, authority string, + ics4Wrapper porttypes.ICS4Wrapper, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -83,6 +91,7 @@ func NewKeeper( feeCollectorName: feeCollectorName, authority: authority, + ics4Wrapper: ics4Wrapper, } refillRewardsPoolTimerCallback := func(ctx sdk.Context, subkey, data []byte) { @@ -142,3 +151,18 @@ func (k Keeper) InitRewardsRefillTS(ctx sdk.Context, gs timerstoretypes.GenesisS func (k Keeper) ExportRewardsRefillTS(ctx sdk.Context) timerstoretypes.GenesisState { return k.refillRewardsPoolTS.Export(ctx) } + +// ICS4 wrapper default implementations +func (k Keeper) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (sequence uint64, err error) { + return k.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) +} + +func (k Keeper) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, + ack exported.Acknowledgement) error { + return k.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +func (k Keeper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return k.ics4Wrapper.GetAppVersion(ctx, portID, channelID) +} From d3c5144b00734f39bc3d5220f434cabf5f997785 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 22 May 2024 12:35:55 +0300 Subject: [PATCH 02/16] CNS-960: implement memo ibc-transfer memo parsing --- x/rewards/ibc_middleware.go | 63 +++++++++++++++++- x/rewards/keeper/ibc_callbacks.go | 13 ---- x/rewards/keeper/ibc_iprpc.go | 103 ++++++++++++++++++++++++++++++ x/rewards/keeper/keeper.go | 4 ++ x/rewards/types/errors.go | 4 +- x/rewards/types/iprpc.go | 10 +++ 6 files changed, 181 insertions(+), 16 deletions(-) delete mode 100644 x/rewards/keeper/ibc_callbacks.go create mode 100644 x/rewards/keeper/ibc_iprpc.go diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 137c6ffb27..8216bb2418 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -1,13 +1,19 @@ package rewards import ( + "errors" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/lavanet/lava/utils" "github.com/lavanet/lava/x/rewards/keeper" + "github.com/lavanet/lava/x/rewards/types" ) var _ porttypes.Middleware = &IBCMiddleware{} @@ -15,7 +21,7 @@ var _ porttypes.Middleware = &IBCMiddleware{} // IBCMiddleware implements the ICS26 callbacks for the transfer middleware given // the rewards keeper and the underlying application. type IBCMiddleware struct { - app porttypes.IBCModule // will be the transfer stack + app porttypes.IBCModule // transfer stack keeper keeper.Keeper } @@ -71,8 +77,61 @@ func (im IBCMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID st return im.app.OnChanCloseConfirm(ctx, portID, channelID) } +// OnRecvPacket checks the packet's memo and funds the IPRPC pool accordingly. If the memo is not the expected JSON, +// the packet is transferred normally to the next IBC module in the transfer stack func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { - return im.keeper.OnRecvPacket(ctx, packet, relayer) + // unmarshal the packet's data with the transfer module codec (expect an ibc-transfer packet) + var data transfertypes.FungibleTokenPacketData + if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + // extract the packet's memo + memo, err := im.keeper.ExtractIprpcMemoFromPacket(ctx, data) + if errors.Is(err, types.ErrMemoNotIprpcOverIbc) { + // not a packet that should be handled as IPRPC over IBC (not considered as error) + utils.LavaFormatDebug("rewards module IBC middleware processing skipped, memo is invalid for IPRPC over IBC funding", + utils.LogAttr("memo", memo), + ) + return im.app.OnRecvPacket(ctx, packet, relayer) + } else if errors.Is(err, types.ErrIprpcMemoInvalid) { + // memo is in the right format of IPRPC over IBC but the data is invalid + utils.LavaFormatWarning("rewards module IBC middleware processing failed, memo data is invalid", err, + utils.LogAttr("memo", memo)) + return channeltypes.NewErrorAcknowledgement(err) + } + + // change the ibc-transfer packet receiver address to be the rewards module address and empty the memo + data.Receiver = im.keeper.GetModuleAddress() + data.Memo = "" + marshelledData, err := transfertypes.ModuleCdc.MarshalJSON(&data) + if err != nil { + utils.LavaFormatError("rewards module IBC middleware processing failed, cannot marshal packet data", err, + utils.LogAttr("data", data)) + return channeltypes.NewErrorAcknowledgement(err) + } + packet.Data = marshelledData + + // call the next OnRecvPacket() of the transfer stack to make the rewards module get the IBC tokens + ack := im.app.OnRecvPacket(ctx, packet, relayer) + if ack == nil || !ack.Success() { + return ack + } + + // set pending IPRPC over IBC requests on-chain + amountInt, ok := sdk.NewIntFromString(data.Amount) + if !ok { + utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("cannot decode coin amount"), + utils.LogAttr("data", data)) + return channeltypes.NewErrorAcknowledgement(err) + } + amount := sdk.NewCoin(data.Denom, amountInt) + err = im.keeper.SetPendingIprpcOverIbcFunds(ctx, memo, amount) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + return nil } func (im IBCMiddleware) OnAcknowledgementPacket( diff --git a/x/rewards/keeper/ibc_callbacks.go b/x/rewards/keeper/ibc_callbacks.go deleted file mode 100644 index d6ccf51b36..0000000000 --- a/x/rewards/keeper/ibc_callbacks.go +++ /dev/null @@ -1,13 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/cosmos/ibc-go/v7/modules/core/exported" - "github.com/lavanet/lava/utils" -) - -func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { - utils.LogLavaEvent(ctx, k.Logger(ctx), "oren", nil, "you did it!!!") - return nil -} diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go new file mode 100644 index 0000000000..9f6319d23f --- /dev/null +++ b/x/rewards/keeper/ibc_iprpc.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + "github.com/lavanet/lava/utils" + "github.com/lavanet/lava/x/rewards/types" +) + +/* + +The rewards module (which acts as an IBC middleware) analyzes incoming ibc-transfer packets and checks their memo field. +If the memo field is in the IPRPC over IBC format, it uses the tokens from the packet and saves them for a future fund to +the IPRPC pool. + +An example of the expected IPRPC over IBC memo field: +{ + "iprpc": { + "creator": "my-moniker", + "spec": "ETH1", + "duration": 3 + } +} + +The tokens will be transferred to the pool once the minimum IPRPC funding fee is paid. In the meantime, the IPRPC over IBC +funds are saved in the IbcIprpcFund scaffolded map. + +*/ + +// ExtractIprpcMemoFromPacket extracts the memo field from an ibc-transfer packet and verifies that it's in the right format +// and holds valid values. If the memo is not in the right format, a custom error is returned so the packet will be skipped and +// passed to the next IBC module in the transfer stack normally (and not return an error ack) +func (k Keeper) ExtractIprpcMemoFromPacket(ctx sdk.Context, transferData transfertypes.FungibleTokenPacketData) (types.IprpcMemo, error) { + memo := types.IprpcMemo{} + transferMemo := make(map[string]interface{}) + err := json.Unmarshal([]byte(transferData.Memo), &transferMemo) + if err != nil || transferMemo["iprpc"] == nil { + // memo is not for IPRPC over IBC, return custom error to skip processing for this packet + return types.IprpcMemo{}, types.ErrMemoNotIprpcOverIbc + } + + if iprpcData, ok := transferMemo["iprpc"].(map[string]interface{}); ok { + // verify creator field + creator, ok := iprpcData["creator"] + if !ok { + return printInvalidMemoWarning(iprpcData, "memo data does not contain creator field") + } + creatorStr, ok := creator.(string) + if !ok { + return printInvalidMemoWarning(iprpcData, "memo's creator field is not string") + } + if creatorStr == "" { + return printInvalidMemoWarning(iprpcData, "memo's creator field cannot be empty") + } + memo.Creator = creatorStr + + // verify spec field + spec, ok := iprpcData["spec"] + if !ok { + return printInvalidMemoWarning(iprpcData, "memo data does not contain spec field") + } + specStr, ok := spec.(string) + if !ok { + return printInvalidMemoWarning(iprpcData, "memo's spec field is not string") + } + _, found := k.specKeeper.GetSpec(ctx, specStr) + if !found { + return printInvalidMemoWarning(iprpcData, "memo's spec field does not exist on chain") + } + memo.Spec = specStr + + // verify duration field + duration, ok := iprpcData["duration"] + if !ok { + return printInvalidMemoWarning(iprpcData, "memo data does not contain duration field") + } + durationFloat64, ok := duration.(float64) + if !ok { + return printInvalidMemoWarning(iprpcData, "memo's duration field is not uint64") + } + if durationFloat64 <= 0 { + return printInvalidMemoWarning(iprpcData, "memo's duration field cannot be non-positive") + } + memo.Duration = uint64(durationFloat64) + } + + return memo, nil +} + +func printInvalidMemoWarning(iprpcData map[string]interface{}, description string) (types.IprpcMemo, error) { + utils.LavaFormatWarning("invalid ibc over iprpc memo", fmt.Errorf(description), + utils.LogAttr("data", iprpcData), + ) + return types.IprpcMemo{}, types.ErrIprpcMemoInvalid +} + +func (k Keeper) SetPendingIprpcOverIbcFunds(ctx sdk.Context, memo types.IprpcMemo, amount sdk.Coin) error { + // TODO: implement + return nil +} diff --git a/x/rewards/keeper/keeper.go b/x/rewards/keeper/keeper.go index ca40591688..ba70e132b5 100644 --- a/x/rewards/keeper/keeper.go +++ b/x/rewards/keeper/keeper.go @@ -115,6 +115,10 @@ func (k Keeper) BeginBlock(ctx sdk.Context) { k.DistributeBlockReward(ctx) } +func (k Keeper) GetModuleAddress() string { + return k.accountKeeper.GetModuleAddress(types.ModuleName).String() +} + // BondedTargetFactor calculates the bonded target factor which is used to calculate the validators // block rewards func (k Keeper) BondedTargetFactor(ctx sdk.Context) cosmosMath.LegacyDec { diff --git a/x/rewards/types/errors.go b/x/rewards/types/errors.go index f3d7d64317..5fcb1336ce 100644 --- a/x/rewards/types/errors.go +++ b/x/rewards/types/errors.go @@ -8,5 +8,7 @@ import ( // x/rewards module sentinel errors var ( - ErrFundIprpc = sdkerrors.Register(ModuleName, 1, "fund iprpc TX failed") + ErrFundIprpc = sdkerrors.Register(ModuleName, 1, "fund iprpc TX failed") + ErrMemoNotIprpcOverIbc = sdkerrors.Register(ModuleName, 2, "ibc-transfer packet's memo is not in the right format of IPRPC over IBC") + ErrIprpcMemoInvalid = sdkerrors.Register(ModuleName, 3, "ibc-transfer packet's memo of IPRPC over IBC is invalid") ) diff --git a/x/rewards/types/iprpc.go b/x/rewards/types/iprpc.go index 38cdcff1fd..8957e41f74 100644 --- a/x/rewards/types/iprpc.go +++ b/x/rewards/types/iprpc.go @@ -13,3 +13,13 @@ const ( // IprpcRewardsCurrentPrefix is the prefix to retrieve all IprpcRewardsCurrent IprpcRewardsCurrentPrefix = "IprpcRewardsCurrent/" ) + +type IprpcMemo struct { + Creator string `json:"creator"` + Spec string `json:"spec"` + Duration uint64 `json:"duration"` +} + +func (im IprpcMemo) IsEqual(other IprpcMemo) bool { + return im.Creator == other.Creator && im.Duration == other.Duration && im.Spec == other.Spec +} From 10e1650ef34677e53668d6414eaaed18bc541c27 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 22 May 2024 12:36:09 +0300 Subject: [PATCH 03/16] CNS-960: unit test for memo parsing --- testutil/sample/sample.go | 7 ++ x/rewards/keeper/helpers_test.go | 6 ++ x/rewards/keeper/ibc_iprpc_test.go | 163 +++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 x/rewards/keeper/ibc_iprpc_test.go diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index 9132c9681d..c4b22f521b 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -13,6 +13,13 @@ func AccAddress() string { return sdk.AccAddress(addr).String() } +// AccAddress returns a sample account address of type sdk.AccAddress +func AccAddressObject() sdk.AccAddress { + pk := ed25519.GenPrivKey().PubKey() + addr := pk.Address() + return sdk.AccAddress(addr) +} + // ValAddress returns a sample validator account address func ValAddress() string { pk := ed25519.GenPrivKey().PubKey() diff --git a/x/rewards/keeper/helpers_test.go b/x/rewards/keeper/helpers_test.go index 8ab23dd586..32ce58f4af 100644 --- a/x/rewards/keeper/helpers_test.go +++ b/x/rewards/keeper/helpers_test.go @@ -9,9 +9,11 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" commontypes "github.com/lavanet/lava/common/types" "github.com/lavanet/lava/testutil/common" testkeeper "github.com/lavanet/lava/testutil/keeper" + "github.com/lavanet/lava/testutil/sample" planstypes "github.com/lavanet/lava/x/plans/types" rewardstypes "github.com/lavanet/lava/x/rewards/types" spectypes "github.com/lavanet/lava/x/spec/types" @@ -138,6 +140,10 @@ func (ts *tester) setupForIprpcTests(fundIprpcPool bool) { } } +func (ts *tester) createIbcTransferPacketData(memo string) transfertypes.FungibleTokenPacketData { + return transfertypes.NewFungibleTokenPacketData(ts.TokenDenom(), "100000", sample.AccAddress(), sample.AccAddress(), memo) +} + // deductParticipationFees calculates the validators and community participation // fees and returns the providers reward after deducting them func (ts *tester) DeductParticipationFees(reward math.Int) (updatedReward math.Int, valParticipation math.Int, communityParticipation math.Int) { diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go new file mode 100644 index 0000000000..c719034b30 --- /dev/null +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -0,0 +1,163 @@ +package keeper_test + +import ( + "testing" + + sdkerrors "cosmossdk.io/errors" + "github.com/lavanet/lava/x/rewards/types" + "github.com/stretchr/testify/require" +) + +// TestParseIprpcOverIbcMemo tests the behavior of OnRecvPacket() for different memos: +// 0. empty memo -> "not an iprpc memo" error +// 1. non-JSON memo -> "not an iprpc memo" error +// 2. JSON memo without "iprpc" tag -> "not an iprpc memo" error +// 3. valid JSON memo with "iprpc" tag -> happy flow +// 4. invalid JSON memo with "iprpc" tag (invalid/missing values) -> returns error (multiple cases) +func TestParseIprpcOverIbcMemo(t *testing.T) { + ts := newTester(t, false) + memos := []string{ + "", + "blabla", + `{ + "client": "Bruce", + "duration": 3 + }`, + `{ + "iprpc": { + "creator": "my-moniker", + "spec": "mockspec", + "duration": 3 + } + }`, + `{ + "iprpc": { + "creator": "", + "spec": "mockspec", + "duration": 3 + } + }`, + `{ + "iprpc": { + "spec": "mockspec", + "duration": 3 + } + }`, + `{ + "iprpc": { + "creator": "my-moniker", + "spec": "other-mockspec", + "duration": 3 + } + }`, + `{ + "iprpc": { + "creator": "my-moniker", + "duration": 3 + } + }`, + `{ + "iprpc": { + "creator": "my-moniker", + "spec": "mockspec", + "duration": -3 + } + }`, + `{ + "iprpc": { + "creator": "my-moniker", + "spec": "mockspec" + } + }`, + } + + const ( + EMPTY = iota + NOT_JSON + JSON_NO_IPRPC + VALID_JSON_IPRPC + INVALID_CREATOR_JSON_IPRPC + MISSING_CREATOR_JSON_IPRPC + INVALID_SPEC_JSON_IPRPC + MISSING_SPEC_JSON_IPRPC + INVALID_DURATION_JSON_IPRPC + MISSING_DURATION_JSON_IPRPC + ) + + testCases := []struct { + name string + memoInd int + expectError *sdkerrors.Error + expectedMemo types.IprpcMemo + }{ + { + name: "empty memo", + memoInd: EMPTY, + expectError: types.ErrMemoNotIprpcOverIbc, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "memo not json", + memoInd: NOT_JSON, + expectError: types.ErrMemoNotIprpcOverIbc, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "memo json that is not iprpc", + memoInd: JSON_NO_IPRPC, + expectError: types.ErrMemoNotIprpcOverIbc, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "memo iprpc json valid", + memoInd: VALID_JSON_IPRPC, + expectError: nil, + expectedMemo: types.IprpcMemo{Creator: "my-moniker", Spec: "mockspec", Duration: 3}, + }, + { + name: "invalid memo iprpc json - invalid creator", + memoInd: INVALID_CREATOR_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - missing creator", + memoInd: MISSING_CREATOR_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - invalid spec", + memoInd: INVALID_SPEC_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - missing spec", + memoInd: MISSING_SPEC_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - invalid duration", + memoInd: INVALID_SPEC_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - missing duration", + memoInd: MISSING_SPEC_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + data := ts.createIbcTransferPacketData(memos[tt.memoInd]) + memo, err := ts.Keepers.Rewards.ExtractIprpcMemoFromPacket(ts.Ctx, data) + require.True(t, tt.expectError.Is(err)) + require.True(t, memo.IsEqual(tt.expectedMemo)) + }) + } +} From 14559ca110b3947971bcc5b34598ae9d3bdbc963 Mon Sep 17 00:00:00 2001 From: Oren Date: Wed, 22 May 2024 12:45:45 +0300 Subject: [PATCH 04/16] CNS-960: fix lint --- x/rewards/ibc_middleware.go | 6 ++++-- x/rewards/keeper/keeper.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 8216bb2418..8dc2b54232 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -153,12 +153,14 @@ func (im IBCMiddleware) OnTimeoutPacket( // ICS4Wrapper interface (default implementations) func (im IBCMiddleware) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, - timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (sequence uint64, err error) { + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, +) (sequence uint64, err error) { return im.keeper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, - ack exported.Acknowledgement) error { + ack exported.Acknowledgement, +) error { return im.keeper.WriteAcknowledgement(ctx, chanCap, packet, ack) } diff --git a/x/rewards/keeper/keeper.go b/x/rewards/keeper/keeper.go index ba70e132b5..064cd784b4 100644 --- a/x/rewards/keeper/keeper.go +++ b/x/rewards/keeper/keeper.go @@ -158,12 +158,14 @@ func (k Keeper) ExportRewardsRefillTS(ctx sdk.Context) timerstoretypes.GenesisSt // ICS4 wrapper default implementations func (k Keeper) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, - timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (sequence uint64, err error) { + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, +) (sequence uint64, err error) { return k.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } func (k Keeper) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, - ack exported.Acknowledgement) error { + ack exported.Acknowledgement, +) error { return k.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack) } From c5b5020fc7184d9099d5fe578c69a2ba43db320e Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 6 Jun 2024 14:12:56 +0300 Subject: [PATCH 05/16] CNS-960: nil ack comment --- x/rewards/ibc_middleware.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 8dc2b54232..0a1da6ae5f 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -115,6 +115,11 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet // call the next OnRecvPacket() of the transfer stack to make the rewards module get the IBC tokens ack := im.app.OnRecvPacket(ctx, packet, relayer) if ack == nil || !ack.Success() { + // we check for ack == nil because it means that IBC transfer module did not return an acknowledgement. + // This isn't necessarily an error, but it could indicate unexpected behavior or asynchronous processing + // on the IBC transfer module's side (which returns a non-nil ack when executed without errors). Asynchronous + // processing can be queued processing of packets, interacting with external APIs and more. These can cause + // delays in the IBC-transfer's processing which will make the module return a nil ack until the processing is done. return ack } @@ -131,7 +136,7 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return channeltypes.NewErrorAcknowledgement(err) } - return nil + return channeltypes.NewResultAcknowledgement([]byte{byte(1)}) } func (im IBCMiddleware) OnAcknowledgementPacket( From 1723bb796860c079c1c6a6e659dbe8b849c2c815 Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 6 Jun 2024 15:21:12 +0300 Subject: [PATCH 06/16] CNS-960: make iprpc memo a protobuf --- proto/lavanet/lava/rewards/iprpc.proto | 6 + x/rewards/types/iprpc.go | 6 - x/rewards/types/iprpc.pb.go | 299 +++++++++++++++++++++++-- 3 files changed, 285 insertions(+), 26 deletions(-) diff --git a/proto/lavanet/lava/rewards/iprpc.proto b/proto/lavanet/lava/rewards/iprpc.proto index 151ba86bb4..8e5733ac19 100644 --- a/proto/lavanet/lava/rewards/iprpc.proto +++ b/proto/lavanet/lava/rewards/iprpc.proto @@ -17,4 +17,10 @@ message Specfund { (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false ]; +} + +message IprpcMemo { + string creator = 1; + string spec = 2; + uint64 duration = 3; // Iprpc fund period in months } \ No newline at end of file diff --git a/x/rewards/types/iprpc.go b/x/rewards/types/iprpc.go index 8957e41f74..3ee0f3456e 100644 --- a/x/rewards/types/iprpc.go +++ b/x/rewards/types/iprpc.go @@ -14,12 +14,6 @@ const ( IprpcRewardsCurrentPrefix = "IprpcRewardsCurrent/" ) -type IprpcMemo struct { - Creator string `json:"creator"` - Spec string `json:"spec"` - Duration uint64 `json:"duration"` -} - func (im IprpcMemo) IsEqual(other IprpcMemo) bool { return im.Creator == other.Creator && im.Duration == other.Duration && im.Spec == other.Spec } diff --git a/x/rewards/types/iprpc.pb.go b/x/rewards/types/iprpc.pb.go index 7349489ce8..f8d29aca9a 100644 --- a/x/rewards/types/iprpc.pb.go +++ b/x/rewards/types/iprpc.pb.go @@ -130,34 +130,98 @@ func (m *Specfund) GetFund() github_com_cosmos_cosmos_sdk_types.Coins { return nil } +type IprpcMemo struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + Spec string `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` + Duration uint64 `protobuf:"varint,3,opt,name=duration,proto3" json:"duration,omitempty"` +} + +func (m *IprpcMemo) Reset() { *m = IprpcMemo{} } +func (m *IprpcMemo) String() string { return proto.CompactTextString(m) } +func (*IprpcMemo) ProtoMessage() {} +func (*IprpcMemo) Descriptor() ([]byte, []int) { + return fileDescriptor_1293618a311573f7, []int{2} +} +func (m *IprpcMemo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IprpcMemo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IprpcMemo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IprpcMemo) XXX_Merge(src proto.Message) { + xxx_messageInfo_IprpcMemo.Merge(m, src) +} +func (m *IprpcMemo) XXX_Size() int { + return m.Size() +} +func (m *IprpcMemo) XXX_DiscardUnknown() { + xxx_messageInfo_IprpcMemo.DiscardUnknown(m) +} + +var xxx_messageInfo_IprpcMemo proto.InternalMessageInfo + +func (m *IprpcMemo) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *IprpcMemo) GetSpec() string { + if m != nil { + return m.Spec + } + return "" +} + +func (m *IprpcMemo) GetDuration() uint64 { + if m != nil { + return m.Duration + } + return 0 +} + func init() { proto.RegisterType((*IprpcReward)(nil), "lavanet.lava.rewards.IprpcReward") proto.RegisterType((*Specfund)(nil), "lavanet.lava.rewards.Specfund") + proto.RegisterType((*IprpcMemo)(nil), "lavanet.lava.rewards.IprpcMemo") } func init() { proto.RegisterFile("lavanet/lava/rewards/iprpc.proto", fileDescriptor_1293618a311573f7) } var fileDescriptor_1293618a311573f7 = []byte{ - // 301 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xf3, 0x30, - 0x14, 0x85, 0x93, 0xfc, 0xd1, 0x2f, 0xea, 0x4a, 0x0c, 0x56, 0x87, 0xd2, 0xc1, 0x8d, 0xba, 0x90, - 0x05, 0x9b, 0xc2, 0x13, 0x90, 0x4a, 0x48, 0xac, 0x61, 0x63, 0xa9, 0x12, 0xc7, 0x04, 0x0b, 0x1a, - 0x5b, 0x71, 0x5a, 0x60, 0xe2, 0x15, 0x78, 0x0e, 0x9e, 0xa4, 0x63, 0x47, 0x26, 0x40, 0xc9, 0x8b, - 0xa0, 0xeb, 0x18, 0x04, 0x12, 0xd3, 0xb5, 0x74, 0xce, 0xfd, 0xee, 0xf1, 0x41, 0xd1, 0x5d, 0xb6, - 0xc9, 0x2a, 0xd1, 0x30, 0x98, 0xac, 0x16, 0xf7, 0x59, 0x5d, 0x18, 0x26, 0x75, 0xad, 0x39, 0xd5, - 0xb5, 0x6a, 0x14, 0x1e, 0x39, 0x07, 0x85, 0x49, 0x9d, 0x63, 0x32, 0x2a, 0x55, 0xa9, 0xac, 0x81, - 0xc1, 0xab, 0xf7, 0x4e, 0x08, 0x57, 0x66, 0xa5, 0x0c, 0xcb, 0x33, 0x23, 0xd8, 0x66, 0x9e, 0x8b, - 0x26, 0x9b, 0x33, 0xae, 0x64, 0xd5, 0xeb, 0xb3, 0x1c, 0x0d, 0x2f, 0x00, 0x9d, 0x5a, 0x0a, 0xde, - 0x47, 0x81, 0x2c, 0xc6, 0x7e, 0xe4, 0xc7, 0x61, 0x1a, 0xc8, 0x02, 0x2f, 0x10, 0x32, 0x5a, 0xf0, - 0xe5, 0xf5, 0xba, 0x2a, 0xcc, 0x38, 0x88, 0xfe, 0xc5, 0xc3, 0x13, 0x42, 0xff, 0xba, 0x4f, 0x2f, - 0xb5, 0xe0, 0x60, 0x4b, 0xc2, 0xed, 0xdb, 0xd4, 0x4b, 0x07, 0xb0, 0x77, 0x0e, 0x6b, 0xb3, 0x27, - 0xb4, 0xf7, 0x25, 0x62, 0x8c, 0x42, 0x10, 0xec, 0x89, 0x41, 0x6a, 0xdf, 0x78, 0x89, 0x42, 0xd0, - 0x1c, 0xfe, 0x80, 0xf6, 0x91, 0x29, 0x44, 0xa6, 0x2e, 0x32, 0x5d, 0x28, 0x59, 0x25, 0xc7, 0x40, - 0x7e, 0x79, 0x9f, 0xc6, 0xa5, 0x6c, 0x6e, 0xd6, 0x39, 0xe5, 0x6a, 0xc5, 0xdc, 0xff, 0xfa, 0x71, - 0x64, 0x8a, 0x5b, 0xd6, 0x3c, 0x6a, 0x61, 0xec, 0x82, 0x49, 0x2d, 0x38, 0x39, 0xdb, 0xb6, 0xc4, - 0xdf, 0xb5, 0xc4, 0xff, 0x68, 0x89, 0xff, 0xdc, 0x11, 0x6f, 0xd7, 0x11, 0xef, 0xb5, 0x23, 0xde, - 0xd5, 0xe1, 0x0f, 0xd2, 0xaf, 0xde, 0x1f, 0xbe, 0x9b, 0xb7, 0xb8, 0xfc, 0xbf, 0xad, 0xeb, 0xf4, - 0x33, 0x00, 0x00, 0xff, 0xff, 0x19, 0x8c, 0x21, 0xa2, 0x9e, 0x01, 0x00, 0x00, + // 343 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xb1, 0x4e, 0xf3, 0x30, + 0x14, 0x85, 0x93, 0x34, 0xfa, 0xff, 0xd6, 0x95, 0x18, 0xac, 0x0e, 0xa1, 0x83, 0x5b, 0x75, 0xa1, + 0x0b, 0x36, 0x85, 0x27, 0xa0, 0x95, 0x90, 0x18, 0x58, 0x82, 0x58, 0x58, 0x2a, 0xc7, 0x31, 0xc5, + 0x82, 0xc6, 0x91, 0xed, 0x16, 0x98, 0x78, 0x05, 0x9e, 0x83, 0x27, 0xe9, 0xd8, 0x91, 0x09, 0x50, + 0xfb, 0x22, 0xc8, 0x8e, 0x1b, 0x15, 0x89, 0xe9, 0xda, 0x3a, 0xe7, 0x1e, 0x7f, 0xd7, 0x17, 0xf4, + 0x1f, 0xe9, 0x92, 0x16, 0xdc, 0x10, 0x5b, 0x89, 0xe2, 0x4f, 0x54, 0xe5, 0x9a, 0x88, 0x52, 0x95, + 0x0c, 0x97, 0x4a, 0x1a, 0x09, 0x3b, 0xde, 0x81, 0x6d, 0xc5, 0xde, 0xd1, 0xed, 0xcc, 0xe4, 0x4c, + 0x3a, 0x03, 0xb1, 0xa7, 0xca, 0xdb, 0x45, 0x4c, 0xea, 0xb9, 0xd4, 0x24, 0xa3, 0x9a, 0x93, 0xe5, + 0x28, 0xe3, 0x86, 0x8e, 0x08, 0x93, 0xa2, 0xa8, 0xf4, 0x41, 0x06, 0xda, 0x97, 0x36, 0x3a, 0x75, + 0x29, 0xf0, 0x00, 0x44, 0x22, 0x4f, 0xc2, 0x7e, 0x38, 0x8c, 0xd3, 0x48, 0xe4, 0x70, 0x02, 0x80, + 0x2e, 0x39, 0x9b, 0xde, 0x2d, 0x8a, 0x5c, 0x27, 0x51, 0xbf, 0x31, 0x6c, 0x9f, 0x22, 0xfc, 0xd7, + 0xfb, 0xf8, 0xba, 0xe4, 0xcc, 0xda, 0xc6, 0xf1, 0xea, 0xb3, 0x17, 0xa4, 0x2d, 0xdb, 0x77, 0x61, + 0xdb, 0x06, 0xaf, 0xa0, 0xb9, 0x13, 0x21, 0x04, 0xb1, 0x15, 0xdc, 0x13, 0xad, 0xd4, 0x9d, 0xe1, + 0x14, 0xc4, 0x56, 0xf3, 0xf1, 0x87, 0xb8, 0x42, 0xc6, 0x16, 0x19, 0x7b, 0x64, 0x3c, 0x91, 0xa2, + 0x18, 0x9f, 0xd8, 0xe4, 0xf7, 0xaf, 0xde, 0x70, 0x26, 0xcc, 0xfd, 0x22, 0xc3, 0x4c, 0xce, 0x89, + 0x9f, 0xaf, 0x2a, 0xc7, 0x3a, 0x7f, 0x20, 0xe6, 0xa5, 0xe4, 0xda, 0x35, 0xe8, 0xd4, 0x05, 0x0f, + 0x6e, 0x40, 0xcb, 0x0d, 0x79, 0xc5, 0xe7, 0x12, 0x26, 0xe0, 0x3f, 0x53, 0x9c, 0x1a, 0xa9, 0x3c, + 0xc4, 0xee, 0x5a, 0xb3, 0x45, 0x7b, 0x6c, 0x5d, 0xd0, 0xcc, 0x17, 0x8a, 0x1a, 0x21, 0x8b, 0xa4, + 0xe1, 0xbe, 0xa5, 0xbe, 0x8f, 0xcf, 0x57, 0x1b, 0x14, 0xae, 0x37, 0x28, 0xfc, 0xde, 0xa0, 0xf0, + 0x6d, 0x8b, 0x82, 0xf5, 0x16, 0x05, 0x1f, 0x5b, 0x14, 0xdc, 0x1e, 0xed, 0x01, 0xfe, 0x5a, 0xe7, + 0x73, 0xbd, 0x50, 0x47, 0x99, 0xfd, 0x73, 0x5b, 0x38, 0xfb, 0x09, 0x00, 0x00, 0xff, 0xff, 0x51, + 0x32, 0xe9, 0xcc, 0xf5, 0x01, 0x00, 0x00, } func (m *IprpcReward) Marshal() (dAtA []byte, err error) { @@ -246,6 +310,48 @@ func (m *Specfund) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *IprpcMemo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IprpcMemo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IprpcMemo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Duration != 0 { + i = encodeVarintIprpc(dAtA, i, uint64(m.Duration)) + i-- + dAtA[i] = 0x18 + } + if len(m.Spec) > 0 { + i -= len(m.Spec) + copy(dAtA[i:], m.Spec) + i = encodeVarintIprpc(dAtA, i, uint64(len(m.Spec))) + i-- + dAtA[i] = 0x12 + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintIprpc(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintIprpc(dAtA []byte, offset int, v uint64) int { offset -= sovIprpc(v) base := offset @@ -294,6 +400,26 @@ func (m *Specfund) Size() (n int) { return n } +func (m *IprpcMemo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovIprpc(uint64(l)) + } + l = len(m.Spec) + if l > 0 { + n += 1 + l + sovIprpc(uint64(l)) + } + if m.Duration != 0 { + n += 1 + sovIprpc(uint64(m.Duration)) + } + return n +} + func sovIprpc(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -519,6 +645,139 @@ func (m *Specfund) Unmarshal(dAtA []byte) error { } return nil } +func (m *IprpcMemo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IprpcMemo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IprpcMemo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthIprpc + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthIprpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthIprpc + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthIprpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Spec = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) + } + m.Duration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Duration |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipIprpc(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIprpc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipIprpc(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 6a3ccb77e8bbbc67dbcee9c249f901a75f992857 Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 6 Jun 2024 15:21:30 +0300 Subject: [PATCH 07/16] CNS-960: improve memo decoding code --- x/rewards/keeper/ibc_iprpc.go | 76 ++++++++++-------------------- x/rewards/keeper/ibc_iprpc_test.go | 20 ++++++-- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index 9f6319d23f..395bbec1a8 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -1,12 +1,12 @@ package keeper import ( - "encoding/json" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/lavanet/lava/utils" + "github.com/lavanet/lava/utils/decoder" "github.com/lavanet/lava/x/rewards/types" ) @@ -34,67 +34,43 @@ funds are saved in the IbcIprpcFund scaffolded map. // and holds valid values. If the memo is not in the right format, a custom error is returned so the packet will be skipped and // passed to the next IBC module in the transfer stack normally (and not return an error ack) func (k Keeper) ExtractIprpcMemoFromPacket(ctx sdk.Context, transferData transfertypes.FungibleTokenPacketData) (types.IprpcMemo, error) { - memo := types.IprpcMemo{} - transferMemo := make(map[string]interface{}) - err := json.Unmarshal([]byte(transferData.Memo), &transferMemo) - if err != nil || transferMemo["iprpc"] == nil { + var memo types.IprpcMemo + err := decoder.Decode(transferData.Memo, "iprpc", &memo, nil, nil, nil) + if err != nil { // memo is not for IPRPC over IBC, return custom error to skip processing for this packet return types.IprpcMemo{}, types.ErrMemoNotIprpcOverIbc } + err = k.validateIprpcMemo(ctx, memo) + if err != nil { + return types.IprpcMemo{}, err + } + + return memo, nil +} - if iprpcData, ok := transferMemo["iprpc"].(map[string]interface{}); ok { - // verify creator field - creator, ok := iprpcData["creator"] - if !ok { - return printInvalidMemoWarning(iprpcData, "memo data does not contain creator field") - } - creatorStr, ok := creator.(string) - if !ok { - return printInvalidMemoWarning(iprpcData, "memo's creator field is not string") - } - if creatorStr == "" { - return printInvalidMemoWarning(iprpcData, "memo's creator field cannot be empty") - } - memo.Creator = creatorStr +func (k Keeper) validateIprpcMemo(ctx sdk.Context, memo types.IprpcMemo) error { + if _, found := k.specKeeper.GetSpec(ctx, memo.Spec); !found { + return printInvalidMemoWarning(memo, "memo's spec does not exist on chain") + } - // verify spec field - spec, ok := iprpcData["spec"] - if !ok { - return printInvalidMemoWarning(iprpcData, "memo data does not contain spec field") - } - specStr, ok := spec.(string) - if !ok { - return printInvalidMemoWarning(iprpcData, "memo's spec field is not string") - } - _, found := k.specKeeper.GetSpec(ctx, specStr) - if !found { - return printInvalidMemoWarning(iprpcData, "memo's spec field does not exist on chain") - } - memo.Spec = specStr + if memo.Creator == "" { + return printInvalidMemoWarning(memo, "memo's creator cannot be empty") + } else if _, found := k.specKeeper.GetSpec(ctx, memo.Creator); found { + return printInvalidMemoWarning(memo, "memo's creator cannot be an on-chain spec index") + } - // verify duration field - duration, ok := iprpcData["duration"] - if !ok { - return printInvalidMemoWarning(iprpcData, "memo data does not contain duration field") - } - durationFloat64, ok := duration.(float64) - if !ok { - return printInvalidMemoWarning(iprpcData, "memo's duration field is not uint64") - } - if durationFloat64 <= 0 { - return printInvalidMemoWarning(iprpcData, "memo's duration field cannot be non-positive") - } - memo.Duration = uint64(durationFloat64) + if memo.Duration == uint64(0) { + return printInvalidMemoWarning(memo, "memo's duration cannot be zero") } - return memo, nil + return nil } -func printInvalidMemoWarning(iprpcData map[string]interface{}, description string) (types.IprpcMemo, error) { +func printInvalidMemoWarning(memo types.IprpcMemo, description string) error { utils.LavaFormatWarning("invalid ibc over iprpc memo", fmt.Errorf(description), - utils.LogAttr("data", iprpcData), + utils.LogAttr("memo", memo.String()), ) - return types.IprpcMemo{}, types.ErrIprpcMemoInvalid + return types.ErrIprpcMemoInvalid } func (k Keeper) SetPendingIprpcOverIbcFunds(ctx sdk.Context, memo types.IprpcMemo, amount sdk.Coin) error { diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go index c719034b30..dbe3e9baf9 100644 --- a/x/rewards/keeper/ibc_iprpc_test.go +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -37,6 +37,13 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { "duration": 3 } }`, + `{ + "iprpc": { + "creator": "mockspec", + "spec": "mockspec", + "duration": 3 + } + }`, `{ "iprpc": { "spec": "mockspec", @@ -76,7 +83,8 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { NOT_JSON JSON_NO_IPRPC VALID_JSON_IPRPC - INVALID_CREATOR_JSON_IPRPC + EMPTY_CREATOR_JSON_IPRPC + CREATOR_IS_SPEC_JSON_IPRPC MISSING_CREATOR_JSON_IPRPC INVALID_SPEC_JSON_IPRPC MISSING_SPEC_JSON_IPRPC @@ -115,8 +123,14 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { expectedMemo: types.IprpcMemo{Creator: "my-moniker", Spec: "mockspec", Duration: 3}, }, { - name: "invalid memo iprpc json - invalid creator", - memoInd: INVALID_CREATOR_JSON_IPRPC, + name: "invalid memo iprpc json - invalid creator - empty creator", + memoInd: EMPTY_CREATOR_JSON_IPRPC, + expectError: types.ErrIprpcMemoInvalid, + expectedMemo: types.IprpcMemo{}, + }, + { + name: "invalid memo iprpc json - invalid creator - creator is named like on-chain spec", + memoInd: CREATOR_IS_SPEC_JSON_IPRPC, expectError: types.ErrIprpcMemoInvalid, expectedMemo: types.IprpcMemo{}, }, From a331cd932f755c63ee2cb7c4f2c1f09c00d4dcd9 Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 13 Jun 2024 14:57:43 +0300 Subject: [PATCH 08/16] CNS-960: create types/ibc_iprpc.go and PendingIprpcPool --- app/app.go | 1 + x/rewards/keeper/grpc_query_pools.go | 4 +++ x/rewards/keeper/pool_test.go | 2 ++ x/rewards/types/ibc_iprpc.go | 51 ++++++++++++++++++++++++++++ x/rewards/types/ibc_iprpc_test.go | 29 ++++++++++++++++ x/rewards/types/iprpc.go | 4 --- 6 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 x/rewards/types/ibc_iprpc.go create mode 100644 x/rewards/types/ibc_iprpc_test.go diff --git a/app/app.go b/app/app.go index 218b7e034b..d24bc8457b 100644 --- a/app/app.go +++ b/app/app.go @@ -283,6 +283,7 @@ var ( string(rewardsmoduletypes.ProvidersRewardsAllocationPool): {authtypes.Minter, authtypes.Staking}, dualstakingmoduletypes.ModuleName: {authtypes.Burner, authtypes.Staking}, string(rewardsmoduletypes.IprpcPoolName): nil, + string(rewardsmoduletypes.PendingIprpcPoolName): nil, // this line is used by starport scaffolding # stargate/app/maccPerms } ) diff --git a/x/rewards/keeper/grpc_query_pools.go b/x/rewards/keeper/grpc_query_pools.go index 4e6d35d3d6..d3d463cf1c 100644 --- a/x/rewards/keeper/grpc_query_pools.go +++ b/x/rewards/keeper/grpc_query_pools.go @@ -37,6 +37,10 @@ func (k Keeper) Pools(goCtx context.Context, req *types.QueryPoolsRequest) (*typ Name: string(types.IprpcPoolName), Balance: k.TotalPoolTokens(ctx, types.IprpcPoolName), }, + { + Name: string(types.PendingIprpcPoolName), + Balance: k.TotalPoolTokens(ctx, types.PendingIprpcPoolName), + }, } estimatedBlocksToRefill := k.BlocksToNextTimerExpiry(ctx) diff --git a/x/rewards/keeper/pool_test.go b/x/rewards/keeper/pool_test.go index 6bdf71a995..142a4c1a0d 100644 --- a/x/rewards/keeper/pool_test.go +++ b/x/rewards/keeper/pool_test.go @@ -64,6 +64,8 @@ func TestRewardsModuleSetup(t *testing.T) { require.Equal(t, (allocationPoolBalance/lifetime)-blockReward, pool.Balance.AmountOf(ts.BondDenom()).Int64()) case string(types.IprpcPoolName): require.True(t, pool.Balance.Empty()) + case string(types.PendingIprpcPoolName): + require.True(t, pool.Balance.Empty()) } } diff --git a/x/rewards/types/ibc_iprpc.go b/x/rewards/types/ibc_iprpc.go new file mode 100644 index 0000000000..8c5411cad5 --- /dev/null +++ b/x/rewards/types/ibc_iprpc.go @@ -0,0 +1,51 @@ +package types + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func (im IprpcMemo) IsEqual(other IprpcMemo) bool { + return im.Creator == other.Creator && im.Duration == other.Duration && im.Spec == other.Spec +} + +func CreateIprpcMemo(creator string, spec string, duration uint64) (memoStr string, err error) { + memo := IprpcMemo{ + Creator: creator, + Spec: spec, + Duration: duration, + } + + // memo wrapper allows marshaling the memo as a nested JSON with a primary key "iprpc" + memoWrapper := struct { + Iprpc IprpcMemo `json:"iprpc"` + }{ + Iprpc: memo, + } + + bz, err := json.Marshal(memoWrapper) + if err != nil { + return "", err + } + + return string(bz), nil +} + +// IbcIprpcReceiverAddress is a temporary address that holds the funds from an IPRPC over IBC request. The funds are +// then immediately transferred to the pending IPRPC pool +// Note, the NewModuleAddress() function is used for convenience. The IbcIprpcReceiver is not a module account +const ( + IbcIprpcReceiver = "iprpc" +) + +func IbcIprpcReceiverAddress() sdk.AccAddress { + return authtypes.NewModuleAddress(IbcIprpcReceiver) +} + +// Pending IPRPC Pool: +// Pool that holds the funds of pending IPRPC fund requests (which originate from an ibc-transfer TX) +const ( + PendingIprpcPoolName Pool = "pending_iprpc_pool" +) diff --git a/x/rewards/types/ibc_iprpc_test.go b/x/rewards/types/ibc_iprpc_test.go new file mode 100644 index 0000000000..2b993cfce2 --- /dev/null +++ b/x/rewards/types/ibc_iprpc_test.go @@ -0,0 +1,29 @@ +package types_test + +import ( + "testing" + + "github.com/lavanet/lava/x/rewards/types" + "github.com/stretchr/testify/require" +) + +// TestIprpcMemoIsEqual tests IprpcMemo method: IsEqual +func TestIprpcMemoIsEqual(t *testing.T) { + memo := types.IprpcMemo{Creator: "creator", Spec: "spec", Duration: 3} + template := []struct { + name string + memo types.IprpcMemo + isEqual bool + }{ + {"equal", types.IprpcMemo{Creator: "creator", Spec: "spec", Duration: 3}, true}, + {"different creator", types.IprpcMemo{Creator: "creator2", Spec: "spec", Duration: 3}, false}, + {"different spec", types.IprpcMemo{Creator: "creator", Spec: "spec2", Duration: 3}, false}, + {"different duration", types.IprpcMemo{Creator: "creator", Spec: "spec", Duration: 2}, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.isEqual, memo.IsEqual(tt.memo)) + }) + } +} diff --git a/x/rewards/types/iprpc.go b/x/rewards/types/iprpc.go index 3ee0f3456e..38cdcff1fd 100644 --- a/x/rewards/types/iprpc.go +++ b/x/rewards/types/iprpc.go @@ -13,7 +13,3 @@ const ( // IprpcRewardsCurrentPrefix is the prefix to retrieve all IprpcRewardsCurrent IprpcRewardsCurrentPrefix = "IprpcRewardsCurrent/" ) - -func (im IprpcMemo) IsEqual(other IprpcMemo) bool { - return im.Creator == other.Creator && im.Duration == other.Duration && im.Spec == other.Spec -} From 154a4201cee11ca7288dcf1b91a1295c6354592c Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 13 Jun 2024 15:17:26 +0300 Subject: [PATCH 09/16] CNS-960: update middleware to work with pending pool and temp addrees IbcIprpcReceiver --- x/rewards/ibc_middleware.go | 87 +++++++++++++++++++++-------- x/rewards/keeper/ibc_iprpc.go | 9 ++- x/rewards/keeper/ibc_iprpc_test.go | 68 ++++------------------ x/rewards/types/expected_keepers.go | 1 + 4 files changed, 82 insertions(+), 83 deletions(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 0a1da6ae5f..84e7871ce3 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -86,6 +86,14 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return channeltypes.NewErrorAcknowledgement(err) } + // sanity check: validate data + if err := data.ValidateBasic(); err != nil { + detaliedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", err, + utils.LogAttr("data", data), + ) + return channeltypes.NewErrorAcknowledgement(detaliedErr) + } + // extract the packet's memo memo, err := im.keeper.ExtractIprpcMemoFromPacket(ctx, data) if errors.Is(err, types.ErrMemoNotIprpcOverIbc) { @@ -101,19 +109,8 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return channeltypes.NewErrorAcknowledgement(err) } - // change the ibc-transfer packet receiver address to be the rewards module address and empty the memo - data.Receiver = im.keeper.GetModuleAddress() - data.Memo = "" - marshelledData, err := transfertypes.ModuleCdc.MarshalJSON(&data) - if err != nil { - utils.LavaFormatError("rewards module IBC middleware processing failed, cannot marshal packet data", err, - utils.LogAttr("data", data)) - return channeltypes.NewErrorAcknowledgement(err) - } - packet.Data = marshelledData - - // call the next OnRecvPacket() of the transfer stack to make the rewards module get the IBC tokens - ack := im.app.OnRecvPacket(ctx, packet, relayer) + // send the IBC transfer to get the tokens + ack := im.sendIbcTransfer(ctx, packet, relayer, data.Sender, types.IbcIprpcReceiverAddress().String()) if ack == nil || !ack.Success() { // we check for ack == nil because it means that IBC transfer module did not return an acknowledgement. // This isn't necessarily an error, but it could indicate unexpected behavior or asynchronous processing @@ -123,19 +120,41 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return ack } - // set pending IPRPC over IBC requests on-chain - amountInt, ok := sdk.NewIntFromString(data.Amount) + // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress + amount, ok := sdk.NewIntFromString(data.Amount) if !ok { - utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("cannot decode coin amount"), - utils.LogAttr("data", data)) - return channeltypes.NewErrorAcknowledgement(err) + detaliedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("invalid amount in ibc-transfer data"), + utils.LogAttr("data", data), + ) + return channeltypes.NewErrorAcknowledgement(detaliedErr) } - amount := sdk.NewCoin(data.Denom, amountInt) - err = im.keeper.SetPendingIprpcOverIbcFunds(ctx, memo, amount) - if err != nil { - return channeltypes.NewErrorAcknowledgement(err) + ibcTokens := transfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, data.Denom, amount) + + // transfer the token from the temp IbcIprpcReceiverAddress to the pending IPRPC fund request pool + err1 := im.keeper.SendIbcTokensToPendingIprpcPool(ctx, ibcTokens) + if err1 != nil { + // we couldn't transfer the funds to the pending IPRPC fund request pool, try moving it to the community pool + err2 := im.keeper.FundCommunityPoolFromIbcIprpcReceiver(ctx, ibcTokens) + if err2 != nil { + // community pool transfer failed, token kept locked in IbcIprpcReceiverAddress, return err ack + err := utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool or community pool, tokens are locked in IbcIprpcReceiverAddress", + errors.Join(err1, err2), + utils.LogAttr("amount", ibcTokens.String()), + utils.LogAttr("sender", data.Sender), + ) + return channeltypes.NewErrorAcknowledgement(err) + } else { + err := utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool, sent to community pool", err1, + utils.LogAttr("amount", ibcTokens.String()), + utils.LogAttr("sender", data.Sender), + ) + return channeltypes.NewErrorAcknowledgement(err) + } } + // TODO: define a PendingIbcIprpcFund store to keep track on pending requests. Also create an event that + // a new pending request has been created + return channeltypes.NewResultAcknowledgement([]byte{byte(1)}) } @@ -172,3 +191,27 @@ func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilit func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { return im.keeper.GetAppVersion(ctx, portID, channelID) } + +// sendIbcTransfer sends the ibc-transfer packet to the IBC Transfer module +func (im IBCMiddleware) sendIbcTransfer(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, sender string, receiver string) exported.Acknowledgement { + // unmarshal the packet's data with the transfer module codec (expect an ibc-transfer packet) + var data transfertypes.FungibleTokenPacketData + if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + // change the ibc-transfer packet data with sender and receiver + data.Sender = sender + data.Receiver = receiver + data.Memo = "" + marshelledData, err := transfertypes.ModuleCdc.MarshalJSON(&data) + if err != nil { + utils.LavaFormatError("rewards module IBC middleware processing failed, cannot marshal packet data", err, + utils.LogAttr("data", data)) + return channeltypes.NewErrorAcknowledgement(err) + } + packet.Data = marshelledData + + // call the next OnRecvPacket() of the transfer stack to make the IBC Transfer module's OnRecvPacket send the IBC tokens + return im.app.OnRecvPacket(ctx, packet, relayer) +} diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index 395bbec1a8..8eb8e04669 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -73,7 +73,10 @@ func printInvalidMemoWarning(memo types.IprpcMemo, description string) error { return types.ErrIprpcMemoInvalid } -func (k Keeper) SetPendingIprpcOverIbcFunds(ctx sdk.Context, memo types.IprpcMemo, amount sdk.Coin) error { - // TODO: implement - return nil +func (k Keeper) SendIbcTokensToPendingIprpcPool(ctx sdk.Context, amount sdk.Coin) error { + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, types.IbcIprpcReceiverAddress(), string(types.PendingIprpcPoolName), sdk.NewCoins(amount)) +} + +func (k Keeper) FundCommunityPoolFromIbcIprpcReceiver(ctx sdk.Context, amount sdk.Coin) error { + return k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(amount), types.IbcIprpcReceiverAddress()) } diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go index dbe3e9baf9..667e642817 100644 --- a/x/rewards/keeper/ibc_iprpc_test.go +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -19,63 +19,15 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { memos := []string{ "", "blabla", - `{ - "client": "Bruce", - "duration": 3 - }`, - `{ - "iprpc": { - "creator": "my-moniker", - "spec": "mockspec", - "duration": 3 - } - }`, - `{ - "iprpc": { - "creator": "", - "spec": "mockspec", - "duration": 3 - } - }`, - `{ - "iprpc": { - "creator": "mockspec", - "spec": "mockspec", - "duration": 3 - } - }`, - `{ - "iprpc": { - "spec": "mockspec", - "duration": 3 - } - }`, - `{ - "iprpc": { - "creator": "my-moniker", - "spec": "other-mockspec", - "duration": 3 - } - }`, - `{ - "iprpc": { - "creator": "my-moniker", - "duration": 3 - } - }`, - `{ - "iprpc": { - "creator": "my-moniker", - "spec": "mockspec", - "duration": -3 - } - }`, - `{ - "iprpc": { - "creator": "my-moniker", - "spec": "mockspec" - } - }`, + `{"client":"bruce","duration":2}`, + `{"iprpc":{"creator":"my-moniker","duration":2,"spec":"mockspec"}}`, + `{"iprpc":{"creator":"","duration":2,"spec":"mockspec"}}`, + `{"iprpc":{"creator":"mockspec","duration":2,"spec":"mockspec"}}`, + `{"iprpc":{"creator":"mockspec","duration":2,"spec":"mockspec"}}`, + `{"iprpc":{"creator":"my-moniker","duration":2,"spec":"other-mockspec"}}`, + `{"iprpc":{"creator":"my-moniker","duration":2}}`, + `{"iprpc":{"creator":"my-moniker","duration":-2,"spec":"mockspec"}}`, + `{"iprpc":{"creator":"my-moniker","spec":"mockspec"}}`, } const ( @@ -120,7 +72,7 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { name: "memo iprpc json valid", memoInd: VALID_JSON_IPRPC, expectError: nil, - expectedMemo: types.IprpcMemo{Creator: "my-moniker", Spec: "mockspec", Duration: 3}, + expectedMemo: types.IprpcMemo{Creator: "my-moniker", Spec: "mockspec", Duration: 2}, }, { name: "invalid memo iprpc json - invalid creator - empty creator", diff --git a/x/rewards/types/expected_keepers.go b/x/rewards/types/expected_keepers.go index 118ea7e94d..17cc263afe 100644 --- a/x/rewards/types/expected_keepers.go +++ b/x/rewards/types/expected_keepers.go @@ -61,5 +61,6 @@ type DistributionKeeper interface { GetParams(ctx sdk.Context) (params distributiontypes.Params) GetFeePool(ctx sdk.Context) (feePool distributiontypes.FeePool) SetFeePool(ctx sdk.Context, feePool distributiontypes.FeePool) + FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error // Methods imported from bank should be defined here } From 1676677b3ce20083f5eb122a88209ae65d6e4e74 Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 18 Jun 2024 14:03:42 +0300 Subject: [PATCH 10/16] CNS-960: handling async packets --- x/rewards/ibc_middleware.go | 65 ++++++++++++++++++++++------------- x/rewards/keeper/ibc_iprpc.go | 36 ++++++++++++++++--- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 84e7871ce3..6d24fb4641 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -117,39 +117,27 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet // on the IBC transfer module's side (which returns a non-nil ack when executed without errors). Asynchronous // processing can be queued processing of packets, interacting with external APIs and more. These can cause // delays in the IBC-transfer's processing which will make the module return a nil ack until the processing is done. + // Asyncronous acks are handled in the WriteAcknowledgement middleware method. return ack } // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress amount, ok := sdk.NewIntFromString(data.Amount) if !ok { - detaliedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("invalid amount in ibc-transfer data"), + detailedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("invalid amount in ibc-transfer data"), utils.LogAttr("data", data), ) - return channeltypes.NewErrorAcknowledgement(detaliedErr) + return channeltypes.NewErrorAcknowledgement(detailedErr) } ibcTokens := transfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, data.Denom, amount) - // transfer the token from the temp IbcIprpcReceiverAddress to the pending IPRPC fund request pool - err1 := im.keeper.SendIbcTokensToPendingIprpcPool(ctx, ibcTokens) - if err1 != nil { - // we couldn't transfer the funds to the pending IPRPC fund request pool, try moving it to the community pool - err2 := im.keeper.FundCommunityPoolFromIbcIprpcReceiver(ctx, ibcTokens) - if err2 != nil { - // community pool transfer failed, token kept locked in IbcIprpcReceiverAddress, return err ack - err := utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool or community pool, tokens are locked in IbcIprpcReceiverAddress", - errors.Join(err1, err2), - utils.LogAttr("amount", ibcTokens.String()), - utils.LogAttr("sender", data.Sender), - ) - return channeltypes.NewErrorAcknowledgement(err) - } else { - err := utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool, sent to community pool", err1, - utils.LogAttr("amount", ibcTokens.String()), - utils.LogAttr("sender", data.Sender), - ) - return channeltypes.NewErrorAcknowledgement(err) - } + // send the IBC tokens from IbcIprpcReceiver to PendingIprpcPool + err = im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens) + if err != nil { + detailedErr := utils.LavaFormatError("sending token from IbcIprpcReceiver to the PendingIprpcPool failed", err, + utils.LogAttr("sender", data.Sender), + ) + return channeltypes.NewErrorAcknowledgement(detailedErr) } // TODO: define a PendingIbcIprpcFund store to keep track on pending requests. Also create an event that @@ -182,10 +170,41 @@ func (im IBCMiddleware) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Cap return im.keeper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) } +// WriteAcknowledgement is called when handling async acks. Since the OnRecvPacket code returns on a nil ack (which indicates +// that an async ack will occur), funds can stay stuck in the IbcIprpcReceiver account (which is a temp account that should +// not hold funds). This code simply does the missing functionaliy that OnRecvPacket would do if the ack was not nil. func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, ack exported.Acknowledgement, ) error { - return im.keeper.WriteAcknowledgement(ctx, chanCap, packet, ack) + err := im.keeper.WriteAcknowledgement(ctx, chanCap, packet, ack) + if err != nil { + return err + } + + // unmarshal the packet's data with the transfer module codec (expect an ibc-transfer packet) + var data transfertypes.FungibleTokenPacketData + if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return err + } + + // sanity check: validate data + if err := data.ValidateBasic(); err != nil { + return utils.LavaFormatError("handling async transfer packet failed", err, + utils.LogAttr("data", data), + ) + } + + // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress + amount, ok := sdk.NewIntFromString(data.Amount) + if !ok { + return utils.LavaFormatError("handling async transfer packet failed", fmt.Errorf("invalid amount in ibc-transfer data"), + utils.LogAttr("data", data), + ) + } + ibcTokens := transfertypes.GetTransferCoin(packet.GetDestPort(), packet.GetDestChannel(), data.Denom, amount) + + // send the tokens from the IbcIprpcReceiver to the PendingIprpcPool + return im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens) } func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index 8eb8e04669..f968a6017a 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -1,6 +1,7 @@ package keeper import ( + "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -73,10 +74,35 @@ func printInvalidMemoWarning(memo types.IprpcMemo, description string) error { return types.ErrIprpcMemoInvalid } -func (k Keeper) SendIbcTokensToPendingIprpcPool(ctx sdk.Context, amount sdk.Coin) error { - return k.bankKeeper.SendCoinsFromAccountToModule(ctx, types.IbcIprpcReceiverAddress(), string(types.PendingIprpcPoolName), sdk.NewCoins(amount)) -} +// SendIbcIprpcReceiverTokensToPendingIprpcPool sends tokens from the IbcIprpcReceiver to the PendingIprpcPool as part of the IPRPC over IBC mechanism +// if the transfer fails, we try to transfer the tokens to the community pool +func (k Keeper) SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx sdk.Context, amount sdk.Coin) error { + // sanity check: IbcIprpcReceiver has enough funds to send + ibcIprpcReceiverBalances := k.bankKeeper.GetAllBalances(ctx, types.IbcIprpcReceiverAddress()) + if ibcIprpcReceiverBalances.AmountOf(amount.Denom).LT(amount.Amount) { + return utils.LavaFormatError("critical: IbcIprpcReceiver does not have enough funds to send to PendingIprpcPool", fmt.Errorf("send funds to PendingIprpcPool failed"), + utils.LogAttr("IbcIprpcReceiver_balance", ibcIprpcReceiverBalances.AmountOf(amount.Denom)), + utils.LogAttr("amount_to_send_to_PendingIprpcPool", amount.Amount), + ) + } -func (k Keeper) FundCommunityPoolFromIbcIprpcReceiver(ctx sdk.Context, amount sdk.Coin) error { - return k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(amount), types.IbcIprpcReceiverAddress()) + // transfer the token from the temp IbcIprpcReceiverAddress to the pending IPRPC fund request pool + err1 := k.bankKeeper.SendCoinsFromAccountToModule(ctx, types.IbcIprpcReceiverAddress(), string(types.PendingIprpcPoolName), sdk.NewCoins(amount)) + if err1 != nil { + // we couldn't transfer the funds to the pending IPRPC fund request pool, try moving it to the community pool + err2 := k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(amount), types.IbcIprpcReceiverAddress()) + if err2 != nil { + // community pool transfer failed, token kept locked in IbcIprpcReceiverAddress, return err ack + return utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool or community pool, tokens are locked in IbcIprpcReceiverAddress", + errors.Join(err1, err2), + utils.LogAttr("amount", amount), + ) + } else { + return utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool, sent to community pool", err1, + utils.LogAttr("amount", amount), + ) + } + } + + return nil } From fc25eb2003719e784187561c951947bfc6427e47 Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 18 Jun 2024 14:25:59 +0300 Subject: [PATCH 11/16] CNS-960: lint --- x/rewards/ibc_middleware.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 6d24fb4641..af4dbd2b68 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -117,7 +117,7 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet // on the IBC transfer module's side (which returns a non-nil ack when executed without errors). Asynchronous // processing can be queued processing of packets, interacting with external APIs and more. These can cause // delays in the IBC-transfer's processing which will make the module return a nil ack until the processing is done. - // Asyncronous acks are handled in the WriteAcknowledgement middleware method. + // Asynchronous acks are handled in the WriteAcknowledgement middleware method. return ack } @@ -172,7 +172,7 @@ func (im IBCMiddleware) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Cap // WriteAcknowledgement is called when handling async acks. Since the OnRecvPacket code returns on a nil ack (which indicates // that an async ack will occur), funds can stay stuck in the IbcIprpcReceiver account (which is a temp account that should -// not hold funds). This code simply does the missing functionaliy that OnRecvPacket would do if the ack was not nil. +// not hold funds). This code simply does the missing functionally that OnRecvPacket would do if the ack was not nil. func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, ack exported.Acknowledgement, ) error { From 98110fe87df624e9c5183d9b5d42a19ff2f85aba Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 18 Jun 2024 15:01:12 +0300 Subject: [PATCH 12/16] CNS-960: fix coderabbitai comments --- x/rewards/keeper/ibc_iprpc.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index f968a6017a..bebccd0811 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -39,6 +39,7 @@ func (k Keeper) ExtractIprpcMemoFromPacket(ctx sdk.Context, transferData transfe err := decoder.Decode(transferData.Memo, "iprpc", &memo, nil, nil, nil) if err != nil { // memo is not for IPRPC over IBC, return custom error to skip processing for this packet + utils.LavaFormatWarning("memo is not in IPRPC over IBC format", err, utils.LogAttr("memo", memo)) return types.IprpcMemo{}, types.ErrMemoNotIprpcOverIbc } err = k.validateIprpcMemo(ctx, memo) @@ -95,11 +96,13 @@ func (k Keeper) SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx sdk.Context, am // community pool transfer failed, token kept locked in IbcIprpcReceiverAddress, return err ack return utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool or community pool, tokens are locked in IbcIprpcReceiverAddress", errors.Join(err1, err2), - utils.LogAttr("amount", amount), + utils.LogAttr("IbcIprpcReceiver_balance", ibcIprpcReceiverBalances.AmountOf(amount.Denom)), + utils.LogAttr("amount_to_send_to_PendingIprpcPool", amount.Amount), ) } else { return utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool, sent to community pool", err1, - utils.LogAttr("amount", amount), + utils.LogAttr("IbcIprpcReceiver_balance", ibcIprpcReceiverBalances.AmountOf(amount.Denom)), + utils.LogAttr("amount_to_send_to_PendingIprpcPool", amount.Amount), ) } } From 0b5f338f11c007352e9f0d1a2de4ad87c88d44bf Mon Sep 17 00:00:00 2001 From: oren-lava <111131399+oren-lava@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:08:54 +0300 Subject: [PATCH 13/16] feat: IPRPC over IBC: Part 2 - CNS-964: CLI for submitting an IPRPC over IBC proposal (#1450) * CNS-964: generate ibc transfer tx cli * CNS-964: PR fixes * CNS-964: lint fix * CNS-964: replace generate-ibc-transfer with submit-ibc-transfer * feat: IPRPC over IBC: Part 3 - CNS-965: Pending IPRPC IBC fund (#1452) * CNS-965: scaffold param IbcIprpcExpiration * CNS-965: create pending iprpc fund * CNS-965: fix unit tests * CNS-965: move pending iprpc to seperate files and add IsExpired * CNS-965: renamed to PendingIbcIprpcFund + bug fixes * CNS-965: unit tests * CNS-965: remove expired PendingIbcIprpcFunds in begin block * CNS-965: unit tests * CNS-965: comment * CNS-965: divide fund when creating a new pending ibc iprpc fund * CNS-965: lint fix * CNS-965: fixes * CNS-965: add events * CNS-965: fixes after merge * CNS-965: revert ibc transfer in case of middleware failure * CNS-965: pending iprpc pool + reorder code that ibc-transfer is last in middleware * CNS-965: migrator for IbcIprpcExpiration param * CNS-965: make expired pending iprpc funds be taken from the pending pool * CNS-965: small fixes * feat: IPRPC over IBC: Part 4 - CNS-966: pending ibc iprpc fund query (#1457) * CNS-966: implemented pending ibc iprpc query + unit test * CNS-966: fix help section * feat: IPRPC over IBC: Part 5 - CNS-967: Cover pending IBC IPRPC fund costs (#1470) * CNS-967: implement cover ibc iprpc funds TX * CNS-967: make gov module not pay min cost * CNS-967: unit test * CNS-967: partial merge from CNS-966 * CNS-967: small fixes * feat: IPRPC over IBC: Part 6 - CNS-968: IBC middleware testing (#1481) * CNS-968: add mock transfer keeper and IBC middleware to tester * CNS-968: change middleware success ack and move create iprpc memo to types * CNS-968: create ibc transfer helper func for tests and update tests * CNS-968: lint fix * feat: IPRPC over IBC: Part 7 - CNS-969 update README (#1482) * CNS-969: improve comments * CNS-969: rename param and move event * CNS-969: update README * CNS-969: small comment fix --- proto/lavanet/lava/rewards/genesis.proto | 1 + proto/lavanet/lava/rewards/iprpc.proto | 9 + proto/lavanet/lava/rewards/params.proto | 8 +- proto/lavanet/lava/rewards/query.proto | 20 + proto/lavanet/lava/rewards/tx.proto | 9 + scripts/test/cli_test.sh | 7 + testutil/common/tester.go | 37 +- testutil/keeper/keepers_init.go | 9 + testutil/keeper/mock_keepers.go | 103 +++ utils/maps/maps.go | 14 + x/rewards/README.md | 37 +- x/rewards/client/cli/query.go | 1 + .../cli/query_pending_ibc_iprpc_funds.go | 51 ++ x/rewards/client/cli/tx.go | 2 + .../cli/tx_cover_ibc_iprpc_fund_cost.go | 49 ++ .../cli/tx_submit_ibc_iprpc_proposal.go | 260 ++++++ x/rewards/genesis.go | 4 + x/rewards/genesis_test.go | 20 + x/rewards/ibc_middleware.go | 49 +- .../grpc_query_pending_ibc_iprpc_funds.go | 63 ++ x/rewards/keeper/helpers_test.go | 43 + x/rewards/keeper/ibc_iprpc.go | 252 +++++- x/rewards/keeper/ibc_iprpc_test.go | 398 +++++++++ x/rewards/keeper/iprpc.go | 84 +- x/rewards/keeper/iprpc_reward.go | 19 +- x/rewards/keeper/iprpc_test.go | 10 +- x/rewards/keeper/keeper.go | 1 + x/rewards/keeper/migrations.go | 7 + .../msg_server_cover_ibc_iprpc_fund_cost.go | 32 + x/rewards/keeper/msg_server_fund_iprpc.go | 1 + x/rewards/keeper/params.go | 9 + x/rewards/keeper/pool_test.go | 1 + x/rewards/module.go | 8 +- x/rewards/types/errors.go | 7 +- x/rewards/types/genesis.go | 21 +- x/rewards/types/genesis.pb.go | 135 +++- x/rewards/types/genesis_test.go | 14 + x/rewards/types/ibc_iprpc.go | 36 +- x/rewards/types/ibc_iprpc_test.go | 68 ++ x/rewards/types/iprpc.pb.go | 429 +++++++++- .../message_cover_ibc_iprpc_fund_cost.go | 48 ++ .../message_cover_ibc_iprpc_fund_cost_test.go | 43 + x/rewards/types/params.go | 27 + x/rewards/types/params.pb.go | 116 ++- x/rewards/types/query.pb.go | 758 ++++++++++++++++-- x/rewards/types/query.pb.gw.go | 101 +++ x/rewards/types/tx.pb.go | 422 +++++++++- 47 files changed, 3577 insertions(+), 266 deletions(-) create mode 100644 x/rewards/client/cli/query_pending_ibc_iprpc_funds.go create mode 100644 x/rewards/client/cli/tx_cover_ibc_iprpc_fund_cost.go create mode 100644 x/rewards/client/cli/tx_submit_ibc_iprpc_proposal.go create mode 100644 x/rewards/keeper/grpc_query_pending_ibc_iprpc_funds.go create mode 100644 x/rewards/keeper/msg_server_cover_ibc_iprpc_fund_cost.go create mode 100644 x/rewards/types/message_cover_ibc_iprpc_fund_cost.go create mode 100644 x/rewards/types/message_cover_ibc_iprpc_fund_cost_test.go diff --git a/proto/lavanet/lava/rewards/genesis.proto b/proto/lavanet/lava/rewards/genesis.proto index 90d8e9f533..85fc3283a3 100644 --- a/proto/lavanet/lava/rewards/genesis.proto +++ b/proto/lavanet/lava/rewards/genesis.proto @@ -20,5 +20,6 @@ message GenesisState { cosmos.base.v1beta1.Coin min_iprpc_cost = 5 [(gogoproto.nullable) = false]; repeated IprpcReward iprpc_rewards = 6 [(gogoproto.nullable) = false]; uint64 iprpc_rewards_current = 7; + repeated PendingIbcIprpcFund pending_ibc_iprpc_funds = 8 [(gogoproto.nullable) = false]; // this line is used by starport scaffolding # genesis/proto/state } \ No newline at end of file diff --git a/proto/lavanet/lava/rewards/iprpc.proto b/proto/lavanet/lava/rewards/iprpc.proto index 8e5733ac19..7c2d0f135b 100644 --- a/proto/lavanet/lava/rewards/iprpc.proto +++ b/proto/lavanet/lava/rewards/iprpc.proto @@ -23,4 +23,13 @@ message IprpcMemo { string creator = 1; string spec = 2; uint64 duration = 3; // Iprpc fund period in months +} + +message PendingIbcIprpcFund { + uint64 index = 1; // unique index + string creator = 2; + string spec = 3; + uint64 duration = 4; + cosmos.base.v1beta1.Coin fund = 5 [(gogoproto.nullable) = false]; + uint64 expiry = 6; // expiry timestamp } \ No newline at end of file diff --git a/proto/lavanet/lava/rewards/params.proto b/proto/lavanet/lava/rewards/params.proto index f2107fc39a..03c0217de8 100644 --- a/proto/lavanet/lava/rewards/params.proto +++ b/proto/lavanet/lava/rewards/params.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package lavanet.lava.rewards; import "gogoproto/gogo.proto"; - +import "google/protobuf/duration.proto"; option go_package = "github.com/lavanet/lava/x/rewards/types"; // Params defines the parameters for the module. @@ -40,4 +40,10 @@ message Params { (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false ]; + + google.protobuf.Duration ibc_iprpc_expiration = 7 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.moretags) = "yaml:\"ibc_iprpc_expiration\"" + ]; } \ No newline at end of file diff --git a/proto/lavanet/lava/rewards/query.proto b/proto/lavanet/lava/rewards/query.proto index d8ba5a3c33..b63484b82e 100644 --- a/proto/lavanet/lava/rewards/query.proto +++ b/proto/lavanet/lava/rewards/query.proto @@ -47,6 +47,11 @@ service Query { rpc IprpcSpecReward(QueryIprpcSpecRewardRequest) returns (QueryIprpcSpecRewardResponse) { option (google.api.http).get = "/lavanet/lava/rewards/iprpc_spec_reward/{spec}"; } + + // PendingIbcIprpcFunds queries for a spec's IPRPC reward + rpc PendingIbcIprpcFunds(QueryPendingIbcIprpcFundsRequest) returns (QueryPendingIbcIprpcFundsResponse) { + option (google.api.http).get = "/lavanet/lava/rewards/pending_ibc_iprpc_funds/{filter}"; + } // this line is used by starport scaffolding # 2 } @@ -134,4 +139,19 @@ message QueryIprpcSpecRewardResponse { uint64 current_month_id = 2; } +// QueryPendingIbcIprpcFundsRequest is request type for the Query/PendingIbcIprpcFund RPC method. +message QueryPendingIbcIprpcFundsRequest { + string filter = 1; // can be an uint64 index, creator name and spec name +} + +message PendingIbcIprpcFundInfo { + PendingIbcIprpcFund pending_ibc_iprpc_fund = 1 [(gogoproto.nullable) = false]; + cosmos.base.v1beta1.Coin cost = 2 [(gogoproto.nullable) = false]; // equals to min_iprpc_cost * duration +} + +// QueryPendingIbcIprpcFundsResponse is response type for the Query/PendingIbcIprpcFund RPC method. +message QueryPendingIbcIprpcFundsResponse { + repeated PendingIbcIprpcFundInfo pending_ibc_iprpc_funds_info = 1 [(gogoproto.nullable) = false]; +} + // this line is used by starport scaffolding # 3 \ No newline at end of file diff --git a/proto/lavanet/lava/rewards/tx.proto b/proto/lavanet/lava/rewards/tx.proto index 1e4c23daee..fc3f34b574 100644 --- a/proto/lavanet/lava/rewards/tx.proto +++ b/proto/lavanet/lava/rewards/tx.proto @@ -12,6 +12,7 @@ option go_package = "github.com/lavanet/lava/x/rewards/types"; service Msg { rpc SetIprpcData(MsgSetIprpcData) returns (MsgSetIprpcDataResponse); rpc FundIprpc(MsgFundIprpc) returns (MsgFundIprpcResponse); + rpc CoverIbcIprpcFundCost(MsgCoverIbcIprpcFundCost) returns (MsgCoverIbcIprpcFundCostResponse); // this line is used by starport scaffolding # proto/tx/rpc } @@ -37,4 +38,12 @@ message MsgFundIprpc { message MsgFundIprpcResponse { } +message MsgCoverIbcIprpcFundCost { + string creator = 1; + uint64 index = 2; // PendingIbcIprpcFund index +} + +message MsgCoverIbcIprpcFundCostResponse { +} + // this line is used by starport scaffolding # proto/tx/message \ No newline at end of file diff --git a/scripts/test/cli_test.sh b/scripts/test/cli_test.sh index d64f74c32d..0d6aab715f 100755 --- a/scripts/test/cli_test.sh +++ b/scripts/test/cli_test.sh @@ -186,6 +186,13 @@ trace lavad q rewards show-iprpc-data > /dev/null trace lavad q rewards iprpc-provider-reward > /dev/null trace lavad q rewards iprpc-spec-reward > /dev/null trace lavad q rewards provider-reward >/dev/null +trace lavad q rewards pending-ibc-iprpc-funds > /dev/null + +echo "Testing rewards tx commands" +trace lavad tx rewards fund-iprpc ETH1 4 100000ulava --from alice >/dev/null +wait_count_blocks 1 >/dev/null +trace lavad tx rewards submit-ibc-iprpc-tx ETH1 3 100ulava transfer channel-0 --from bob --home ~/.lava --node tcp://localhost:26657 >/dev/null + echo "Testing events command" trace lavad test events 30 10 --event lava_relay_payment --from alice --timeout 1s >/dev/null diff --git a/testutil/common/tester.go b/testutil/common/tester.go index f00b67e9cc..0098d3c51d 100644 --- a/testutil/common/tester.go +++ b/testutil/common/tester.go @@ -14,6 +14,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" testkeeper "github.com/lavanet/lava/testutil/keeper" "github.com/lavanet/lava/utils" "github.com/lavanet/lava/utils/lavaslices" @@ -25,6 +26,7 @@ import ( pairingtypes "github.com/lavanet/lava/x/pairing/types" planstypes "github.com/lavanet/lava/x/plans/types" projectstypes "github.com/lavanet/lava/x/projects/types" + "github.com/lavanet/lava/x/rewards" rewardstypes "github.com/lavanet/lava/x/rewards/types" spectypes "github.com/lavanet/lava/x/spec/types" subscriptiontypes "github.com/lavanet/lava/x/subscription/types" @@ -34,10 +36,11 @@ import ( type Tester struct { T *testing.T - GoCtx context.Context - Ctx sdk.Context - Servers *testkeeper.Servers - Keepers *testkeeper.Keepers + GoCtx context.Context + Ctx sdk.Context + Servers *testkeeper.Servers + Keepers *testkeeper.Keepers + IbcTransfer porttypes.Middleware accounts map[string]sigs.Account plans map[string]planstypes.Plan @@ -75,11 +78,12 @@ func NewTesterRaw(t *testing.T) *Tester { servers, keepers, GoCtx := testkeeper.InitAllKeepers(t) ts := &Tester{ - T: t, - GoCtx: GoCtx, - Ctx: sdk.UnwrapSDKContext(GoCtx), - Servers: servers, - Keepers: keepers, + T: t, + GoCtx: GoCtx, + Ctx: sdk.UnwrapSDKContext(GoCtx), + Servers: servers, + Keepers: keepers, + IbcTransfer: rewards.NewIBCMiddleware(keepers.IbcTransfer, keepers.Rewards), accounts: make(map[string]sigs.Account), plans: make(map[string]planstypes.Plan), @@ -699,6 +703,11 @@ func (ts *Tester) TxRewardsFundIprpc(creator string, spec string, duration uint6 return ts.Servers.RewardsServer.FundIprpc(ts.GoCtx, msg) } +func (ts *Tester) TxRewardsCoverIbcIprpcFundCost(creator string, index uint64) (*rewardstypes.MsgCoverIbcIprpcFundCostResponse, error) { + msg := rewardstypes.NewMsgCoverIbcIprpcFundCost(creator, index) + return ts.Servers.RewardsServer.CoverIbcIprpcFundCost(ts.GoCtx, msg) +} + // TxCreateValidator: implement 'tx staking createvalidator' and bond its tokens func (ts *Tester) TxCreateValidator(validator sigs.Account, amount math.Int) { consensusPowerTokens := ts.Keepers.StakingKeeper.TokensFromConsensusPower(ts.Ctx, 1) @@ -969,6 +978,7 @@ func (ts *Tester) QueryRewardsShowIprpcData() (*rewardstypes.QueryShowIprpcDataR return ts.Keepers.Rewards.ShowIprpcData(ts.GoCtx, msg) } +// QueryRewardsShowIprpcData implements 'q rewards iprpc-provider-reward-estimation' func (ts *Tester) QueryRewardsIprpcProviderRewardEstimation(provider string) (*rewardstypes.QueryIprpcProviderRewardEstimationResponse, error) { msg := &rewardstypes.QueryIprpcProviderRewardEstimationRequest{ Provider: provider, @@ -976,6 +986,7 @@ func (ts *Tester) QueryRewardsIprpcProviderRewardEstimation(provider string) (*r return ts.Keepers.Rewards.IprpcProviderRewardEstimation(ts.GoCtx, msg) } +// QueryRewardsShowIprpcData implements 'q rewards iprpc-spec-reward' func (ts *Tester) QueryRewardsIprpcSpecReward(spec string) (*rewardstypes.QueryIprpcSpecRewardResponse, error) { msg := &rewardstypes.QueryIprpcSpecRewardRequest{ Spec: spec, @@ -983,6 +994,14 @@ func (ts *Tester) QueryRewardsIprpcSpecReward(spec string) (*rewardstypes.QueryI return ts.Keepers.Rewards.IprpcSpecReward(ts.GoCtx, msg) } +// QueryRewardsShowIprpcData implements 'q rewards pending-ibc-iprpc-funds' +func (ts *Tester) QueryRewardsPendingIbcIprpcFunds(filter string) (*rewardstypes.QueryPendingIbcIprpcFundsResponse, error) { + msg := &rewardstypes.QueryPendingIbcIprpcFundsRequest{ + Filter: filter, + } + return ts.Keepers.Rewards.PendingIbcIprpcFunds(ts.GoCtx, msg) +} + // block/epoch helpers // QueryRewardsProviderReward implements 'q rewards provider-reward' func (ts *Tester) QueryRewardsProviderReward(chainID string, provider string) (*rewardstypes.QueryProviderRewardResponse, error) { diff --git a/testutil/keeper/keepers_init.go b/testutil/keeper/keepers_init.go index 9de45e6ef9..f93533bd4d 100644 --- a/testutil/keeper/keepers_init.go +++ b/testutil/keeper/keepers_init.go @@ -31,6 +31,7 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" commonconsts "github.com/lavanet/lava/testutil/common/consts" "github.com/lavanet/lava/utils/sigs" conflictkeeper "github.com/lavanet/lava/x/conflict/keeper" @@ -95,6 +96,7 @@ type Keepers struct { SlashingKeeper slashingkeeper.Keeper Rewards rewardskeeper.Keeper Distribution distributionkeeper.Keeper + IbcTransfer mockIbcTransferKeeper } type Servers struct { @@ -111,6 +113,7 @@ type Servers struct { SlashingServer slashingtypes.MsgServer RewardsServer rewardstypes.MsgServer DistributionServer distributiontypes.MsgServer + IbcTransferServer ibctransfertypes.MsgServer } type KeeperBeginBlockerWithRequest interface { @@ -142,6 +145,9 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { distributionStoreKey := sdk.NewKVStoreKey(distributiontypes.StoreKey) stateStore.MountStoreWithDB(distributionStoreKey, storetypes.StoreTypeIAVL, db) + ibctransferStoreKey := sdk.NewKVStoreKey(ibctransfertypes.StoreKey) + stateStore.MountStoreWithDB(ibctransferStoreKey, storetypes.StoreTypeIAVL, db) + stakingStoreKey := sdk.NewKVStoreKey(stakingtypes.StoreKey) stateStore.MountStoreWithDB(stakingStoreKey, storetypes.StoreTypeIAVL, db) @@ -217,6 +223,7 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { paramsKeeper.Subspace(rewardstypes.ModuleName) paramsKeeper.Subspace(distributiontypes.ModuleName) paramsKeeper.Subspace(dualstakingtypes.ModuleName) + paramsKeeper.Subspace(ibctransfertypes.ModuleName) // paramsKeeper.Subspace(conflicttypes.ModuleName) //TODO... epochparamsSubspace, _ := paramsKeeper.GetSubspace(epochstoragetypes.ModuleName) @@ -245,11 +252,13 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { ) downtimeParamsSubspace, _ := paramsKeeper.GetSubspace(downtimemoduletypes.ModuleName) + ibctransferparamsSubspace, _ := paramsKeeper.GetSubspace(ibctransfertypes.ModuleName) ks := Keepers{} ks.TimerStoreKeeper = timerstorekeeper.NewKeeper(cdc) ks.AccountKeeper = mockAccountKeeper{} ks.BankKeeper = mockBankKeeper{} + ks.IbcTransfer = NewMockIbcTransferKeeper(ibctransferStoreKey, cdc, ibctransferparamsSubspace, ks.AccountKeeper, ks.BankKeeper) init_balance() ks.StakingKeeper = *stakingkeeper.NewKeeper(cdc, stakingStoreKey, ks.AccountKeeper, ks.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String()) ks.Distribution = distributionkeeper.NewKeeper(cdc, distributionStoreKey, ks.AccountKeeper, ks.BankKeeper, ks.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) diff --git a/testutil/keeper/mock_keepers.go b/testutil/keeper/mock_keepers.go index 535c25c180..53c84f0630 100644 --- a/testutil/keeper/mock_keepers.go +++ b/testutil/keeper/mock_keepers.go @@ -4,9 +4,17 @@ import ( "fmt" "time" + "cosmossdk.io/math" tenderminttypes "github.com/cometbft/cometbft/types" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" ) // account keeper mock @@ -156,6 +164,101 @@ func (k mockBankKeeper) BlockedAddr(addr sdk.AccAddress) bool { return false } +// Mock IBC transfer keeper +type mockIbcTransferKeeper struct { + storeKey storetypes.StoreKey + cdc codec.BinaryCodec + paramSpace paramstypes.Subspace + authKeeper mockAccountKeeper + bankKeeper mockBankKeeper +} + +const ( + MockSrcPort = "src" + MockSrcChannel = "srcc" + MockDestPort = "dest" + MockDestChannel = "destc" +) + +func NewMockIbcTransferKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, paramSpace paramstypes.Subspace, authKeeper mockAccountKeeper, bankKeeper mockBankKeeper) mockIbcTransferKeeper { + return mockIbcTransferKeeper{ + storeKey: storeKey, + cdc: cdc, + paramSpace: paramSpace, + authKeeper: authKeeper, + bankKeeper: bankKeeper, + } +} + +func (k mockIbcTransferKeeper) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string) (string, error) { + return "", nil +} + +func (k mockIbcTransferKeeper) OnChanOpenTry(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string) (version string, err error) { + return "", nil +} + +func (k mockIbcTransferKeeper) OnChanOpenAck(ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string) error { + return nil +} + +func (k mockIbcTransferKeeper) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + return nil +} + +func (k mockIbcTransferKeeper) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + return nil +} + +func (k mockIbcTransferKeeper) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + return nil +} + +func (k mockIbcTransferKeeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + // get data from packet + var data ibctransfertypes.FungibleTokenPacketData + if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + // parse data + senderAcc, err := sdk.AccAddressFromBech32(data.Sender) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + receiverAcc, err := sdk.AccAddressFromBech32(data.Receiver) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + amount, ok := math.NewIntFromString(data.Amount) + if !ok { + return channeltypes.NewErrorAcknowledgement(fmt.Errorf("invalid amount in transfer data: %s", data.Amount)) + } + + // sub balance from sender in original denom, add balance to receiver (on other chain) with IBC Denom + coins := sdk.NewCoins(sdk.NewCoin(data.Denom, amount)) + err = k.bankKeeper.SubFromBalance(senderAcc, coins) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + ibcCoins := sdk.NewCoins(ibctransfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, data.Denom, amount)) + err = k.bankKeeper.AddToBalance(receiverAcc, ibcCoins) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + return channeltypes.NewResultAcknowledgement([]byte{byte(1)}) +} + +func (k mockIbcTransferKeeper) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + return nil +} + +func (k mockIbcTransferKeeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { + return nil +} + +// Mock BlockStore type MockBlockStore struct { height int64 blockHistory map[int64]*tenderminttypes.Block diff --git a/utils/maps/maps.go b/utils/maps/maps.go index 35ed70e593..5a0d180bb9 100644 --- a/utils/maps/maps.go +++ b/utils/maps/maps.go @@ -1,5 +1,7 @@ package maps +import "encoding/binary" + func FindLargestIntValueInMap[K comparable](myMap map[K]int) (K, int) { var maxVal int var maxKey K @@ -15,3 +17,15 @@ func FindLargestIntValueInMap[K comparable](myMap map[K]int) (K, int) { return maxKey, maxVal } + +// GetIDBytes returns the byte representation of the uint64 ID +func GetIDBytes(id uint64) []byte { + bz := make([]byte, 8) + binary.BigEndian.PutUint64(bz, id) + return bz +} + +// GetIDFromBytes returns ID in uint64 format from a byte array +func GetIDFromBytes(bz []byte) uint64 { + return binary.BigEndian.Uint64(bz) +} diff --git a/x/rewards/README.md b/x/rewards/README.md index c47ef9375e..746f2a9af6 100644 --- a/x/rewards/README.md +++ b/x/rewards/README.md @@ -18,11 +18,15 @@ Please note that this module replaces Cosmos SDK's mint module, which is typical * [Validators Rewards Pool](#validators-rewards-pool) * [Providers Rewards Pool](#providers-rewards-pool) * [IPRPC](#iprpc) + * [IPRPC over IBC](#iprpc-over-ibc) * [Parameters](#parameters) * [MinBondedTarget](#minbondedtarget) * [MaxBondedTarget](#maxbondedtarget) * [LowFactor](#lowfactor) * [LeftOverBurnRate](#leftoverburnrate) + * [MaxRewardsBoost](#maxrewardsboost) + * [ValidatorsSubscriptionParticipation](#validatorssubscriptionparticipation) + * [IbcIprpcExpiration](#ibciprpcexpiration) * [Queries](#queries) * [Transactions](#transactions) * [Proposals](#proposals) @@ -124,6 +128,26 @@ Users have the freedom to fund the pool with any token of their choice, for any In order to fund the IPRPC pool, the user's funds must cover the monthly minimum IPRPC cost, a parameter determined by governance. This minimum IPRPC cost will be subtracted from the monthly emission. +#### IPRPC over IBC + +To ease the funding process of funding the IPRPC pool, users can send an `ibc-transfer` transaction to fund the IPRPC pool directly from the source chain. For example, using IPRPC over IBC allows funding the IPRPC pool by sending a single `ibc-transfer` to an Osmosis node. + +To make the `ibc-transfer` transaction fund the IPRPC pool, it must contain a custom memo with the following format: + +```json +{ + "iprpc": { + "creator": "alice", + "spec": "ETH1", + "duration": "3" + } +} +``` + +The creator field can be any name, the spec field must be an on-chain spec ID, and the duration should be the funding period (in months). Users can use the `generate-ibc-iprpc-tx` helper command with the `--memo-only` flag to generate a valid IPRPC memo. If the flag is not included, this command creates a valid `ibc-transfer` transaction JSON with a custom memo. The only remaining step is to sign and send the transaction. + +As mentioned above, funding the IPRPC pool requires a fee derived from the monthly minimum IPRPC cost. Because of that, all IPRPC over IBC requests are considered a pending IPRPC fund request until the fee is paid. To view the current pending requests, users can use the `pending-ibc-iprpc-funds` query and cover the minimum cost using the `cover-ibc-iprpc-fund-cost` transaction. The expiration of pending IPRPC fund requests is dictated by the `IbcIprpcExpiration`, which equals the expiration period in months. + ## Parameters The rewards module contains the following parameters: @@ -136,6 +160,7 @@ The rewards module contains the following parameters: | LeftOverBurnRate | math.LegacyDec | 1 | | MaxRewardsBoost | uint64 | 5 | | ValidatorsSubscriptionParticipation | math.LegacyDec | 0.05 | +| IbcIprpcExpiration | uint64 | 3 | ### MinBondedTarget @@ -176,6 +201,10 @@ MaxRewardsBoost is a multiplier that determines the maximum bonus for provider r ValidatorsSubscriptionParticipation is used to calculate the providers rewards participation fees. +### IbcIprpcExpiration + +IbcIprpcExpiration determines the pending IPRPC over IBC fund requests expiration in months. + ## Queries The rewards module supports the following queries: @@ -188,6 +217,8 @@ The rewards module supports the following queries: | `show-iprpc-data` | none | shows the IPRPC data that includes the minimum IPRPC cost and a list of IPRPC eligible subscriptions | | `iprpc-provider-reward` | provider (string) | shows the estimated IPRPC rewards for a specific provider (relative to its serviced CU) for the upcoming monthly emission | | `iprpc-spec-rewards` | spec (string, optional) | shows a specific spec's IPRPC rewards (for the entire period). If no spec is given, all IPRPC rewards are shown | +| `generate-ibc-iprpc-tx` | spec (string), duration (uint64, in months), amount (sdk.Coin, optional), src-port (string, optional), src-channel (string, optional), --from (string, mandatory), --node (string, mandatory), --memo-only (optional) | generates an `ibc-transfer` transaction JSON that can be used to fund the IPRPC pool over IBC. To generate only the custom memo that triggers the IPRPC funding, use the `--memo-only` flag. | +| `pending-ibc-iprpc-funds` | filter (string, optional) | Lists the pending IPRPC over IBC fund requests. Use the optional filter to filter by index, creator, or spec. Each entry is shown with the minimum IPRPC cost that needs to be paid to apply it. | Note, use the provider's operator address for the `iprpc-provider-reward` query. For more information on the operator and vault addresses see the pairing module's [README.md](../pairing/README.md). @@ -200,6 +231,7 @@ The rewards module supports the following transactions: | Transaction | Arguments | What it does | | ---------- | --------------- | ----------------------------------------------| | `fund-iprpc` | spec (string), duration (uint64, in months), coins (sdk.Coins, for example: `100ulava,50ibctoken`) | fund the IPRPC pool to a specific spec with ulava or IBC wrapped tokens. The tokens will be vested for `duration` months. | +| `cover-ibc-iprpc-fund-cost` | index (uint64) | cover the costs of a pending IPRPC over IBC fund request by index. The required minimum IPRPC cost is automatically paid by the transaction sender. | Please note that the coins specified in the `fund-iprpc` transaction is the monthly emission fund. For instance, if you specify a fund of 100ulava for a duration of three months, providers will receive 100ulava each month, not 33ulava. @@ -229,4 +261,7 @@ The rewards module has the following events: | `iprpc_pool_emmission` | a successful distribution of IPRPC bonus rewards | | `set_iprpc_data` | a successful setting of IPRPC data | | `fund_iprpc` | a successful funding of the IPRPC pool | -| `transfer_iprpc_reward_to_next_month` | a successful transfer of the current month's IPRPC reward to the next month. Happens when there are no providers eligible for IPRPC rewards in the current month | \ No newline at end of file +| `transfer_iprpc_reward_to_next_month` | a successful transfer of the current month's IPRPC reward to the next month. Happens when there are no providers eligible for IPRPC rewards in the current month | +| `pending_ibc_iprpc_fund_created` | a successful IPRPC over IBC fund request which creates a pending fund request entry on-chain | +| `cover_ibc_iprpc_fund_cost` | a successful cost coverage of a pending IPRPC fund request that applies the request and funds the IPRPC pool | +| `expired_pending_ibc_iprpc_fund_removed` | a successful removal of an expired pending IPRPC fund request | \ No newline at end of file diff --git a/x/rewards/client/cli/query.go b/x/rewards/client/cli/query.go index bb32aa4781..49379a4e1e 100644 --- a/x/rewards/client/cli/query.go +++ b/x/rewards/client/cli/query.go @@ -31,6 +31,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { cmd.AddCommand(CmdQueryIprpcProviderRewardEstimation()) cmd.AddCommand(CmdQueryIprpcSpecReward()) cmd.AddCommand(CmdQueryProviderReward()) + cmd.AddCommand(CmdQueryPendingIbcIprpcFunds()) // this line is used by starport scaffolding # 1 return cmd diff --git a/x/rewards/client/cli/query_pending_ibc_iprpc_funds.go b/x/rewards/client/cli/query_pending_ibc_iprpc_funds.go new file mode 100644 index 0000000000..fac9f25d2b --- /dev/null +++ b/x/rewards/client/cli/query_pending_ibc_iprpc_funds.go @@ -0,0 +1,51 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/lavanet/lava/x/rewards/types" + "github.com/spf13/cobra" +) + +var _ = strconv.Itoa(0) + +func CmdQueryPendingIbcIprpcFunds() *cobra.Command { + cmd := &cobra.Command{ + Use: "pending-ibc-iprpc-funds [filter: index/creator/spec]", + Short: "Query for pending IBC IPRPC funds", + Long: `Query for pending IBC IPRPC funds. Use the optional filter argument to get a specific fund (using its index), + funds of a specific creator or funds for a specific spec. Each fund has its own cost which is derived from the minimum IPRPC + cost for funding the IPRPC pool, multiplied by the fund's duration. To cover the cost and apply the pending fund, use + the "lavad tx rewards cover-ibc-iprpc-cost" TX`, + Args: cobra.RangeArgs(0, 1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + filter := "" + if len(args) > 0 { + filter = args[0] + } + + params := &types.QueryPendingIbcIprpcFundsRequest{ + Filter: filter, + } + + res, err := queryClient.PendingIbcIprpcFunds(cmd.Context(), params) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/rewards/client/cli/tx.go b/x/rewards/client/cli/tx.go index dc1d1f6e10..fb52ce5c94 100644 --- a/x/rewards/client/cli/tx.go +++ b/x/rewards/client/cli/tx.go @@ -42,6 +42,8 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand(CmdFundIprpc()) + cmd.AddCommand(CmdCoverIbcIprpcFundCost()) + cmd.AddCommand(CmdTxSubmitIbcIprpcProposal()) // this line is used by starport scaffolding # 1 return cmd diff --git a/x/rewards/client/cli/tx_cover_ibc_iprpc_fund_cost.go b/x/rewards/client/cli/tx_cover_ibc_iprpc_fund_cost.go new file mode 100644 index 0000000000..d94f9bfad1 --- /dev/null +++ b/x/rewards/client/cli/tx_cover_ibc_iprpc_fund_cost.go @@ -0,0 +1,49 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/lavanet/lava/x/rewards/types" + "github.com/spf13/cobra" +) + +var _ = strconv.Itoa(0) + +func CmdCoverIbcIprpcFundCost() *cobra.Command { + cmd := &cobra.Command{ + Use: "cover-ibc-iprpc-fund-cost [index] --from ", + Short: "Apply a pending IBC IPRPC fund by paying its mandatory minimum cost of funding the IPRPC pool", + Long: `Apply a pending IBC IPRPC fund by paying its mandatory minimum cost of funding the IPRPC pool. Find your desired + fund's index and cost by using the query pending-ibc-iprpc-funds. By sending this message, the full cost of the fund will be + paid automatically. Then, the pending fund's coins will be sent to the IPRPC pool.`, + Example: `lavad tx rewards cover-ibc-iprpc-fund-cost 4 --from alice`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + index, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + msg := types.NewMsgCoverIbcIprpcFundCost( + clientCtx.GetFromAddress().String(), + index, + ) + if err := msg.ValidateBasic(); err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + cmd.MarkFlagRequired(flags.FlagFrom) + return cmd +} diff --git a/x/rewards/client/cli/tx_submit_ibc_iprpc_proposal.go b/x/rewards/client/cli/tx_submit_ibc_iprpc_proposal.go new file mode 100644 index 0000000000..4712c4b8d5 --- /dev/null +++ b/x/rewards/client/cli/tx_submit_ibc_iprpc_proposal.go @@ -0,0 +1,260 @@ +package cli + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authztypes "github.com/cosmos/cosmos-sdk/x/authz" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + "github.com/lavanet/lava/x/rewards/types" + "github.com/spf13/cobra" +) + +var _ = strconv.Itoa(0) + +const ( + FlagTitle = "title" + FlagSummary = "summary" + FlagExpedited = "expedited" +) + +func CmdTxSubmitIbcIprpcProposal() *cobra.Command { + cmd := &cobra.Command{ + Use: "submit-ibc-iprpc-proposal [spec] [duration] [amount] [src-port] [src-channel] --from --node [flags]", + Short: "TX for submitting an ibc-transfer proposal for IPRPC funding over IBC", + Long: `TX for submitting an ibc-transfer proposal for IPRPC funding over IBC. + The generated proposal holds several transactions that when executed, an IPRPC over IBC transaction is triggered. + The executed transactions (by order): + 1. Grant IBC transfer authz from the creator (alice) to the gov module. + 2. Grant Revoke authz from the creator (alice) to the gov module. + 3. Submit a proposal that holds the following transactions (by order): + 3.1. Community pool spend transaction to the creator. + 3.2. Authz Exec transaction with the following transactions (by order): + a. IBC transfer transaction with a custom memo (to trigger IPRPC over IBC). + b. Authz revoke transaction to revoke the grant from step 1. + b. Authz revoke transaction to revoke the grant from step 2. + + Arguments: + - spec: the spec ID which the user wants to fund. Note, this argument is not validated. A valid spec ID is one + that is registered on-chain in the Lava blockchain. To check whether a spec ID is valid, use the + "lavad q spec show-chain-info" query. + - duration: the duration in months that the user wants the IPRPC to last. + - amount: the total amount of coins to fund the IPRPC pool. Note that each month, the IPRPC pool will be + funded with amount/duration coins. + - src-port: source IBC port (usually "transfer"). + - src-channel: source IBC channel. + + Important notes: + 1. The "--node" flag is mandatory. It should hold the node URI from which the ibc-transfer transaction + originates (probably not a Lava node). + 2. After the proposal passes, a new pending IPRPC fund request is created on the Lava blockchain. + Use the rewards module's pending-ibc-iprpc-funds query to see the pending request. To apply the fund, + use the rewards module's cover-ibc-iprpc-fund-cost transaction. + 3. Use the relayer's query "channels" to get the channel ID of the IBC transfer channel. The IBC transfer + channel's port ID is always "transfer"`, + Args: cobra.ExactArgs(5), + Example: ` + Submit the proposal: + lavad tx rewards submit-ibc-iprpc-proposal transfer --from alice --node https://osmosis-rpc.publicnode.com:443 + + Get the new proposal ID (might need to paginate): + lavad q gov proposals --node https://osmosis-rpc.publicnode.com:443 + + Send a deposit and vote: + lavad tx gov deposit 10000000ulava --from alice --node https://osmosis-rpc.publicnode.com:443 + lavad tx gov vote yes --from alice --node https://osmosis-rpc.publicnode.com:443 + `, + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + // get optional flags + title, err := cmd.Flags().GetString(FlagTitle) + if err != nil { + return err + } + summary, err := cmd.Flags().GetString(FlagSummary) + if err != nil { + return err + } + expedited, err := cmd.Flags().GetBool(FlagExpedited) + if err != nil { + return err + } + + // get args + creator := clientCtx.FromAddress.String() + gov, err := getGovModuleAddress(clientCtx) + if err != nil { + return err + } + + amountStr := args[2] + srcPort := args[3] + srcChannel := args[4] + coin, err := sdk.ParseCoinNormalized(amountStr) + if err != nil { + return err + } + amount := sdk.NewCoins(coin) + + // create transferGrantMsg TX + transferGrantMsg := &authztypes.MsgGrant{ + Granter: creator, + Grantee: gov, + Grant: authztypes.Grant{}, + } + transferAuth := authztypes.NewGenericAuthorization(sdk.MsgTypeURL(&ibctransfertypes.MsgTransfer{})) + err = transferGrantMsg.SetAuthorization(transferAuth) + if err != nil { + return err + } + + // create transferGrant TX + revokeGrantMsg := &authztypes.MsgGrant{ + Granter: creator, + Grantee: gov, + Grant: authztypes.Grant{}, + } + revokeAuth := authztypes.NewGenericAuthorization(sdk.MsgTypeURL(&authztypes.MsgRevoke{})) + err = revokeGrantMsg.SetAuthorization(revokeAuth) + if err != nil { + return err + } + + // create unsigned TXs + memoSpec := args[0] + memoDuration := args[1] + memo, err := createIbcIprpcMemo(creator, memoSpec, memoDuration) + if err != nil { + return err + } + timeoutHeight, err := getTimeoutHeight(clientCtx, srcPort, srcChannel) + if err != nil { + return err + } + transferMsg := ibctransfertypes.NewMsgTransfer(srcPort, srcChannel, amount[0], creator, types.IbcIprpcReceiverAddress().String(), timeoutHeight, 0, memo) + transferRevokeMsg := authztypes.MsgRevoke{ + Granter: creator, + Grantee: gov, + MsgTypeUrl: sdk.MsgTypeURL(&ibctransfertypes.MsgTransfer{}), + } + revokeRevokeMsg := authztypes.MsgRevoke{ + Granter: creator, + Grantee: gov, + MsgTypeUrl: sdk.MsgTypeURL(&authztypes.MsgRevoke{}), + } + + // create authz Exec msg + msgsAny, err := sdktx.SetMsgs([]sdk.Msg{transferMsg, &transferRevokeMsg, &revokeRevokeMsg}) + if err != nil { + return err + } + execMsg := authztypes.MsgExec{ + Grantee: gov, + Msgs: msgsAny, + } + + // create community spend msg + communitySpendMsg := distributiontypes.MsgCommunityPoolSpend{ + Authority: gov, + Recipient: creator, + Amount: amount, + } + + // create submit proposal msg + submitProposalMsg, err := govv1types.NewMsgSubmitProposal([]sdk.Msg{&communitySpendMsg, &execMsg}, sdk.NewCoins(), creator, "", title, summary, expedited) + if err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), revokeGrantMsg, transferGrantMsg, submitProposalMsg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + cmd.Flags().String(FlagTitle, "IPRPC over IBC proposal", "Proposal title") + cmd.Flags().String(FlagSummary, "IPRPC over IBC proposal", "Proposal summary") + cmd.Flags().Bool(FlagExpedited, false, "Expedited proposal") + cmd.MarkFlagRequired(flags.FlagFrom) + cmd.MarkFlagRequired(flags.FlagNode) + return cmd +} + +func getGovModuleAddress(clientCtx client.Context) (string, error) { + authQuerier := authtypes.NewQueryClient(clientCtx) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + res, err := authQuerier.ModuleAccountByName(timeoutCtx, &authtypes.QueryModuleAccountByNameRequest{Name: govtypes.ModuleName}) + if err != nil { + return "", err + } + + var govAcc authtypes.ModuleAccount + err = clientCtx.Codec.Unmarshal(res.Account.Value, &govAcc) + if err != nil { + return "", err + } + + return govAcc.Address, nil +} + +// createIbcIprpcMemo creates the custom memo required for an ibc-transfer TX to trigger an IPRPC over IBC fund request +func createIbcIprpcMemo(creator string, spec string, durationStr string) (string, error) { + duration, err := strconv.ParseUint(durationStr, 10, 64) + if err != nil { + return "", err + } + + return types.CreateIprpcMemo(creator, spec, duration) +} + +// getTimeoutHeight a default timeout height for the node from which the ibc-transfer is sent from +func getTimeoutHeight(clientQueryCtx client.Context, portId string, channelId string) (clienttypes.Height, error) { + ibcQuerier := ibcchanneltypes.NewQueryClient(clientQueryCtx) + timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + clientRes, err := ibcQuerier.ChannelClientState(timeoutCtx, &ibcchanneltypes.QueryChannelClientStateRequest{ + PortId: portId, + ChannelId: channelId, + }) + if err != nil { + return clienttypes.ZeroHeight(), err + } + + var clientState exported.ClientState + if err := clientQueryCtx.InterfaceRegistry.UnpackAny(clientRes.IdentifiedClientState.ClientState, &clientState); err != nil { + return clienttypes.ZeroHeight(), err + } + + clientHeight, ok := clientState.GetLatestHeight().(clienttypes.Height) + if !ok { + return clienttypes.ZeroHeight(), fmt.Errorf("invalid height type. expected type: %T, got: %T", clienttypes.Height{}, clientState.GetLatestHeight()) + } + + defaultTimeoutHeightStr := ibctransfertypes.DefaultRelativePacketTimeoutHeight + defaultTimeoutHeight, err := clienttypes.ParseHeight(defaultTimeoutHeightStr) + if err != nil { + return clienttypes.ZeroHeight(), err + } + + clientHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + clientHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + + return clientHeight, nil +} diff --git a/x/rewards/genesis.go b/x/rewards/genesis.go index bfe82663a5..da8ee0e26a 100644 --- a/x/rewards/genesis.go +++ b/x/rewards/genesis.go @@ -24,6 +24,9 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } k.SetIprpcRewardsCurrentId(ctx, genState.IprpcRewardsCurrent) k.SetIprpcData(ctx, genState.MinIprpcCost, genState.IprpcSubscriptions) + for _, pendingIprpcFund := range genState.PendingIbcIprpcFunds { + k.SetPendingIbcIprpcFund(ctx, pendingIprpcFund) + } } // ExportGenesis returns the capability module's exported genesis. @@ -36,6 +39,7 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { genesis.MinIprpcCost = k.GetMinIprpcCost(ctx) genesis.IprpcRewards = k.GetAllIprpcReward(ctx) genesis.IprpcRewardsCurrent = k.GetIprpcRewardsCurrentId(ctx) + genesis.PendingIbcIprpcFunds = k.GetAllPendingIbcIprpcFund(ctx) // this line is used by starport scaffolding # genesis/module/export return genesis diff --git a/x/rewards/genesis_test.go b/x/rewards/genesis_test.go index cd7e508195..83b2507d99 100644 --- a/x/rewards/genesis_test.go +++ b/x/rewards/genesis_test.go @@ -3,6 +3,7 @@ package rewards import ( "testing" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" testkeeper "github.com/lavanet/lava/testutil/keeper" "github.com/lavanet/lava/testutil/nullify" @@ -25,6 +26,24 @@ func TestGenesis(t *testing.T) { }, }, IprpcRewardsCurrent: 2, + PendingIbcIprpcFunds: []types.PendingIbcIprpcFund{ + { + Index: 1, + Creator: "1", + Spec: "s1", + Duration: 1, + Fund: sdk.NewCoin("denom", math.OneInt()), + Expiry: 1, + }, + { + Index: 2, + Creator: "2", + Spec: "s2", + Duration: 2, + Fund: sdk.NewCoin("denom", math.OneInt().AddRaw(1)), + Expiry: 2, + }, + }, // this line is used by starport scaffolding # genesis/test/state } @@ -40,5 +59,6 @@ func TestGenesis(t *testing.T) { require.ElementsMatch(t, genesisState.IprpcRewards, got.IprpcRewards) require.Equal(t, genesisState.IprpcRewardsCurrent, got.IprpcRewardsCurrent) + require.ElementsMatch(t, genesisState.PendingIbcIprpcFunds, got.PendingIbcIprpcFunds) // this line is used by starport scaffolding # genesis/test/assert } diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index af4dbd2b68..856f713066 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -3,6 +3,7 @@ package rewards import ( "errors" "fmt" + "strconv" sdk "github.com/cosmos/cosmos-sdk/types" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" @@ -109,6 +110,23 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return channeltypes.NewErrorAcknowledgement(err) } + // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress + amount, ok := sdk.NewIntFromString(data.Amount) + if !ok { + detailedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("invalid amount in ibc-transfer data"), + utils.LogAttr("data", data), + ) + return channeltypes.NewErrorAcknowledgement(detailedErr) + } + ibcTokens := transfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, data.Denom, amount) + + // set pending IPRPC over IBC requests on-chain + // leftovers are the tokens that were not registered in the PendingIbcIprpcFund objects due to int division + piif, leftovers, err := im.keeper.NewPendingIbcIprpcFund(ctx, memo.Creator, memo.Spec, memo.Duration, ibcTokens) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + // send the IBC transfer to get the tokens ack := im.sendIbcTransfer(ctx, packet, relayer, data.Sender, types.IbcIprpcReceiverAddress().String()) if ack == nil || !ack.Success() { @@ -117,31 +135,38 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet // on the IBC transfer module's side (which returns a non-nil ack when executed without errors). Asynchronous // processing can be queued processing of packets, interacting with external APIs and more. These can cause // delays in the IBC-transfer's processing which will make the module return a nil ack until the processing is done. - // Asynchronous acks are handled in the WriteAcknowledgement middleware method. + im.keeper.RemovePendingIbcIprpcFund(ctx, piif.Index) return ack } - // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress - amount, ok := sdk.NewIntFromString(data.Amount) - if !ok { - detailedErr := utils.LavaFormatError("rewards module IBC middleware processing failed", fmt.Errorf("invalid amount in ibc-transfer data"), - utils.LogAttr("data", data), + // send the IBC tokens from IbcIprpcReceiver to PendingIprpcPool + err = im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens) + if err != nil { + detailedErr := utils.LavaFormatError("sending token from IbcIprpcReceiver to the PendingIprpcPool failed", err, + utils.LogAttr("sender", data.Sender), ) return channeltypes.NewErrorAcknowledgement(detailedErr) } - ibcTokens := transfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, data.Denom, amount) - // send the IBC tokens from IbcIprpcReceiver to PendingIprpcPool - err = im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens) + // transfer the leftover IBC tokens to the community pool + err = im.keeper.FundCommunityPoolFromIbcIprpcReceiver(ctx, leftovers) if err != nil { - detailedErr := utils.LavaFormatError("sending token from IbcIprpcReceiver to the PendingIprpcPool failed", err, + detailedErr := utils.LavaFormatError("could not send leftover tokens from IbcIprpcReceiverAddress to community pool, tokens are locked in IbcIprpcReceiverAddress", err, + utils.LogAttr("amount", leftovers.String()), utils.LogAttr("sender", data.Sender), ) return channeltypes.NewErrorAcknowledgement(detailedErr) } - // TODO: define a PendingIbcIprpcFund store to keep track on pending requests. Also create an event that - // a new pending request has been created + // make event + utils.LogLavaEvent(ctx, im.keeper.Logger(ctx), types.NewPendingIbcIprpcFundEventName, map[string]string{ + "index": strconv.FormatUint(piif.Index, 10), + "creator": piif.Creator, + "spec": piif.Spec, + "duration": strconv.FormatUint(piif.Duration, 10), + "monthly_fund": piif.Fund.String(), + "expiry": strconv.FormatUint(piif.Expiry, 10), + }, "New pending IBC IPRPC fund was created successfully") return channeltypes.NewResultAcknowledgement([]byte{byte(1)}) } diff --git a/x/rewards/keeper/grpc_query_pending_ibc_iprpc_funds.go b/x/rewards/keeper/grpc_query_pending_ibc_iprpc_funds.go new file mode 100644 index 0000000000..b8be2d28d2 --- /dev/null +++ b/x/rewards/keeper/grpc_query_pending_ibc_iprpc_funds.go @@ -0,0 +1,63 @@ +package keeper + +import ( + "context" + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/x/rewards/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (k Keeper) PendingIbcIprpcFunds(goCtx context.Context, req *types.QueryPendingIbcIprpcFundsRequest) (*types.QueryPendingIbcIprpcFundsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + piifs := []types.PendingIbcIprpcFund{} + infos := []types.PendingIbcIprpcFundInfo{} + ctx := sdk.UnwrapSDKContext(goCtx) + + // get the relevant PendingIbcIprpcFund objects by filter + if index, err := strconv.ParseUint(req.Filter, 10, 64); err == nil { + // index filter + piif, found := k.GetPendingIbcIprpcFund(ctx, index) + if !found { + return nil, fmt.Errorf("PendingIbcIprpcFund not found with index %d", index) + } + piifs = append(piifs, piif) + } else if _, found := k.specKeeper.GetSpec(ctx, req.Filter); found { + // spec filter + allPiifs := k.GetAllPendingIbcIprpcFund(ctx) + for _, piif := range allPiifs { + if piif.Spec == req.Filter { + piifs = append(piifs, piif) + } + } + } else if req.Filter != "" { + // creator filter + allPiifs := k.GetAllPendingIbcIprpcFund(ctx) + for _, piif := range allPiifs { + if piif.Creator == req.Filter { + piifs = append(piifs, piif) + } + } + } else { + // no filter + piifs = k.GetAllPendingIbcIprpcFund(ctx) + } + + // construct PendingIbcIprpcFundInfo list (calculate cost to apply) + if len(piifs) == 0 { + return nil, fmt.Errorf("PendingIbcIprpcFund not found with filter %s", req.Filter) + } + + for _, piif := range piifs { + cost := k.CalcPendingIbcIprpcFundMinCost(ctx, piif) + infos = append(infos, types.PendingIbcIprpcFundInfo{PendingIbcIprpcFund: piif, Cost: cost}) + } + + return &types.QueryPendingIbcIprpcFundsResponse{PendingIbcIprpcFundsInfo: infos}, nil +} diff --git a/x/rewards/keeper/helpers_test.go b/x/rewards/keeper/helpers_test.go index 3bfbbc0f75..b11bb47331 100644 --- a/x/rewards/keeper/helpers_test.go +++ b/x/rewards/keeper/helpers_test.go @@ -10,6 +10,8 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" "github.com/lavanet/lava/testutil/common" testkeeper "github.com/lavanet/lava/testutil/keeper" "github.com/lavanet/lava/testutil/sample" @@ -36,6 +38,7 @@ var ( sdk.NewCoin(commontypes.TokenDenom, sdk.NewInt(1100)), sdk.NewCoin(ibcDenom, sdk.NewInt(500)), ) + mockSpec string = "mockspec" mockSpec2 string = "mock2" ) @@ -227,3 +230,43 @@ func (ts *tester) makeBondedRatioNonZero() { bondedRatio = ts.Keepers.StakingKeeper.BondedRatio(ts.Ctx) require.True(ts.T, bondedRatio.Equal(sdk.NewDecWithPrec(25, 2))) // according to "valInitBalance", bondedRatio should be 0.25 } + +func (ts *tester) SendIprpcOverIbcTransferPacket(sender sdk.AccAddress, amount sdk.Coin, duration uint64) { + // get the sender's and PendingIprpcPool before sending the packet + senderBalanceBefore := ts.Keepers.BankKeeper.GetBalance(ts.Ctx, sender, amount.Denom) + pendingIprpcPoolBalanceBefore := ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, rewardstypes.PendingIprpcPoolName) + + // create packet data + memo, err := rewardstypes.CreateIprpcMemo(sender.String(), mockSpec, duration) + require.NoError(ts.T, err) + data := transfertypes.NewFungibleTokenPacketData(amount.Denom, amount.Amount.String(), sender.String(), "dummy", memo) + marshelledData, err := transfertypes.ModuleCdc.MarshalJSON(&data) + require.NoError(ts.T, err) + + // create packet + packet := channeltypes.NewPacket(marshelledData, 0, testkeeper.MockSrcPort, testkeeper.MockSrcChannel, testkeeper.MockDestPort, testkeeper.MockDestChannel, clienttypes.ZeroHeight(), 1) + + // call OnRecvPacket + ack := ts.IbcTransfer.OnRecvPacket(ts.Ctx, packet, sample.AccAddressObject()) + if ack == nil || !ack.Success() { + require.FailNow(ts.T, "ibc transfer failed") + } + + // verify the sender's balance went down and the PendingIprpcPool balance went up + senderBalanceAfter := ts.Keepers.BankKeeper.GetBalance(ts.Ctx, sender, amount.Denom) + pendingIprpcPoolBalanceAfter := ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, rewardstypes.PendingIprpcPoolName) + + senderDiff := sdk.NewCoins(senderBalanceBefore.Sub(senderBalanceAfter)) + require.True(ts.T, senderDiff.IsEqual(sdk.NewCoins(amount))) + + // pending pool gets the funds after going through the IBC channel -> check balance with ibc denom (subtracting leftovers) + pendingIprpcPoolDiff := pendingIprpcPoolBalanceAfter.Sub(pendingIprpcPoolBalanceBefore...) + leftoversAmount := amount.Amount.ModRaw(int64(duration)) + ibcTokens := transfertypes.GetTransferCoin(packet.DestinationPort, packet.DestinationChannel, amount.Denom, amount.Amount) + expectedIbcSenderDiff := sdk.NewCoins(sdk.NewCoin(ibcTokens.Denom, ibcTokens.Amount.Sub(leftoversAmount))) + require.True(ts.T, expectedIbcSenderDiff.IsEqual(pendingIprpcPoolDiff)) +} + +func GetIbcCoins(amount sdk.Coin) sdk.Coin { + return transfertypes.GetTransferCoin(testkeeper.MockDestPort, testkeeper.MockDestChannel, amount.Denom, amount.Amount) +} diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index bebccd0811..32276b1d38 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -3,11 +3,15 @@ package keeper import ( "errors" "fmt" + "strconv" + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "github.com/lavanet/lava/utils" "github.com/lavanet/lava/utils/decoder" + "github.com/lavanet/lava/utils/maps" "github.com/lavanet/lava/x/rewards/types" ) @@ -27,7 +31,7 @@ An example of the expected IPRPC over IBC memo field: } The tokens will be transferred to the pool once the minimum IPRPC funding fee is paid. In the meantime, the IPRPC over IBC -funds are saved in the IbcIprpcFund scaffolded map. +funds are saved in the PendingIprpcFund scaffolded map. */ @@ -75,6 +79,250 @@ func printInvalidMemoWarning(memo types.IprpcMemo, description string) error { return types.ErrIprpcMemoInvalid } +// The PendingIbcIprpcFund object holds all the necessary information for a pending IPRPC over IBC fund request that its min +// IPRPC cost was not covered. The min cost can be covered using the cover-ibc-iprpc-costs TX. Then, the funds will be transferred +// to the IPRPC pool from the next month for the pending fund's duration field. The corresponding PendingIbcIprpcFund will be deleted. +// Also, every PendingIbcIprpcFund has an expiration on which the object is deleted. There will be no refund of the funds +// upon expiration. The expiration period is determined by the reward module's parameter IbcIprpcExpiration. + +// NewPendingIbcIprpcFund sets a new PendingIbcIprpcFund object. It validates the input and sets the object with the right index and expiry +func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec string, duration uint64, fund sdk.Coin) (newPendingIbcIprpcFund types.PendingIbcIprpcFund, leftovers sdk.Coin, err error) { + zeroCoin := sdk.NewCoin(fund.Denom, math.ZeroInt()) + // validate spec and funds + _, found := k.specKeeper.GetSpec(ctx, spec) + if !found { + return types.PendingIbcIprpcFund{}, zeroCoin, utils.LavaFormatError("spec not found", fmt.Errorf("cannot create PendingIbcIprpcFund"), + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + ) + } + if fund.IsNil() || !fund.IsValid() { + return types.PendingIbcIprpcFund{}, zeroCoin, utils.LavaFormatError("invalid funds", fmt.Errorf("cannot create PendingIbcIprpcFund"), + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + ) + } + + // divide funds by duration since we use addSpecFunds() when applying the PendingIbcIprpcFund + // which assumes that each month will get the input fund + monthlyFund := sdk.NewCoin(fund.Denom, fund.Amount.QuoRaw(int64(duration))) + if monthlyFund.IsZero() { + return types.PendingIbcIprpcFund{}, zeroCoin, utils.LavaFormatWarning("fund amount cannot be less than duration", fmt.Errorf("cannot create PendingIbcIprpcFund"), + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + ) + } + + // leftovers will be transferred to the community pool in the calling function + leftovers = sdk.NewCoin(fund.Denom, fund.Amount.Sub(monthlyFund.Amount.MulRaw(int64(duration)))) + + // get index for the new object + latestPendingIbcIprpcFund := k.GetLatestPendingIbcIprpcFund(ctx) + newIndex := uint64(0) + if !latestPendingIbcIprpcFund.IsEmpty() { + newIndex = latestPendingIbcIprpcFund.Index + 1 + } + + // get expiry from current block time (using the rewards module parameter) + expiry := k.CalcPendingIbcIprpcFundExpiration(ctx) + + pendingIbcIprpcFund := types.PendingIbcIprpcFund{ + Index: newIndex, + Creator: creator, + Spec: spec, + Duration: duration, + Fund: monthlyFund, + Expiry: expiry, + } + + // sanity check + if !pendingIbcIprpcFund.IsValid() { + return types.PendingIbcIprpcFund{}, zeroCoin, utils.LavaFormatError("PendingIbcIprpcFund is invalid. expiry and duration must be positive, fund cannot be zero", fmt.Errorf("cannot create PendingIbcIprpcFund"), + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + utils.LogAttr("expiry", expiry), + ) + } + + k.SetPendingIbcIprpcFund(ctx, pendingIbcIprpcFund) + + return pendingIbcIprpcFund, leftovers, nil +} + +// SetPendingIbcIprpcFund set an PendingIbcIprpcFund in the PendingIbcIprpcFund store +func (k Keeper) SetPendingIbcIprpcFund(ctx sdk.Context, pendingIbcIprpcFund types.PendingIbcIprpcFund) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + b := k.cdc.MustMarshal(&pendingIbcIprpcFund) + store.Set(maps.GetIDBytes(pendingIbcIprpcFund.Index), b) +} + +// IsPendingIbcIprpcFund gets an PendingIbcIprpcFund from the PendingIbcIprpcFund store +func (k Keeper) GetPendingIbcIprpcFund(ctx sdk.Context, id uint64) (val types.PendingIbcIprpcFund, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + b := store.Get(maps.GetIDBytes(id)) + if b == nil { + return val, false + } + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// RemovePendingIbcIprpcFund removes an PendingIbcIprpcFund from the PendingIbcIprpcFund store +func (k Keeper) RemovePendingIbcIprpcFund(ctx sdk.Context, id uint64) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + store.Delete(maps.GetIDBytes(id)) +} + +// GetAllPendingIbcIprpcFund returns all PendingIbcIprpcFund from the PendingIbcIprpcFund store +func (k Keeper) GetAllPendingIbcIprpcFund(ctx sdk.Context) (list []types.PendingIbcIprpcFund) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.PendingIbcIprpcFund + k.cdc.MustUnmarshal(iterator.Value(), &val) + list = append(list, val) + } + + return +} + +// RemoveExpiredPendingIbcIprpcFunds removes all the expired PendingIbcIprpcFund objects from the PendingIbcIprpcFund store +func (k Keeper) RemoveExpiredPendingIbcIprpcFunds(ctx sdk.Context) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.PendingIbcIprpcFund + k.cdc.MustUnmarshal(iterator.Value(), &val) + if val.IsExpired(ctx) { + err := k.FundCommunityPoolFromModule(ctx, sdk.NewCoins(val.Fund), string(types.PendingIprpcPoolName)) + if err != nil { + utils.LavaFormatError("failed funding community pool from expired IBC IPRPC fund, removing without funding", err, + utils.LogAttr("pending_ibc_iprpc_fund", val.String()), + ) + } + k.RemovePendingIbcIprpcFund(ctx, val.Index) + utils.LogLavaEvent(ctx, k.Logger(ctx), types.ExpiredPendingIbcIprpcFundRemovedEventName, map[string]string{ + "index": strconv.FormatUint(val.Index, 10), + "creator": val.Creator, + "spec": val.Spec, + "duration": strconv.FormatUint(val.Duration, 10), + "monthly_fund": val.Fund.String(), + "expiry": strconv.FormatUint(val.Expiry, 10), + }, "Expired pending IBC IPRPC fund was removed. Funds were transferred to the community pool") + } else { + break + } + } +} + +// GetLatestPendingIbcIprpcFund gets the latest PendingIbcIprpcFund from the PendingIbcIprpcFund store +func (k Keeper) GetLatestPendingIbcIprpcFund(ctx sdk.Context) types.PendingIbcIprpcFund { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) + iterator := sdk.KVStoreReversePrefixIterator(store, []byte{}) + + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var val types.PendingIbcIprpcFund + k.cdc.MustUnmarshal(iterator.Value(), &val) + return val + } + + return types.PendingIbcIprpcFund{} +} + +// CalcPendingIbcIprpcFundMinCost calculates the required cost to apply it (transfer the funds to the IPRPC pool) +func (k Keeper) CalcPendingIbcIprpcFundMinCost(ctx sdk.Context, pendingIbcIprpcFund types.PendingIbcIprpcFund) sdk.Coin { + minCost := k.GetMinIprpcCost(ctx) + minCost.Amount = minCost.Amount.MulRaw(int64(pendingIbcIprpcFund.Duration)) + return minCost +} + +// CalcPendingIbcIprpcFundExpiration returns the expiration timestamp of a PendingIbcIprpcFund +func (k Keeper) CalcPendingIbcIprpcFundExpiration(ctx sdk.Context) uint64 { + return uint64(ctx.BlockTime().Add(k.IbcIprpcExpiration(ctx)).UTC().Unix()) +} + +// CoverIbcIprpcFundCost covers the cost of a PendingIbcIprpcFund by sending the min cost funds to the IBC IPRPC receiver +// address and call FundIprpc(). Finally, it removes the PendingIbcIprpcFund object from the store +func (k Keeper) CoverIbcIprpcFundCost(ctx sdk.Context, creator string, index uint64) (costCovered sdk.Coin, err error) { + zeroCoin := sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), math.ZeroInt()) + creatorAddr, err := sdk.AccAddressFromBech32(creator) + if err != nil { + return zeroCoin, utils.LavaFormatWarning("invalid creator address. not Bech32", types.ErrCoverIbcIprpcFundCostFailed, + utils.LogAttr("creator", creator), + utils.LogAttr("index", index), + ) + } + + // get the PendingIbcIprpcFund with index + piif, found := k.GetPendingIbcIprpcFund(ctx, index) + if !found { + return zeroCoin, utils.LavaFormatWarning("PendingIbcIprpcFund not found", types.ErrCoverIbcIprpcFundCostFailed, + utils.LogAttr("creator", creator), + utils.LogAttr("index", index), + ) + } + + // sanity check: PendingIbcIprpcFund is not expired + if piif.IsExpired(ctx) { + k.RemovePendingIbcIprpcFund(ctx, index) + return zeroCoin, utils.LavaFormatWarning("PendingIbcIprpcFund with index is expired (deleted fund)", types.ErrCoverIbcIprpcFundCostFailed, + utils.LogAttr("creator", creator), + utils.LogAttr("index", index), + ) + } + + // send the min cost to the ValidatorsRewardsAllocationPoolName (gov module doesn't pay min cost) + cost := zeroCoin + if creator != k.authority { + cost = k.CalcPendingIbcIprpcFundMinCost(ctx, piif) + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, creatorAddr, string(types.ValidatorsRewardsAllocationPoolName), sdk.NewCoins(cost)) + if err != nil { + return zeroCoin, utils.LavaFormatWarning(types.ErrCoverIbcIprpcFundCostFailed.Error(), err, + utils.LogAttr("creator", creator), + utils.LogAttr("index", index), + ) + } + } + + // fund the iprpc pool from PendingIprpcPool (inside, the PendingIprpcPool and the gov module are not paying the min cost) + err = k.FundIprpc(ctx, string(types.PendingIprpcPoolName), piif.Duration, sdk.NewCoins(piif.Fund), piif.Spec) + if err != nil { + return zeroCoin, utils.LavaFormatWarning(types.ErrCoverIbcIprpcFundCostFailed.Error(), err, + utils.LogAttr("creator", creator), + utils.LogAttr("index", index), + ) + } + + // remove the PendingIbcIprpcFund + k.RemovePendingIbcIprpcFund(ctx, index) + + return cost, nil +} + +func (k Keeper) SendIbcTokensToPendingIprpcPool(ctx sdk.Context, amount sdk.Coin) error { + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, types.IbcIprpcReceiverAddress(), string(types.PendingIprpcPoolName), sdk.NewCoins(amount)) +} + +func (k Keeper) FundCommunityPoolFromIbcIprpcReceiver(ctx sdk.Context, amount sdk.Coin) error { + return k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(amount), types.IbcIprpcReceiverAddress()) +} + // SendIbcIprpcReceiverTokensToPendingIprpcPool sends tokens from the IbcIprpcReceiver to the PendingIprpcPool as part of the IPRPC over IBC mechanism // if the transfer fails, we try to transfer the tokens to the community pool func (k Keeper) SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx sdk.Context, amount sdk.Coin) error { @@ -91,7 +339,7 @@ func (k Keeper) SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx sdk.Context, am err1 := k.bankKeeper.SendCoinsFromAccountToModule(ctx, types.IbcIprpcReceiverAddress(), string(types.PendingIprpcPoolName), sdk.NewCoins(amount)) if err1 != nil { // we couldn't transfer the funds to the pending IPRPC fund request pool, try moving it to the community pool - err2 := k.distributionKeeper.FundCommunityPool(ctx, sdk.NewCoins(amount), types.IbcIprpcReceiverAddress()) + err2 := k.FundCommunityPoolFromIbcIprpcReceiver(ctx, amount) if err2 != nil { // community pool transfer failed, token kept locked in IbcIprpcReceiverAddress, return err ack return utils.LavaFormatError("could not send tokens from IbcIprpcReceiverAddress to pending IPRPC pool or community pool, tokens are locked in IbcIprpcReceiverAddress", diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go index 667e642817..bb4fb50c30 100644 --- a/x/rewards/keeper/ibc_iprpc_test.go +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -1,10 +1,21 @@ package keeper_test import ( + "strconv" "testing" + "time" sdkerrors "cosmossdk.io/errors" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + keepertest "github.com/lavanet/lava/testutil/keeper" + "github.com/lavanet/lava/testutil/nullify" + "github.com/lavanet/lava/testutil/sample" + commontypes "github.com/lavanet/lava/utils/common/types" + "github.com/lavanet/lava/x/rewards/keeper" "github.com/lavanet/lava/x/rewards/types" + "github.com/stretchr/testify/require" ) @@ -127,3 +138,390 @@ func TestParseIprpcOverIbcMemo(t *testing.T) { }) } } + +// Prevent strconv unused error +var _ = strconv.IntSize + +// createNPendingIbcIprpcFunds is a helper function that creates an n-sized array of PendingIbcIprpcFund objects +func createNPendingIbcIprpcFunds(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.PendingIbcIprpcFund { + items := make([]types.PendingIbcIprpcFund, n) + for i := range items { + items[i] = types.PendingIbcIprpcFund{ + Index: uint64(i), + Creator: "dummy", + Spec: "mock", + Duration: uint64(i), + Fund: GetIbcCoins(sdk.NewCoin(commontypes.TokenDenom, sdk.NewInt(int64(i+1)))), + Expiry: uint64(ctx.BlockTime().UTC().Unix()) + uint64(i), + } + keeper.SetPendingIbcIprpcFund(ctx, items[i]) + } + return items +} + +// TestPendingIbcIprpcFundsGet tests GetPendingIbcIprpcFund() +func TestPendingIbcIprpcFundsGet(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + items := createNPendingIbcIprpcFunds(keeper, ctx, 10) + for _, item := range items { + res, found := keeper.GetPendingIbcIprpcFund(ctx, item.Index) + require.True(t, found) + require.True(t, res.IsEqual(item)) + } +} + +// TestPendingIbcIprpcFundsRemove tests RemovePendingIbcIprpcFund +func TestPendingIbcIprpcFundsRemove(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + items := createNPendingIbcIprpcFunds(keeper, ctx, 10) + for _, item := range items { + keeper.RemovePendingIbcIprpcFund(ctx, item.Index) + _, found := keeper.GetPendingIbcIprpcFund(ctx, item.Index) + require.False(t, found) + } +} + +// TestPendingIbcIprpcFundsGetAll tests GetAllPendingIbcIprpcFund +func TestPendingIbcIprpcFundsGetAll(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + items := createNPendingIbcIprpcFunds(keeper, ctx, 10) + require.ElementsMatch(t, + nullify.Fill(items), + nullify.Fill(keeper.GetAllPendingIbcIprpcFund(ctx)), + ) +} + +// TestPendingIbcIprpcFundsRemoveExpired tests RemoveExpiredPendingIbcIprpcFunds and IsExpired +func TestPendingIbcIprpcFundsRemoveExpired(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + items := createNPendingIbcIprpcFunds(keeper, ctx, 10) + + // advance time so some of the PendingIbcIprpcFund will expire + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(3 * time.Second)) + + // verify they're expired + for i := range items { + if i <= 3 { + require.True(t, items[i].IsExpired(ctx)) + } else { + require.False(t, items[i].IsExpired(ctx)) + } + } + + // remove expired PendingIbcIprpcFund and check they cannot be found + keeper.RemoveExpiredPendingIbcIprpcFunds(ctx) + for _, item := range items { + _, found := keeper.GetPendingIbcIprpcFund(ctx, item.Index) + if item.Index <= 3 { + require.False(t, found) + } else { + require.True(t, found) + } + } +} + +// TestPendingIbcIprpcFundsRemoveExpiredWithBeginBlock tests that expired PendingIbcIprpcFunds are removed with BeginBlock +// Also, their funds should be sent to the community pool +func TestPendingIbcIprpcFundsRemoveExpiredWithBeginBlock(t *testing.T) { + ts := newTester(t, false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + items := createNPendingIbcIprpcFunds(&keeper, ctx, 10) + + // let funder be the account that sends the ibc-transfer msg + funder := sample.AccAddressObject() + funderBalance := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(10000))) + err := ts.Keepers.BankKeeper.SetBalance(ctx, funder, funderBalance) + require.NoError(t, err) + + // IbcIprpcReceiver gets its balance via an IBC transfer + ts.SendIprpcOverIbcTransferPacket(funder, sdk.NewCoin(ts.TokenDenom(), iprpcFunds.AmountOf(ts.TokenDenom())), 1) + + // advance block with 3 seconds to expire some of the PendingIbcIprpcFunds + ts.AdvanceBlock(3 * time.Second) + + // check that expired PendingIbcIprpcFunds were removed + for _, item := range items { + _, found := keeper.GetPendingIbcIprpcFund(ctx, item.Index) + if item.Index <= 3 { + require.False(t, found) + } else { + require.True(t, found) + } + } + + // check the community pool's balance (objects in indices 0-3 were removed, so expected balance is 1+2+3+4=10ulava) + expectedBalance := GetIbcCoins(sdk.NewCoin(commontypes.TokenDenom, sdk.NewInt(10))) + communityCoins := ts.Keepers.Distribution.GetFeePoolCommunityCoins(ts.Ctx) + communityBalance := communityCoins.AmountOf(expectedBalance.Denom).TruncateInt() + require.True(t, communityBalance.Equal(expectedBalance.Amount)) +} + +// TestPendingIbcIprpcFundGetLatest tests GetLatestPendingIbcIprpcFund +func TestPendingIbcIprpcFundGetLatest(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + latest := keeper.GetLatestPendingIbcIprpcFund(ctx) + require.True(t, latest.IsEmpty()) + items := createNPendingIbcIprpcFunds(keeper, ctx, 10) + latest = keeper.GetLatestPendingIbcIprpcFund(ctx) + require.True(t, latest.IsEqual(items[len(items)-1])) +} + +// TestPendingIbcIprpcFundNew tests NewPendingIbcIprpcFund +func TestPendingIbcIprpcFundNew(t *testing.T) { + ts := newTester(t, false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + spec := ts.Spec("mock") + validFunds := sdk.NewCoin("denom", math.OneInt()) + + template := []struct { + name string + spec string + funds sdk.Coin + success bool + }{ + {"valid", spec.Index, validFunds, true}, + {"invalid fund", spec.Index, sdk.NewCoin(ts.TokenDenom(), math.ZeroInt()), false}, + {"non-existent spec", "eth", validFunds, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + _, _, err := keeper.NewPendingIbcIprpcFund(ctx, "creator", tt.spec, 1, tt.funds) + if tt.success { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +// TestCalcPendingIbcIprpcFundMinCost tests CalcPendingIbcIprpcFundMinCost +func TestCalcPendingIbcIprpcFundMinCost(t *testing.T) { + ts := newTester(t, true) + ts.setupForIprpcTests(false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + latest := keeper.GetLatestPendingIbcIprpcFund(ctx) + minCost := keeper.CalcPendingIbcIprpcFundMinCost(ctx, latest) + expectedMinCost := sdk.NewCoin(ts.TokenDenom(), keeper.GetMinIprpcCost(ctx).Amount.MulRaw(int64(latest.Duration))) + require.True(t, minCost.IsEqual(expectedMinCost)) +} + +// TestCalcPendingIbcIprpcFundExpiration tests CalcPendingIbcIprpcFundExpiration +func TestCalcPendingIbcIprpcFundExpiration(t *testing.T) { + keeper, ctx := keepertest.RewardsKeeper(t) + expectedExpiry := uint64(ctx.BlockTime().Add(keeper.IbcIprpcExpiration(ctx)).UTC().Unix()) + expiry := keeper.CalcPendingIbcIprpcFundExpiration(ctx) + require.Equal(t, expectedExpiry, expiry) +} + +// TestPendingIbcIprpcFundsQuery tests that the pending-ibc-iprpc-funds query works as expected with filters +func TestPendingIbcIprpcFundsQuery(t *testing.T) { + ts := newTester(t, true) + ts.setupForIprpcTests(false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + items := createNPendingIbcIprpcFunds(&keeper, ctx, 3) + + // make some of the PendingIbcIprpcFunds different with creator and spec + items[0].Creator = "blabla" + items[1].Spec = mockSpec2 + keeper.SetPendingIbcIprpcFund(ctx, items[0]) + keeper.SetPendingIbcIprpcFund(ctx, items[1]) + + minCost := keeper.GetMinIprpcCost(ctx) + template := []struct { + name string + filter string + expectedPendingIbcIprpcFundInfo []types.PendingIbcIprpcFundInfo + success bool + }{ + {"no filter", "", []types.PendingIbcIprpcFundInfo{ + {PendingIbcIprpcFund: items[0], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[0].Duration)))}, + {PendingIbcIprpcFund: items[1], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[1].Duration)))}, + {PendingIbcIprpcFund: items[2], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[2].Duration)))}, + }, true}, + {"index filter", "2", []types.PendingIbcIprpcFundInfo{ + {PendingIbcIprpcFund: items[2], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[2].Duration)))}, + }, true}, + {"creator filter", "blabla", []types.PendingIbcIprpcFundInfo{ + {PendingIbcIprpcFund: items[0], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[0].Duration)))}, + }, true}, + {"spec filter", mockSpec2, []types.PendingIbcIprpcFundInfo{ + {PendingIbcIprpcFund: items[1], Cost: sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(int64(items[1].Duration)))}, + }, true}, + {"invalid index filter", "100", []types.PendingIbcIprpcFundInfo{}, false}, + {"invalid creator/spec filter", "yoyo", []types.PendingIbcIprpcFundInfo{}, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + res, err := ts.QueryRewardsPendingIbcIprpcFunds(tt.filter) + if tt.success { + require.NoError(t, err) + foundMatch := false + for _, piifi := range res.PendingIbcIprpcFundsInfo { + for _, expectedPiifi := range tt.expectedPendingIbcIprpcFundInfo { + if piifi.PendingIbcIprpcFund.IsEqual(expectedPiifi.PendingIbcIprpcFund) && piifi.Cost.IsEqual(expectedPiifi.Cost) { + foundMatch = true + break + } + } + if !foundMatch { + require.FailNow(t, "info result not matching expected") + } + foundMatch = false + } + } else { + require.Error(t, err) + } + }) + } +} + +// TestPendingIbcIprpcFundNewFunds tests that when creating a new PendingIbcIprpcFund the original +// fund gets divided by duration and the division leftovers are transferred to the community pool +func TestPendingIbcIprpcFundNewFunds(t *testing.T) { + template := []struct { + name string + funds math.Int + duration uint64 + expectedFundsInPending math.Int + expectedFundsInCommunity math.Int + success bool + }{ + {"divisiable - 9ulava", math.NewInt(9), 3, math.NewInt(3), math.ZeroInt(), true}, + {"not divisiable - 10ulava", math.NewInt(10), 3, math.NewInt(3), math.OneInt(), true}, + {"less than duration - 1ulava", math.NewInt(1), 3, math.ZeroInt(), math.ZeroInt(), false}, + {"one month duration - 10ulava", math.NewInt(10), 1, math.NewInt(10), math.ZeroInt(), true}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + ts := newTester(t, false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + spec := ts.Spec("mock") + funds := sdk.NewCoin(ts.TokenDenom(), tt.funds) + + // let funder be the account that sends the ibc-transfer msg + funder := sample.AccAddressObject() + funderBalance := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(10000))) + err := ts.Keepers.BankKeeper.SetBalance(ctx, funder, funderBalance) + require.NoError(t, err) + + // IbcIprpcReceiver gets its balance via an IBC transfer (leftover funds are taken from it to the community pool) + ts.SendIprpcOverIbcTransferPacket(funder, funds, 1) + + // create a new PendingIbcIprpcFund + piif, leftovers, err := keeper.NewPendingIbcIprpcFund(ctx, "creator", spec.Index, tt.duration, funds) + if tt.success { + require.NoError(t, err) + require.True(t, piif.Fund.Amount.Equal(tt.expectedFundsInPending)) + require.True(t, leftovers.Amount.Equal(tt.expectedFundsInCommunity)) + } else { + require.Error(t, err) + } + }) + } +} + +// TestCoverIbcIprpcFundCost tests that the cover-ibc-iprpc-fund-cost transaction +// Scenarios: +// 0. Create 2 PendingIbcIprpcFund objects and fund PendingIprpcPool and gov. First with 101ulava for 2 months, second with +// 99ulava for 2 months. Expected: PendingIbcIprpcFund with 50ulava, PendingIbcIprpcFund with 49ulava, community pool 2ulava +// 1. Cover costs with alice for first PendingIbcIprpcFund. Expect two iprpc rewards from next month of 50ulava, PendingIbcIprpcFund +// removed, IPRPC pool with 100ulava, second PendingIbcIprpcFund remains (49ulava), alice balance reduced by MinIprpcCost +// 2. Cover costs with gov module for second PendingIbcIprpcFund. Expect two iprpc rewards from next month of 99ulava, PendingIbcIprpcFund +// removed, IPRPC pool with 198ulava, gov module balance not reduced by MinIprpcCost +func TestCoverIbcIprpcFundCost(t *testing.T) { + ts := newTester(t, true) + ts.setupForIprpcTests(false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + + // let funder be a dummy account to send the IBC transfer coins + // let alice be the account that cover costs + funder := sample.AccAddressObject() + funderBalance := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(10000))) + err := ts.Keepers.BankKeeper.SetBalance(ctx, funder, funderBalance) + require.NoError(t, err) + + alice := sample.AccAddressObject() + aliceBalance := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(10000))) + err = ts.Keepers.BankKeeper.SetBalance(ctx, alice, aliceBalance) + require.NoError(t, err) + + // set min IPRPC cost to be 50ulava (for validation checks later) + minCost := sdk.NewCoin(ts.TokenDenom(), math.NewInt(50)) + keeper.SetMinIprpcCost(ctx, minCost) + + // create 2 pending IPRPC requests + funds1 := sdk.NewCoin(ts.TokenDenom(), math.NewInt(101)) // will be index 0 + ts.SendIprpcOverIbcTransferPacket(funder, funds1, 2) + funds2 := sdk.NewCoin(ts.TokenDenom(), math.NewInt(99)) // will be index 1 + ts.SendIprpcOverIbcTransferPacket(funder, funds2, 2) + expectedPendingIprpcPoolBalance := GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(198))) // 99+101-leftovers = 99+101-2 + pendingIprpcPoolBalance := ts.Keepers.Rewards.TotalPoolTokens(ctx, types.PendingIprpcPoolName) + require.True(t, pendingIprpcPoolBalance.IsEqual(sdk.NewCoins(expectedPendingIprpcPoolBalance))) + + // fund the gov module with a dummy balance, just to see it's not changing + govModuleBalance := sdk.NewCoins(sdk.NewCoin(ts.TokenDenom(), math.OneInt())) + govModule := ts.Keepers.AccountKeeper.GetModuleAddress("gov") + err = ts.Keepers.BankKeeper.SetBalance(ctx, govModule, govModuleBalance) + require.NoError(t, err) + + // cover costs of first PendingIbcIprpcFund with alice + expectedMinCost := sdk.NewCoin(minCost.Denom, minCost.Amount.MulRaw(2)) + _, err = ts.TxRewardsCoverIbcIprpcFundCost(alice.String(), 0) + require.NoError(t, err) + _, found := keeper.GetPendingIbcIprpcFund(ctx, 0) + require.False(t, found) + require.Equal(t, expectedMinCost.Amount.Int64(), aliceBalance.AmountOf(ts.TokenDenom()).Int64()-ts.GetBalance(alice)) + + res, err := ts.QueryRewardsIprpcSpecReward(mockSpec) + require.NoError(t, err) + expectedIprpcRewards := []types.IprpcReward{ + {Id: 1, SpecFunds: []types.Specfund{{Spec: mockSpec, Fund: sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(50))))}}}, + {Id: 2, SpecFunds: []types.Specfund{{Spec: mockSpec, Fund: sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(50))))}}}, + } + require.Len(t, res.IprpcRewards, len(expectedIprpcRewards)) + for i := range res.IprpcRewards { + require.Equal(t, expectedIprpcRewards[i].Id, res.IprpcRewards[i].Id) + require.ElementsMatch(t, expectedIprpcRewards[i].SpecFunds, res.IprpcRewards[i].SpecFunds) + } + + _, found = keeper.GetPendingIbcIprpcFund(ctx, 1) + require.True(t, found) + + expectedIprpcPoolBalance := sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(100)))) + iprpcPoolBalance := ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, types.IprpcPoolName) + require.True(t, expectedIprpcPoolBalance.IsEqual(iprpcPoolBalance)) + + // cover costs of second PendingIbcIprpcFund with gov + // note that the gov module's balance should not change since it's the only account + // that doesn't need to pay min cost (see check below) + _, err = ts.TxRewardsCoverIbcIprpcFundCost(govModule.String(), 1) + require.NoError(t, err) + _, found = keeper.GetPendingIbcIprpcFund(ctx, 1) + require.False(t, found) + require.Equal(t, int64(0), govModuleBalance.AmountOf(ts.TokenDenom()).Int64()-ts.GetBalance(govModule)) + + res, err = ts.QueryRewardsIprpcSpecReward(mockSpec) + require.NoError(t, err) + expectedIprpcRewards = []types.IprpcReward{ + {Id: 1, SpecFunds: []types.Specfund{{Spec: mockSpec, Fund: sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(99))))}}}, + {Id: 2, SpecFunds: []types.Specfund{{Spec: mockSpec, Fund: sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(99))))}}}, + } + require.Len(t, res.IprpcRewards, len(expectedIprpcRewards)) + for i := range res.IprpcRewards { + require.Equal(t, expectedIprpcRewards[i].Id, res.IprpcRewards[i].Id) + require.ElementsMatch(t, expectedIprpcRewards[i].SpecFunds, res.IprpcRewards[i].SpecFunds) + } + + expectedIprpcPoolBalance = sdk.NewCoins(GetIbcCoins(sdk.NewCoin(ts.TokenDenom(), math.NewInt(198)))) + iprpcPoolBalance = ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, types.IprpcPoolName) + require.True(t, expectedIprpcPoolBalance.IsEqual(iprpcPoolBalance)) + + // verify that PendingIprpcPool has 0ulava balance (in the IBC middleware, the leftovers + // of both IPRPC over IBC are sent to the community pool) + pendingIprpcPoolBalance = ts.Keepers.Rewards.TotalPoolTokens(ts.Ctx, types.PendingIprpcPoolName) + require.True(t, pendingIprpcPoolBalance.Empty()) +} diff --git a/x/rewards/keeper/iprpc.go b/x/rewards/keeper/iprpc.go index cccaaee334..41d2e60dd6 100644 --- a/x/rewards/keeper/iprpc.go +++ b/x/rewards/keeper/iprpc.go @@ -17,40 +17,56 @@ func (k Keeper) FundIprpc(ctx sdk.Context, creator string, duration uint64, fund return utils.LavaFormatWarning("spec not found or disabled", types.ErrFundIprpc) } - // check fund consists of minimum amount of ulava (min_iprpc_cost) - minIprpcFundCost := k.GetMinIprpcCost(ctx) - if fund.AmountOf(k.stakingKeeper.BondDenom(ctx)).LT(minIprpcFundCost.Amount) { - return utils.LavaFormatWarning("insufficient ulava tokens in fund. should be at least min iprpc cost * duration", types.ErrFundIprpc, - utils.LogAttr("min_iprpc_cost", k.GetMinIprpcCost(ctx).String()), - utils.LogAttr("duration", strconv.FormatUint(duration, 10)), - utils.LogAttr("fund_ulava_amount", fund.AmountOf(k.stakingKeeper.BondDenom(ctx))), - ) - } - - // check creator has enough balance - addr, err := sdk.AccAddressFromBech32(creator) - if err != nil { - return utils.LavaFormatWarning("invalid creator address", types.ErrFundIprpc) - } + // calculate total funds to transfer to the IPRPC pool (input fund is monthly fund) + totalFunds := fund.MulInt(math.NewIntFromUint64(duration)) + + // if the fund TX originates from the gov module (keeper's authority field) or the pending IPRPC pool it's not paying the minimum IPRPC cost + if creator != k.authority && creator != string(types.PendingIprpcPoolName) { + // check fund consists of minimum amount of ulava (min_iprpc_cost) + minIprpcFundCost := k.GetMinIprpcCost(ctx) + if fund.AmountOf(k.stakingKeeper.BondDenom(ctx)).LT(minIprpcFundCost.Amount) { + return utils.LavaFormatWarning("insufficient ulava tokens in fund. should be at least min iprpc cost * duration", types.ErrFundIprpc, + utils.LogAttr("min_iprpc_cost", k.GetMinIprpcCost(ctx).String()), + utils.LogAttr("duration", strconv.FormatUint(duration, 10)), + utils.LogAttr("fund_ulava_amount", fund.AmountOf(k.stakingKeeper.BondDenom(ctx))), + ) + } else if fund.IsEqual(sdk.NewCoins(minIprpcFundCost)) { + return utils.LavaFormatWarning("funds are equal to min iprpc cost, no funds left to send to iprpc pool", types.ErrFundIprpc, + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("funds", fund.String()), + utils.LogAttr("min_iprpc_cost", minIprpcFundCost.String()), + ) + } - // send the minimum cost to the validators allocation pool (and subtract them from the fund) - minIprpcFundCostCoins := sdk.NewCoins(minIprpcFundCost).MulInt(sdk.NewIntFromUint64(duration)) - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, string(types.ValidatorsRewardsAllocationPoolName), minIprpcFundCostCoins) - if err != nil { - return utils.LavaFormatError(types.ErrFundIprpc.Error()+"for funding validator allocation pool", err, - utils.LogAttr("creator", creator), - utils.LogAttr("min_iprpc_fund_cost", minIprpcFundCost.String()), - ) + // send the minimum cost to the validators allocation pool (and subtract them from the fund) + minIprpcFundCostCoins := sdk.NewCoins(minIprpcFundCost).MulInt(sdk.NewIntFromUint64(duration)) + addr, err := sdk.AccAddressFromBech32(creator) + if err != nil { + return utils.LavaFormatError(types.ErrFundIprpc.Error()+"for funding validator allocation pool with min iprpc cost", err, + utils.LogAttr("creator", creator), + utils.LogAttr("min_iprpc_fund_cost", minIprpcFundCost.String()), + ) + } + err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, string(types.ValidatorsRewardsAllocationPoolName), minIprpcFundCostCoins) + if err != nil { + return utils.LavaFormatError(types.ErrFundIprpc.Error()+"for funding validator allocation pool with min iprpc cost", err, + utils.LogAttr("creator", creator), + utils.LogAttr("min_iprpc_fund_cost", minIprpcFundCost.String()), + ) + } + fund = fund.Sub(minIprpcFundCost) + totalFunds = fund.MulInt(math.NewIntFromUint64(duration)) } - fund = fund.Sub(minIprpcFundCost) - allFunds := fund.MulInt(math.NewIntFromUint64(duration)) // send the funds to the iprpc pool - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, string(types.IprpcPoolName), allFunds) + err := k.sendCoinsToIprpcPool(ctx, creator, totalFunds) if err != nil { return utils.LavaFormatError(types.ErrFundIprpc.Error()+"for funding iprpc pool", err, utils.LogAttr("creator", creator), - utils.LogAttr("fund", fund.String()), + utils.LogAttr("monthly_fund", fund.String()), + utils.LogAttr("duration", duration), + utils.LogAttr("total_fund", totalFunds.String()), ) } @@ -60,6 +76,20 @@ func (k Keeper) FundIprpc(ctx sdk.Context, creator string, duration uint64, fund return nil } +func (k Keeper) sendCoinsToIprpcPool(ctx sdk.Context, sender string, amount sdk.Coins) error { + // sender is gov module or pending IPRPC pool - use SendCoinsFromModuleToModule + if sender == k.authority || sender == string(types.PendingIprpcPoolName) { + return k.bankKeeper.SendCoinsFromModuleToModule(ctx, sender, string(types.IprpcPoolName), amount) + } + + addr, err := sdk.AccAddressFromBech32(sender) + if err != nil { + return err + } + + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, string(types.IprpcPoolName), amount) +} + // handleNoIprpcRewardToProviders handles the situation in which there are no providers to send IPRPC rewards to // so the IPRPC rewards transfer to the next month func (k Keeper) handleNoIprpcRewardToProviders(ctx sdk.Context, iprpcFunds []types.Specfund) { diff --git a/x/rewards/keeper/iprpc_reward.go b/x/rewards/keeper/iprpc_reward.go index 4df6b9c713..19f7cab20d 100644 --- a/x/rewards/keeper/iprpc_reward.go +++ b/x/rewards/keeper/iprpc_reward.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/utils/maps" "github.com/lavanet/lava/x/rewards/types" ) @@ -36,13 +37,13 @@ func (k Keeper) SetIprpcRewardsCurrentId(ctx sdk.Context, current uint64) { func (k Keeper) SetIprpcReward(ctx sdk.Context, iprpcReward types.IprpcReward) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.IprpcRewardPrefix)) b := k.cdc.MustMarshal(&iprpcReward) - store.Set(GetIprpcRewardIDBytes(iprpcReward.Id), b) + store.Set(maps.GetIDBytes(iprpcReward.Id), b) } // GetIprpcReward returns a IprpcReward from its id func (k Keeper) GetIprpcReward(ctx sdk.Context, id uint64) (val types.IprpcReward, found bool) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.IprpcRewardPrefix)) - b := store.Get(GetIprpcRewardIDBytes(id)) + b := store.Get(maps.GetIDBytes(id)) if b == nil { return val, false } @@ -53,7 +54,7 @@ func (k Keeper) GetIprpcReward(ctx sdk.Context, id uint64) (val types.IprpcRewar // RemoveIprpcReward removes a IprpcReward from the store func (k Keeper) RemoveIprpcReward(ctx sdk.Context, id uint64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.IprpcRewardPrefix)) - store.Delete(GetIprpcRewardIDBytes(id)) + store.Delete(maps.GetIDBytes(id)) } // GetAllIprpcReward returns all IprpcReward @@ -72,18 +73,6 @@ func (k Keeper) GetAllIprpcReward(ctx sdk.Context) (list []types.IprpcReward) { return } -// GetIprpcRewardIDBytes returns the byte representation of the ID -func GetIprpcRewardIDBytes(id uint64) []byte { - bz := make([]byte, 8) - binary.BigEndian.PutUint64(bz, id) - return bz -} - -// GetIprpcRewardIDFromBytes returns ID in uint64 format from a byte array -func GetIprpcRewardIDFromBytes(bz []byte) uint64 { - return binary.BigEndian.Uint64(bz) -} - // PopIprpcReward gets the lowest id IprpcReward object and removes it func (k Keeper) PopIprpcReward(ctx sdk.Context) (types.IprpcReward, bool) { current := k.GetIprpcRewardsCurrentId(ctx) diff --git a/x/rewards/keeper/iprpc_test.go b/x/rewards/keeper/iprpc_test.go index fd94755a6b..100e2c488b 100644 --- a/x/rewards/keeper/iprpc_test.go +++ b/x/rewards/keeper/iprpc_test.go @@ -31,12 +31,14 @@ func TestFundIprpcTX(t *testing.T) { } // we fund as follows (to all we add the min IPRPC price. the description below is the funds that go to the pool): + // - invalid amount = exactly minIprpcCost // - 10ulava, 1 month, mockspec // - 50uibc, 1 month, mockspec // - 90ulava + 30uibc, 3 months, mockspec2 // - 130uibc, 3 months, mockspec // - 10ulava + 120uibc, 12 months, mockspec2 fundIprpcTXsData := []fundIprpcData{ + {spec: ts.specs[0].Index, duration: 1, fund: sdk.NewCoins(minIprpcCost)}, // invalid {spec: ts.specs[0].Index, duration: 1, fund: sdk.NewCoins( sdk.NewCoin(ts.BondDenom(), math.NewInt(10+minIprpcCost.Amount.Int64())), )}, @@ -58,9 +60,13 @@ func TestFundIprpcTX(t *testing.T) { )}, } - for _, txData := range fundIprpcTXsData { + for i, txData := range fundIprpcTXsData { _, err = ts.TxRewardsFundIprpc(consumer, txData.spec, txData.duration, txData.fund) - require.NoError(t, err) + if i == 0 { + require.Error(t, err) + } else { + require.NoError(t, err) + } } // Expected total IPRPC pool balance (by TXs): diff --git a/x/rewards/keeper/keeper.go b/x/rewards/keeper/keeper.go index 064cd784b4..54d2c73acc 100644 --- a/x/rewards/keeper/keeper.go +++ b/x/rewards/keeper/keeper.go @@ -113,6 +113,7 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { // redeclaring BeginBlock for testing (this is not called outside of unit tests) func (k Keeper) BeginBlock(ctx sdk.Context) { k.DistributeBlockReward(ctx) + k.RemoveExpiredPendingIbcIprpcFunds(ctx) } func (k Keeper) GetModuleAddress() string { diff --git a/x/rewards/keeper/migrations.go b/x/rewards/keeper/migrations.go index 80f6d3486e..9b0c527307 100644 --- a/x/rewards/keeper/migrations.go +++ b/x/rewards/keeper/migrations.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/x/rewards/types" ) type Migrator struct { @@ -16,3 +17,9 @@ func NewMigrator(keeper Keeper) Migrator { func (m Migrator) MigrateVersion1To2(ctx sdk.Context) error { return m.keeper.SetIprpcData(ctx, sdk.NewCoin(m.keeper.stakingKeeper.BondDenom(ctx), sdk.NewInt(100000000)), []string{}) } + +// MigrateVersion2To3 sets PendingIbcIprpcExpiration to 3 months +func (m Migrator) MigrateVersion2To3(ctx sdk.Context) error { + m.keeper.SetParams(ctx, types.DefaultGenesis().Params) + return nil +} diff --git a/x/rewards/keeper/msg_server_cover_ibc_iprpc_fund_cost.go b/x/rewards/keeper/msg_server_cover_ibc_iprpc_fund_cost.go new file mode 100644 index 0000000000..2a3e2e83b7 --- /dev/null +++ b/x/rewards/keeper/msg_server_cover_ibc_iprpc_fund_cost.go @@ -0,0 +1,32 @@ +package keeper + +import ( + "context" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/utils" + "github.com/lavanet/lava/x/rewards/types" +) + +func (k msgServer) CoverIbcIprpcFundCost(goCtx context.Context, msg *types.MsgCoverIbcIprpcFundCost) (*types.MsgCoverIbcIprpcFundCostResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + err := msg.ValidateBasic() + if err != nil { + return &types.MsgCoverIbcIprpcFundCostResponse{}, err + } + + cost, err := k.Keeper.CoverIbcIprpcFundCost(ctx, msg.Creator, msg.Index) + if err == nil { + logger := k.Keeper.Logger(ctx) + details := map[string]string{ + "creator": msg.Creator, + "index": strconv.FormatUint(msg.Index, 10), + "cost_covered": cost.String(), + } + utils.LogLavaEvent(ctx, logger, types.CoverIbcIprpcFundCostEventName, details, "Covered costs of pending IBC IPRPC fund successfully") + } + + return &types.MsgCoverIbcIprpcFundCostResponse{}, err +} diff --git a/x/rewards/keeper/msg_server_fund_iprpc.go b/x/rewards/keeper/msg_server_fund_iprpc.go index d0e4e4a678..3c28e46717 100644 --- a/x/rewards/keeper/msg_server_fund_iprpc.go +++ b/x/rewards/keeper/msg_server_fund_iprpc.go @@ -21,6 +21,7 @@ func (k msgServer) FundIprpc(goCtx context.Context, msg *types.MsgFundIprpc) (*t if err == nil { logger := k.Keeper.Logger(ctx) details := map[string]string{ + "creator": msg.Creator, "spec": msg.Spec, "duration": strconv.FormatUint(msg.Duration, 10), "amounts": msg.Amounts.String(), diff --git a/x/rewards/keeper/params.go b/x/rewards/keeper/params.go index a2c2cb1a8f..9abd06b8bb 100644 --- a/x/rewards/keeper/params.go +++ b/x/rewards/keeper/params.go @@ -1,6 +1,8 @@ package keeper import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/lavanet/lava/x/rewards/types" ) @@ -14,6 +16,7 @@ func (k Keeper) GetParams(ctx sdk.Context) types.Params { k.LeftoverBurnRate(ctx), k.MaxRewardBoost(ctx), k.ValidatorsSubscriptionParticipation(ctx), + k.IbcIprpcExpiration(ctx), ) } @@ -57,3 +60,9 @@ func (k Keeper) ValidatorsSubscriptionParticipation(ctx sdk.Context) (res sdk.De k.paramstore.Get(ctx, types.KeyValidatorsSubscriptionParticipation, &res) return } + +// IbcIprpcExpiration returns the IbcIprpcExpiration param +func (k Keeper) IbcIprpcExpiration(ctx sdk.Context) (res time.Duration) { + k.paramstore.Get(ctx, types.KeyIbcIprpcExpiration, &res) + return +} diff --git a/x/rewards/keeper/pool_test.go b/x/rewards/keeper/pool_test.go index 142a4c1a0d..9cd34c91e8 100644 --- a/x/rewards/keeper/pool_test.go +++ b/x/rewards/keeper/pool_test.go @@ -328,6 +328,7 @@ func TestBondedTargetFactorEdgeCases(t *testing.T) { LeftoverBurnRate: types.DefaultLeftOverBurnRate, MaxRewardBoost: types.DefaultMaxRewardBoost, ValidatorsSubscriptionParticipation: types.DefaultValidatorsSubscriptionParticipation, + IbcIprpcExpiration: types.DefaultIbcIprpcExpiration, } ts.Keepers.Rewards.SetParams(ts.Ctx, params) diff --git a/x/rewards/module.go b/x/rewards/module.go index cba6a9c05d..4232bbbfd9 100644 --- a/x/rewards/module.go +++ b/x/rewards/module.go @@ -135,6 +135,12 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { // panic:ok: at start up, migration cannot proceed anyhow panic(fmt.Errorf("%s: failed to register migration to v2: %w", types.ModuleName, err)) } + + // register v2 -> v3 migration + if err := cfg.RegisterMigration(types.ModuleName, 2, migrator.MigrateVersion2To3); err != nil { + // panic:ok: at start up, migration cannot proceed anyhow + panic(fmt.Errorf("%s: failed to register migration to v3: %w", types.ModuleName, err)) + } } // RegisterInvariants registers the capability module's invariants. @@ -159,7 +165,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 2 } +func (AppModule) ConsensusVersion() uint64 { return 3 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { diff --git a/x/rewards/types/errors.go b/x/rewards/types/errors.go index 5fcb1336ce..b3dae1c670 100644 --- a/x/rewards/types/errors.go +++ b/x/rewards/types/errors.go @@ -8,7 +8,8 @@ import ( // x/rewards module sentinel errors var ( - ErrFundIprpc = sdkerrors.Register(ModuleName, 1, "fund iprpc TX failed") - ErrMemoNotIprpcOverIbc = sdkerrors.Register(ModuleName, 2, "ibc-transfer packet's memo is not in the right format of IPRPC over IBC") - ErrIprpcMemoInvalid = sdkerrors.Register(ModuleName, 3, "ibc-transfer packet's memo of IPRPC over IBC is invalid") + ErrFundIprpc = sdkerrors.Register(ModuleName, 1, "fund iprpc TX failed") + ErrMemoNotIprpcOverIbc = sdkerrors.Register(ModuleName, 2, "ibc-transfer packet's memo is not in the right format of IPRPC over IBC") + ErrIprpcMemoInvalid = sdkerrors.Register(ModuleName, 3, "ibc-transfer packet's memo of IPRPC over IBC is invalid") + ErrCoverIbcIprpcFundCostFailed = sdkerrors.Register(ModuleName, 4, "cover ibc iprpc fund cost failed") ) diff --git a/x/rewards/types/genesis.go b/x/rewards/types/genesis.go index 217ed9506d..fa39c6a8fa 100644 --- a/x/rewards/types/genesis.go +++ b/x/rewards/types/genesis.go @@ -17,13 +17,14 @@ const DefaultIndex uint64 = 1 func DefaultGenesis() *GenesisState { return &GenesisState{ // this line is used by starport scaffolding # genesis/types/default - Params: DefaultParams(), - RefillRewardsTS: *types.DefaultGenesis(), - BasePays: []BasePayGenesis{}, - IprpcSubscriptions: []string{}, - MinIprpcCost: sdk.NewCoin(commontypes.TokenDenom, sdk.ZeroInt()), - IprpcRewards: []IprpcReward{}, - IprpcRewardsCurrent: 0, + Params: DefaultParams(), + RefillRewardsTS: *types.DefaultGenesis(), + BasePays: []BasePayGenesis{}, + IprpcSubscriptions: []string{}, + MinIprpcCost: sdk.NewCoin(commontypes.TokenDenom, sdk.ZeroInt()), + IprpcRewards: []IprpcReward{}, + IprpcRewardsCurrent: 0, + PendingIbcIprpcFunds: []PendingIbcIprpcFund{}, } } @@ -68,5 +69,11 @@ func (gs GenesisState) Validate() error { } } + for _, pendingIbcIprpcFund := range gs.PendingIbcIprpcFunds { + if !pendingIbcIprpcFund.IsValid() { + return fmt.Errorf("invalid ibc iprpc fund: %s", pendingIbcIprpcFund.String()) + } + } + return gs.Params.Validate() } diff --git a/x/rewards/types/genesis.pb.go b/x/rewards/types/genesis.pb.go index d39a2fc306..4e6bfcf7b6 100644 --- a/x/rewards/types/genesis.pb.go +++ b/x/rewards/types/genesis.pb.go @@ -27,13 +27,14 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the rewards module's genesis state. type GenesisState struct { - Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` - RefillRewardsTS types.GenesisState `protobuf:"bytes,2,opt,name=refillRewardsTS,proto3" json:"refillRewardsTS"` - BasePays []BasePayGenesis `protobuf:"bytes,3,rep,name=base_pays,json=basePays,proto3" json:"base_pays"` - IprpcSubscriptions []string `protobuf:"bytes,4,rep,name=iprpc_subscriptions,json=iprpcSubscriptions,proto3" json:"iprpc_subscriptions,omitempty"` - MinIprpcCost types1.Coin `protobuf:"bytes,5,opt,name=min_iprpc_cost,json=minIprpcCost,proto3" json:"min_iprpc_cost"` - IprpcRewards []IprpcReward `protobuf:"bytes,6,rep,name=iprpc_rewards,json=iprpcRewards,proto3" json:"iprpc_rewards"` - IprpcRewardsCurrent uint64 `protobuf:"varint,7,opt,name=iprpc_rewards_current,json=iprpcRewardsCurrent,proto3" json:"iprpc_rewards_current,omitempty"` + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + RefillRewardsTS types.GenesisState `protobuf:"bytes,2,opt,name=refillRewardsTS,proto3" json:"refillRewardsTS"` + BasePays []BasePayGenesis `protobuf:"bytes,3,rep,name=base_pays,json=basePays,proto3" json:"base_pays"` + IprpcSubscriptions []string `protobuf:"bytes,4,rep,name=iprpc_subscriptions,json=iprpcSubscriptions,proto3" json:"iprpc_subscriptions,omitempty"` + MinIprpcCost types1.Coin `protobuf:"bytes,5,opt,name=min_iprpc_cost,json=minIprpcCost,proto3" json:"min_iprpc_cost"` + IprpcRewards []IprpcReward `protobuf:"bytes,6,rep,name=iprpc_rewards,json=iprpcRewards,proto3" json:"iprpc_rewards"` + IprpcRewardsCurrent uint64 `protobuf:"varint,7,opt,name=iprpc_rewards_current,json=iprpcRewardsCurrent,proto3" json:"iprpc_rewards_current,omitempty"` + PendingIbcIprpcFunds []PendingIbcIprpcFund `protobuf:"bytes,8,rep,name=pending_ibc_iprpc_funds,json=pendingIbcIprpcFunds,proto3" json:"pending_ibc_iprpc_funds"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -118,6 +119,13 @@ func (m *GenesisState) GetIprpcRewardsCurrent() uint64 { return 0 } +func (m *GenesisState) GetPendingIbcIprpcFunds() []PendingIbcIprpcFund { + if m != nil { + return m.PendingIbcIprpcFunds + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "lavanet.lava.rewards.GenesisState") } @@ -127,34 +135,37 @@ func init() { } var fileDescriptor_02c24f4df31ca14e = []byte{ - // 429 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0x41, 0x6f, 0xd3, 0x30, - 0x14, 0x80, 0x13, 0x52, 0x0a, 0xf3, 0x06, 0x48, 0xde, 0x90, 0x42, 0x85, 0x42, 0x36, 0x40, 0xe4, - 0x64, 0x6b, 0xe5, 0xc6, 0x8d, 0x56, 0x68, 0x42, 0xe2, 0x30, 0xa5, 0x70, 0xe1, 0x12, 0x39, 0xc1, - 0x04, 0x4b, 0x8d, 0x1d, 0xf9, 0xb9, 0x83, 0xfe, 0x0b, 0x7e, 0xd6, 0x8e, 0x3b, 0x72, 0x42, 0xa8, - 0xe5, 0x87, 0xa0, 0xd8, 0x2e, 0x6d, 0xa7, 0x9c, 0x6c, 0xf9, 0x7d, 0xef, 0xf3, 0x7b, 0x4f, 0x0f, - 0x9d, 0xcd, 0xd9, 0x15, 0x93, 0xdc, 0xd0, 0xee, 0xa4, 0x9a, 0x7f, 0x67, 0xfa, 0x0b, 0xd0, 0x9a, - 0x4b, 0x0e, 0x02, 0x48, 0xab, 0x95, 0x51, 0xf8, 0xc4, 0x33, 0xa4, 0x3b, 0x89, 0x67, 0x46, 0x27, - 0xb5, 0xaa, 0x95, 0x05, 0x68, 0x77, 0x73, 0xec, 0xe8, 0xb4, 0xd7, 0xd7, 0x32, 0xcd, 0x1a, 0xaf, - 0x1b, 0x3d, 0xef, 0x45, 0x4a, 0x06, 0xbc, 0x68, 0xd9, 0xd2, 0x43, 0x69, 0x2f, 0x24, 0x5a, 0xdd, - 0x56, 0xbd, 0x1a, 0x23, 0x1a, 0xae, 0xc1, 0x28, 0xcd, 0xdd, 0xd5, 0x43, 0x49, 0xa5, 0xa0, 0x51, - 0xce, 0x4e, 0xaf, 0xce, 0x4b, 0x6e, 0xd8, 0x39, 0xad, 0x94, 0x90, 0x2e, 0x7e, 0xf6, 0x37, 0x42, - 0x47, 0x17, 0xae, 0xd9, 0x99, 0x61, 0x86, 0xe3, 0x37, 0x68, 0xe8, 0x8a, 0x8d, 0xc3, 0x34, 0xcc, - 0x0e, 0xc7, 0x4f, 0x49, 0x5f, 0xf3, 0xe4, 0xd2, 0x32, 0x93, 0xc1, 0xf5, 0xef, 0x67, 0x41, 0xee, - 0x33, 0xf0, 0x27, 0xf4, 0x48, 0xf3, 0xaf, 0x62, 0x3e, 0xcf, 0x1d, 0xf5, 0x71, 0x16, 0xdf, 0xb1, - 0x92, 0x97, 0xfb, 0x92, 0x6d, 0xad, 0x64, 0xf7, 0x6f, 0x6f, 0xbb, 0xed, 0xc0, 0x17, 0xe8, 0x60, - 0x33, 0x1c, 0x88, 0xa3, 0x34, 0xca, 0x0e, 0xc7, 0x2f, 0xfa, 0xab, 0x9a, 0x30, 0xe0, 0x97, 0x6c, - 0xe9, 0xa5, 0xde, 0x77, 0xbf, 0x74, 0xaf, 0x80, 0x29, 0x3a, 0xb6, 0x03, 0x2c, 0x60, 0x51, 0x42, - 0xa5, 0x45, 0x6b, 0x84, 0x92, 0x10, 0x0f, 0xd2, 0x28, 0x3b, 0xc8, 0xb1, 0x0d, 0xcd, 0x76, 0x23, - 0xf8, 0x1d, 0x7a, 0xd8, 0x08, 0x59, 0xb8, 0xa4, 0x4a, 0x81, 0x89, 0xef, 0xda, 0x7e, 0x9e, 0x10, - 0x37, 0x56, 0xd2, 0xa9, 0x89, 0x1f, 0x2b, 0x99, 0x2a, 0x21, 0xfd, 0x9f, 0x47, 0x8d, 0x90, 0xef, - 0xbb, 0xac, 0xa9, 0x02, 0x83, 0x3f, 0xa0, 0x07, 0x4e, 0xe1, 0xeb, 0x8c, 0x87, 0xb6, 0x89, 0xd3, - 0xfe, 0x26, 0x6c, 0x9e, 0xeb, 0x7e, 0x63, 0x13, 0xdb, 0x27, 0xc0, 0x63, 0xf4, 0x78, 0xcf, 0x56, - 0x54, 0x0b, 0xad, 0xb9, 0x34, 0xf1, 0xbd, 0x34, 0xcc, 0x06, 0xf9, 0xf1, 0x2e, 0x3c, 0x75, 0xa1, - 0xc9, 0xdb, 0xeb, 0x55, 0x12, 0xde, 0xac, 0x92, 0xf0, 0xcf, 0x2a, 0x09, 0x7f, 0xae, 0x93, 0xe0, - 0x66, 0x9d, 0x04, 0xbf, 0xd6, 0x49, 0xf0, 0xf9, 0x55, 0x2d, 0xcc, 0xb7, 0x45, 0x49, 0x2a, 0xd5, - 0xd0, 0xbd, 0x85, 0xfa, 0xf1, 0x7f, 0xe9, 0xcc, 0xb2, 0xe5, 0x50, 0x0e, 0xed, 0xc2, 0xbc, 0xfe, - 0x17, 0x00, 0x00, 0xff, 0xff, 0x15, 0xb1, 0xb4, 0xbe, 0x31, 0x03, 0x00, 0x00, + // 471 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0x41, 0x6f, 0xd3, 0x30, + 0x14, 0xc7, 0x1b, 0x5a, 0xca, 0xe6, 0x0d, 0x90, 0xbc, 0x22, 0x42, 0x85, 0x42, 0x36, 0x40, 0x94, + 0x8b, 0xa3, 0x95, 0x1b, 0x37, 0x5a, 0xc1, 0x34, 0x89, 0xc3, 0xd4, 0xc2, 0x85, 0x4b, 0xe4, 0xa4, + 0x6e, 0xb0, 0xd4, 0xd8, 0x96, 0x9f, 0x33, 0xe8, 0xb7, 0xe0, 0x63, 0xed, 0xc0, 0x61, 0x47, 0x4e, + 0x08, 0xb5, 0x5f, 0x04, 0xc5, 0x76, 0x59, 0x0b, 0xe1, 0x14, 0xcb, 0xef, 0xff, 0x7e, 0xfe, 0xff, + 0x5f, 0x1e, 0x3a, 0x59, 0xd0, 0x4b, 0x2a, 0x98, 0x49, 0xea, 0x6f, 0xa2, 0xd9, 0x17, 0xaa, 0x67, + 0x90, 0x14, 0x4c, 0x30, 0xe0, 0x40, 0x94, 0x96, 0x46, 0xe2, 0x9e, 0xd7, 0x90, 0xfa, 0x4b, 0xbc, + 0xa6, 0xdf, 0x2b, 0x64, 0x21, 0xad, 0x20, 0xa9, 0x4f, 0x4e, 0xdb, 0x3f, 0x6e, 0xe4, 0x29, 0xaa, + 0x69, 0xe9, 0x71, 0xfd, 0xa7, 0x8d, 0x92, 0x8c, 0x02, 0x4b, 0x15, 0x5d, 0x7a, 0x51, 0xdc, 0x28, + 0xe2, 0x4a, 0xab, 0xbc, 0x11, 0x63, 0x78, 0xc9, 0x34, 0x18, 0xa9, 0x99, 0x3b, 0x7a, 0x51, 0x94, + 0x4b, 0x28, 0xa5, 0xa3, 0x27, 0x97, 0xa7, 0x19, 0x33, 0xf4, 0x34, 0xc9, 0x25, 0x17, 0xae, 0x7e, + 0xf2, 0xbd, 0x83, 0x0e, 0xcf, 0x5c, 0xd8, 0xa9, 0xa1, 0x86, 0xe1, 0xd7, 0xa8, 0xeb, 0xcc, 0x86, + 0x41, 0x1c, 0x0c, 0x0e, 0x86, 0x8f, 0x49, 0x53, 0x78, 0x72, 0x61, 0x35, 0xa3, 0xce, 0xd5, 0xcf, + 0x27, 0xad, 0x89, 0xef, 0xc0, 0x1f, 0xd1, 0x7d, 0xcd, 0xe6, 0x7c, 0xb1, 0x98, 0x38, 0xd5, 0x87, + 0x69, 0x78, 0xcb, 0x42, 0x9e, 0xef, 0x42, 0x6e, 0xbc, 0x92, 0xed, 0xb7, 0x3d, 0xed, 0x6f, 0x06, + 0x3e, 0x43, 0xfb, 0x9b, 0xe1, 0x40, 0xd8, 0x8e, 0xdb, 0x83, 0x83, 0xe1, 0xb3, 0x66, 0x57, 0x23, + 0x0a, 0xec, 0x82, 0x2e, 0x3d, 0xd4, 0xf3, 0xf6, 0x32, 0x77, 0x0b, 0x38, 0x41, 0x47, 0x76, 0x80, + 0x29, 0x54, 0x19, 0xe4, 0x9a, 0x2b, 0xc3, 0xa5, 0x80, 0xb0, 0x13, 0xb7, 0x07, 0xfb, 0x13, 0x6c, + 0x4b, 0xd3, 0xed, 0x0a, 0x7e, 0x8b, 0xee, 0x95, 0x5c, 0xa4, 0xae, 0x29, 0x97, 0x60, 0xc2, 0xdb, + 0x36, 0xcf, 0x23, 0xe2, 0xc6, 0x4a, 0x6a, 0x34, 0xf1, 0x63, 0x25, 0x63, 0xc9, 0x85, 0x7f, 0xf3, + 0xb0, 0xe4, 0xe2, 0xbc, 0xee, 0x1a, 0x4b, 0x30, 0xf8, 0x3d, 0xba, 0xeb, 0x10, 0xde, 0x67, 0xd8, + 0xb5, 0x21, 0x8e, 0x9b, 0x43, 0xd8, 0x3e, 0x97, 0x7e, 0x43, 0xe3, 0x37, 0x57, 0x80, 0x87, 0xe8, + 0xc1, 0x0e, 0x2d, 0xcd, 0x2b, 0xad, 0x99, 0x30, 0xe1, 0x9d, 0x38, 0x18, 0x74, 0x26, 0x47, 0xdb, + 0xe2, 0xb1, 0x2b, 0xe1, 0x39, 0x7a, 0xa8, 0x98, 0x98, 0x71, 0x51, 0xa4, 0x3c, 0xcb, 0x7d, 0xa0, + 0x79, 0x25, 0x66, 0x10, 0xee, 0x59, 0x2f, 0x2f, 0xff, 0xf3, 0x9b, 0x5d, 0xd3, 0x79, 0x96, 0x5b, + 0x57, 0xef, 0x2a, 0xb1, 0xf1, 0xd4, 0x53, 0xff, 0x96, 0x60, 0xf4, 0xe6, 0x6a, 0x15, 0x05, 0xd7, + 0xab, 0x28, 0xf8, 0xb5, 0x8a, 0x82, 0x6f, 0xeb, 0xa8, 0x75, 0xbd, 0x8e, 0x5a, 0x3f, 0xd6, 0x51, + 0xeb, 0xd3, 0x8b, 0x82, 0x9b, 0xcf, 0x55, 0x46, 0x72, 0x59, 0x26, 0x3b, 0x8b, 0xfb, 0xf5, 0xcf, + 0x72, 0x9b, 0xa5, 0x62, 0x90, 0x75, 0xed, 0x62, 0xbe, 0xfa, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xd2, 0xf1, 0x4f, 0x99, 0x03, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -177,6 +188,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.PendingIbcIprpcFunds) > 0 { + for iNdEx := len(m.PendingIbcIprpcFunds) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PendingIbcIprpcFunds[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } if m.IprpcRewardsCurrent != 0 { i = encodeVarintGenesis(dAtA, i, uint64(m.IprpcRewardsCurrent)) i-- @@ -296,6 +321,12 @@ func (m *GenesisState) Size() (n int) { if m.IprpcRewardsCurrent != 0 { n += 1 + sovGenesis(uint64(m.IprpcRewardsCurrent)) } + if len(m.PendingIbcIprpcFunds) > 0 { + for _, e := range m.PendingIbcIprpcFunds { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -552,6 +583,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { break } } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingIbcIprpcFunds", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PendingIbcIprpcFunds = append(m.PendingIbcIprpcFunds, PendingIbcIprpcFund{}) + if err := m.PendingIbcIprpcFunds[len(m.PendingIbcIprpcFunds)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/rewards/types/genesis_test.go b/x/rewards/types/genesis_test.go index e7745ca5ad..17b99ebaef 100644 --- a/x/rewards/types/genesis_test.go +++ b/x/rewards/types/genesis_test.go @@ -31,6 +31,20 @@ func TestGenesisState_Validate(t *testing.T) { }, valid: false, }, + { + desc: "invalid ibc iprpc funds", + genState: &types.GenesisState{ + Params: types.DefaultParams(), + RefillRewardsTS: types.DefaultGenesis().RefillRewardsTS, + BasePays: types.DefaultGenesis().BasePays, + IprpcSubscriptions: types.DefaultGenesis().IprpcSubscriptions, + MinIprpcCost: types.DefaultGenesis().MinIprpcCost, + IprpcRewards: types.DefaultGenesis().IprpcRewards, + IprpcRewardsCurrent: types.DefaultGenesis().GetIprpcRewardsCurrent(), + PendingIbcIprpcFunds: []types.PendingIbcIprpcFund{{Expiry: 0}}, + }, + valid: false, + }, // this line is used by starport scaffolding # types/genesis/testcase } { t.Run(tc.desc, func(t *testing.T) { diff --git a/x/rewards/types/ibc_iprpc.go b/x/rewards/types/ibc_iprpc.go index 8c5411cad5..35535c717e 100644 --- a/x/rewards/types/ibc_iprpc.go +++ b/x/rewards/types/ibc_iprpc.go @@ -36,10 +36,6 @@ func CreateIprpcMemo(creator string, spec string, duration uint64) (memoStr stri // IbcIprpcReceiverAddress is a temporary address that holds the funds from an IPRPC over IBC request. The funds are // then immediately transferred to the pending IPRPC pool // Note, the NewModuleAddress() function is used for convenience. The IbcIprpcReceiver is not a module account -const ( - IbcIprpcReceiver = "iprpc" -) - func IbcIprpcReceiverAddress() sdk.AccAddress { return authtypes.NewModuleAddress(IbcIprpcReceiver) } @@ -49,3 +45,35 @@ func IbcIprpcReceiverAddress() sdk.AccAddress { const ( PendingIprpcPoolName Pool = "pending_iprpc_pool" ) + +// PendingIbcIprpcFund methods and constants +const ( + IbcIprpcReceiver = "iprpc" +) + +const ( + PendingIbcIprpcFundPrefix = "PendingIbcIprpcFund/" +) + +func (piif PendingIbcIprpcFund) IsEqual(other PendingIbcIprpcFund) bool { + return piif.Index == other.Index && piif.Creator == other.Creator && piif.Spec == other.Spec && + piif.Duration == other.Duration && piif.Expiry == other.Expiry && piif.Fund.IsEqual(other.Fund) +} + +func (piif PendingIbcIprpcFund) IsEmpty() bool { + return piif.IsEqual(PendingIbcIprpcFund{}) +} + +func (piif PendingIbcIprpcFund) IsValid() bool { + return piif.Expiry > 0 && piif.Fund.IsValid() && piif.Fund.Amount.IsPositive() && piif.Duration > 0 +} + +func (piif PendingIbcIprpcFund) IsExpired(ctx sdk.Context) bool { + return uint64(ctx.BlockTime().UTC().Unix()) >= piif.Expiry +} + +const ( + NewPendingIbcIprpcFundEventName = "pending_ibc_iprpc_fund_created" + ExpiredPendingIbcIprpcFundRemovedEventName = "expired_pending_ibc_iprpc_fund_removed" + CoverIbcIprpcFundCostEventName = "cover_ibc_iprpc_fund_cost" +) diff --git a/x/rewards/types/ibc_iprpc_test.go b/x/rewards/types/ibc_iprpc_test.go index 2b993cfce2..d6b03fb6c8 100644 --- a/x/rewards/types/ibc_iprpc_test.go +++ b/x/rewards/types/ibc_iprpc_test.go @@ -3,6 +3,9 @@ package types_test import ( "testing" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + commontypes "github.com/lavanet/lava/utils/common/types" "github.com/lavanet/lava/x/rewards/types" "github.com/stretchr/testify/require" ) @@ -27,3 +30,68 @@ func TestIprpcMemoIsEqual(t *testing.T) { }) } } + +// TestPendingIbcIprpcFundIsEqual tests PendingIbcIprpcFund method: IsEqual +func TestPendingIbcIprpcFundIsEqual(t *testing.T) { + piif := types.PendingIbcIprpcFund{ + Index: 1, Creator: "creator", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10, + } + template := []struct { + name string + piif types.PendingIbcIprpcFund + isEqual bool + }{ + {"equal", types.PendingIbcIprpcFund{Index: 1, Creator: "creator", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10}, true}, + {"different index", types.PendingIbcIprpcFund{Index: 2, Creator: "creator", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10}, false}, + {"different creator", types.PendingIbcIprpcFund{Index: 1, Creator: "creator2", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10}, false}, + {"different spec", types.PendingIbcIprpcFund{Index: 1, Creator: "creator", Spec: "spec2", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10}, false}, + {"different duration", types.PendingIbcIprpcFund{Index: 1, Creator: "creator", Spec: "spec", Duration: 2, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 10}, false}, + {"different fund", types.PendingIbcIprpcFund{Index: 1, Creator: "creator", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt().AddRaw(1)), Expiry: 10}, false}, + {"different expiry", types.PendingIbcIprpcFund{Index: 1, Creator: "creator", Spec: "spec", Duration: 3, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Expiry: 12}, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.isEqual, piif.IsEqual(tt.piif)) + }) + } +} + +// TestPendingIbcIprpcFundIsEmpty tests PendingIbcIprpcFund method: IsEmpty +func TestPendingIbcIprpcFundIsEmpty(t *testing.T) { + template := []struct { + name string + piif types.PendingIbcIprpcFund + isEmpty bool + }{ + {"empty", types.PendingIbcIprpcFund{}, true}, + {"not empty", types.PendingIbcIprpcFund{Index: 1}, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.isEmpty, tt.piif.IsEmpty()) + }) + } +} + +// TestPendingIbcIprpcFundIsValid tests PendingIbcIprpcFund method: IsValid +func TestPendingIbcIprpcFundIsValid(t *testing.T) { + template := []struct { + name string + piif types.PendingIbcIprpcFund + isValid bool + }{ + {"valid", types.PendingIbcIprpcFund{Expiry: 1, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Duration: 1}, true}, + {"empty", types.PendingIbcIprpcFund{}, false}, + {"invalid expiry", types.PendingIbcIprpcFund{Expiry: 0, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Duration: 1}, false}, + {"invalid fund amount", types.PendingIbcIprpcFund{Expiry: 1, Fund: sdk.NewCoin(commontypes.TokenDenom, math.ZeroInt()), Duration: 1}, false}, + {"invalid duration", types.PendingIbcIprpcFund{Expiry: 1, Fund: sdk.NewCoin(commontypes.TokenDenom, math.OneInt()), Duration: 0}, false}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.isValid, tt.piif.IsValid()) + }) + } +} diff --git a/x/rewards/types/iprpc.pb.go b/x/rewards/types/iprpc.pb.go index f8d29aca9a..3ede0b7d18 100644 --- a/x/rewards/types/iprpc.pb.go +++ b/x/rewards/types/iprpc.pb.go @@ -190,38 +190,127 @@ func (m *IprpcMemo) GetDuration() uint64 { return 0 } +type PendingIbcIprpcFund struct { + Index uint64 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + Creator string `protobuf:"bytes,2,opt,name=creator,proto3" json:"creator,omitempty"` + Spec string `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + Duration uint64 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` + Fund types.Coin `protobuf:"bytes,5,opt,name=fund,proto3" json:"fund"` + Expiry uint64 `protobuf:"varint,6,opt,name=expiry,proto3" json:"expiry,omitempty"` +} + +func (m *PendingIbcIprpcFund) Reset() { *m = PendingIbcIprpcFund{} } +func (m *PendingIbcIprpcFund) String() string { return proto.CompactTextString(m) } +func (*PendingIbcIprpcFund) ProtoMessage() {} +func (*PendingIbcIprpcFund) Descriptor() ([]byte, []int) { + return fileDescriptor_1293618a311573f7, []int{3} +} +func (m *PendingIbcIprpcFund) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PendingIbcIprpcFund) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PendingIbcIprpcFund.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PendingIbcIprpcFund) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingIbcIprpcFund.Merge(m, src) +} +func (m *PendingIbcIprpcFund) XXX_Size() int { + return m.Size() +} +func (m *PendingIbcIprpcFund) XXX_DiscardUnknown() { + xxx_messageInfo_PendingIbcIprpcFund.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingIbcIprpcFund proto.InternalMessageInfo + +func (m *PendingIbcIprpcFund) GetIndex() uint64 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *PendingIbcIprpcFund) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *PendingIbcIprpcFund) GetSpec() string { + if m != nil { + return m.Spec + } + return "" +} + +func (m *PendingIbcIprpcFund) GetDuration() uint64 { + if m != nil { + return m.Duration + } + return 0 +} + +func (m *PendingIbcIprpcFund) GetFund() types.Coin { + if m != nil { + return m.Fund + } + return types.Coin{} +} + +func (m *PendingIbcIprpcFund) GetExpiry() uint64 { + if m != nil { + return m.Expiry + } + return 0 +} + func init() { proto.RegisterType((*IprpcReward)(nil), "lavanet.lava.rewards.IprpcReward") proto.RegisterType((*Specfund)(nil), "lavanet.lava.rewards.Specfund") proto.RegisterType((*IprpcMemo)(nil), "lavanet.lava.rewards.IprpcMemo") + proto.RegisterType((*PendingIbcIprpcFund)(nil), "lavanet.lava.rewards.PendingIbcIprpcFund") } func init() { proto.RegisterFile("lavanet/lava/rewards/iprpc.proto", fileDescriptor_1293618a311573f7) } var fileDescriptor_1293618a311573f7 = []byte{ - // 343 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xb1, 0x4e, 0xf3, 0x30, - 0x14, 0x85, 0x93, 0x34, 0xfa, 0xff, 0xd6, 0x95, 0x18, 0xac, 0x0e, 0xa1, 0x83, 0x5b, 0x75, 0xa1, - 0x0b, 0x36, 0x85, 0x27, 0xa0, 0x95, 0x90, 0x18, 0x58, 0x82, 0x58, 0x58, 0x2a, 0xc7, 0x31, 0xc5, - 0x82, 0xc6, 0x91, 0xed, 0x16, 0x98, 0x78, 0x05, 0x9e, 0x83, 0x27, 0xe9, 0xd8, 0x91, 0x09, 0x50, - 0xfb, 0x22, 0xc8, 0x8e, 0x1b, 0x15, 0x89, 0xe9, 0xda, 0x3a, 0xe7, 0x1e, 0x7f, 0xd7, 0x17, 0xf4, - 0x1f, 0xe9, 0x92, 0x16, 0xdc, 0x10, 0x5b, 0x89, 0xe2, 0x4f, 0x54, 0xe5, 0x9a, 0x88, 0x52, 0x95, - 0x0c, 0x97, 0x4a, 0x1a, 0x09, 0x3b, 0xde, 0x81, 0x6d, 0xc5, 0xde, 0xd1, 0xed, 0xcc, 0xe4, 0x4c, - 0x3a, 0x03, 0xb1, 0xa7, 0xca, 0xdb, 0x45, 0x4c, 0xea, 0xb9, 0xd4, 0x24, 0xa3, 0x9a, 0x93, 0xe5, - 0x28, 0xe3, 0x86, 0x8e, 0x08, 0x93, 0xa2, 0xa8, 0xf4, 0x41, 0x06, 0xda, 0x97, 0x36, 0x3a, 0x75, - 0x29, 0xf0, 0x00, 0x44, 0x22, 0x4f, 0xc2, 0x7e, 0x38, 0x8c, 0xd3, 0x48, 0xe4, 0x70, 0x02, 0x80, - 0x2e, 0x39, 0x9b, 0xde, 0x2d, 0x8a, 0x5c, 0x27, 0x51, 0xbf, 0x31, 0x6c, 0x9f, 0x22, 0xfc, 0xd7, - 0xfb, 0xf8, 0xba, 0xe4, 0xcc, 0xda, 0xc6, 0xf1, 0xea, 0xb3, 0x17, 0xa4, 0x2d, 0xdb, 0x77, 0x61, - 0xdb, 0x06, 0xaf, 0xa0, 0xb9, 0x13, 0x21, 0x04, 0xb1, 0x15, 0xdc, 0x13, 0xad, 0xd4, 0x9d, 0xe1, - 0x14, 0xc4, 0x56, 0xf3, 0xf1, 0x87, 0xb8, 0x42, 0xc6, 0x16, 0x19, 0x7b, 0x64, 0x3c, 0x91, 0xa2, - 0x18, 0x9f, 0xd8, 0xe4, 0xf7, 0xaf, 0xde, 0x70, 0x26, 0xcc, 0xfd, 0x22, 0xc3, 0x4c, 0xce, 0x89, - 0x9f, 0xaf, 0x2a, 0xc7, 0x3a, 0x7f, 0x20, 0xe6, 0xa5, 0xe4, 0xda, 0x35, 0xe8, 0xd4, 0x05, 0x0f, - 0x6e, 0x40, 0xcb, 0x0d, 0x79, 0xc5, 0xe7, 0x12, 0x26, 0xe0, 0x3f, 0x53, 0x9c, 0x1a, 0xa9, 0x3c, - 0xc4, 0xee, 0x5a, 0xb3, 0x45, 0x7b, 0x6c, 0x5d, 0xd0, 0xcc, 0x17, 0x8a, 0x1a, 0x21, 0x8b, 0xa4, - 0xe1, 0xbe, 0xa5, 0xbe, 0x8f, 0xcf, 0x57, 0x1b, 0x14, 0xae, 0x37, 0x28, 0xfc, 0xde, 0xa0, 0xf0, - 0x6d, 0x8b, 0x82, 0xf5, 0x16, 0x05, 0x1f, 0x5b, 0x14, 0xdc, 0x1e, 0xed, 0x01, 0xfe, 0x5a, 0xe7, - 0x73, 0xbd, 0x50, 0x47, 0x99, 0xfd, 0x73, 0x5b, 0x38, 0xfb, 0x09, 0x00, 0x00, 0xff, 0xff, 0x51, - 0x32, 0xe9, 0xcc, 0xf5, 0x01, 0x00, 0x00, + // 411 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x52, 0xb1, 0xae, 0xd3, 0x30, + 0x14, 0x4d, 0xd2, 0xbc, 0xf2, 0xea, 0x27, 0x31, 0x98, 0x0a, 0x85, 0x0e, 0x7e, 0x51, 0x16, 0xba, + 0x60, 0x53, 0xfa, 0x05, 0xb4, 0x12, 0x52, 0x07, 0x24, 0x14, 0xc4, 0xc2, 0x52, 0x25, 0xb6, 0x09, + 0x16, 0xd4, 0x8e, 0xec, 0xb4, 0xb4, 0x13, 0xbf, 0xc0, 0x77, 0xf0, 0x19, 0x4c, 0x1d, 0x3b, 0x32, + 0x01, 0x6a, 0x7f, 0x04, 0xd9, 0x71, 0xab, 0x56, 0x2a, 0x4c, 0xce, 0xd5, 0x3d, 0xf7, 0x9c, 0x73, + 0x4f, 0x2e, 0x48, 0x3f, 0x17, 0xab, 0x42, 0xf2, 0x86, 0xd8, 0x97, 0x68, 0xfe, 0xa5, 0xd0, 0xcc, + 0x10, 0x51, 0xeb, 0x9a, 0xe2, 0x5a, 0xab, 0x46, 0xc1, 0xbe, 0x47, 0x60, 0xfb, 0x62, 0x8f, 0x18, + 0xf4, 0x2b, 0x55, 0x29, 0x07, 0x20, 0xf6, 0xab, 0xc5, 0x0e, 0x10, 0x55, 0x66, 0xa1, 0x0c, 0x29, + 0x0b, 0xc3, 0xc9, 0x6a, 0x54, 0xf2, 0xa6, 0x18, 0x11, 0xaa, 0x84, 0x6c, 0xfb, 0x59, 0x09, 0xee, + 0x66, 0x96, 0x3a, 0x77, 0x2c, 0xf0, 0x21, 0x88, 0x04, 0x4b, 0xc2, 0x34, 0x1c, 0xc6, 0x79, 0x24, + 0x18, 0x9c, 0x02, 0x60, 0x6a, 0x4e, 0xe7, 0x1f, 0x96, 0x92, 0x99, 0x24, 0x4a, 0x3b, 0xc3, 0xbb, + 0x17, 0x08, 0x5f, 0xd3, 0xc7, 0x6f, 0x6b, 0x4e, 0x2d, 0x6c, 0x12, 0x6f, 0x7f, 0xdd, 0x07, 0x79, + 0xcf, 0xce, 0xbd, 0xb2, 0x63, 0xd9, 0x57, 0x70, 0x7b, 0x6c, 0x42, 0x08, 0x62, 0xdb, 0x70, 0x12, + 0xbd, 0xdc, 0x7d, 0xc3, 0x39, 0x88, 0x6d, 0xcf, 0xd3, 0x3f, 0xc1, 0xad, 0x65, 0x6c, 0x2d, 0x63, + 0x6f, 0x19, 0x4f, 0x95, 0x90, 0x93, 0xe7, 0x96, 0xf9, 0xfb, 0xef, 0xfb, 0x61, 0x25, 0x9a, 0x8f, + 0xcb, 0x12, 0x53, 0xb5, 0x20, 0x7e, 0xbf, 0xf6, 0x79, 0x66, 0xd8, 0x27, 0xd2, 0x6c, 0x6a, 0x6e, + 0xdc, 0x80, 0xc9, 0x1d, 0x71, 0xf6, 0x0e, 0xf4, 0xdc, 0x92, 0xaf, 0xf9, 0x42, 0xc1, 0x04, 0x3c, + 0xa0, 0x9a, 0x17, 0x8d, 0xd2, 0xde, 0xc4, 0xb1, 0x3c, 0x79, 0x8b, 0xce, 0xbc, 0x0d, 0xc0, 0x2d, + 0x5b, 0xea, 0xa2, 0x11, 0x4a, 0x26, 0x1d, 0x17, 0xcb, 0xa9, 0xce, 0x7e, 0x84, 0xe0, 0xd1, 0x1b, + 0x2e, 0x99, 0x90, 0xd5, 0xac, 0xa4, 0x4e, 0xc1, 0x2e, 0x0c, 0xfb, 0xe0, 0x46, 0x48, 0xc6, 0xd7, + 0x3e, 0xc7, 0xb6, 0x38, 0xd7, 0x8d, 0xae, 0xeb, 0x76, 0xfe, 0xa1, 0x1b, 0x5f, 0xea, 0xc2, 0xb1, + 0xcf, 0xeb, 0x26, 0x0d, 0xff, 0x9f, 0x57, 0xfb, 0x27, 0x1c, 0x18, 0x3e, 0x06, 0x5d, 0xbe, 0xae, + 0x85, 0xde, 0x24, 0x5d, 0x47, 0xe7, 0xab, 0xc9, 0xcb, 0xed, 0x1e, 0x85, 0xbb, 0x3d, 0x0a, 0xff, + 0xec, 0x51, 0xf8, 0xed, 0x80, 0x82, 0xdd, 0x01, 0x05, 0x3f, 0x0f, 0x28, 0x78, 0xff, 0xf4, 0x2c, + 0xe5, 0x8b, 0x9b, 0x5c, 0x9f, 0xae, 0xd2, 0x45, 0x5d, 0x76, 0xdd, 0x29, 0x8d, 0xff, 0x06, 0x00, + 0x00, 0xff, 0xff, 0xae, 0x70, 0x26, 0xfe, 0xba, 0x02, 0x00, 0x00, } func (m *IprpcReward) Marshal() (dAtA []byte, err error) { @@ -352,6 +441,68 @@ func (m *IprpcMemo) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PendingIbcIprpcFund) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PendingIbcIprpcFund) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PendingIbcIprpcFund) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Expiry != 0 { + i = encodeVarintIprpc(dAtA, i, uint64(m.Expiry)) + i-- + dAtA[i] = 0x30 + } + { + size, err := m.Fund.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintIprpc(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if m.Duration != 0 { + i = encodeVarintIprpc(dAtA, i, uint64(m.Duration)) + i-- + dAtA[i] = 0x20 + } + if len(m.Spec) > 0 { + i -= len(m.Spec) + copy(dAtA[i:], m.Spec) + i = encodeVarintIprpc(dAtA, i, uint64(len(m.Spec))) + i-- + dAtA[i] = 0x1a + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintIprpc(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0x12 + } + if m.Index != 0 { + i = encodeVarintIprpc(dAtA, i, uint64(m.Index)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintIprpc(dAtA []byte, offset int, v uint64) int { offset -= sovIprpc(v) base := offset @@ -420,6 +571,34 @@ func (m *IprpcMemo) Size() (n int) { return n } +func (m *PendingIbcIprpcFund) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Index != 0 { + n += 1 + sovIprpc(uint64(m.Index)) + } + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovIprpc(uint64(l)) + } + l = len(m.Spec) + if l > 0 { + n += 1 + l + sovIprpc(uint64(l)) + } + if m.Duration != 0 { + n += 1 + sovIprpc(uint64(m.Duration)) + } + l = m.Fund.Size() + n += 1 + l + sovIprpc(uint64(l)) + if m.Expiry != 0 { + n += 1 + sovIprpc(uint64(m.Expiry)) + } + return n +} + func sovIprpc(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -778,6 +957,210 @@ func (m *IprpcMemo) Unmarshal(dAtA []byte) error { } return nil } +func (m *PendingIbcIprpcFund) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PendingIbcIprpcFund: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PendingIbcIprpcFund: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthIprpc + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthIprpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Spec", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthIprpc + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthIprpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Spec = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Duration", wireType) + } + m.Duration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Duration |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fund", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthIprpc + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthIprpc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Fund.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Expiry", wireType) + } + m.Expiry = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowIprpc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Expiry |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipIprpc(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthIprpc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipIprpc(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/rewards/types/message_cover_ibc_iprpc_fund_cost.go b/x/rewards/types/message_cover_ibc_iprpc_fund_cost.go new file mode 100644 index 0000000000..6e6165ddf5 --- /dev/null +++ b/x/rewards/types/message_cover_ibc_iprpc_fund_cost.go @@ -0,0 +1,48 @@ +package types + +import ( + sdkerrors "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + legacyerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const TypeMsgCoverIbcIprpcFundCost = "cover_ibc_iprpc_fund_cost" + +var _ sdk.Msg = &MsgCoverIbcIprpcFundCost{} + +func NewMsgCoverIbcIprpcFundCost(creator string, index uint64) *MsgCoverIbcIprpcFundCost { + return &MsgCoverIbcIprpcFundCost{ + Creator: creator, + Index: index, + } +} + +func (msg *MsgCoverIbcIprpcFundCost) Route() string { + return RouterKey +} + +func (msg *MsgCoverIbcIprpcFundCost) Type() string { + return TypeMsgCoverIbcIprpcFundCost +} + +func (msg *MsgCoverIbcIprpcFundCost) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgCoverIbcIprpcFundCost) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgCoverIbcIprpcFundCost) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return sdkerrors.Wrapf(legacyerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + + return nil +} diff --git a/x/rewards/types/message_cover_ibc_iprpc_fund_cost_test.go b/x/rewards/types/message_cover_ibc_iprpc_fund_cost_test.go new file mode 100644 index 0000000000..cb0245e406 --- /dev/null +++ b/x/rewards/types/message_cover_ibc_iprpc_fund_cost_test.go @@ -0,0 +1,43 @@ +package types + +import ( + "testing" + + "github.com/lavanet/lava/testutil/sample" + "github.com/stretchr/testify/require" +) + +func TestCoverIbcIprpcFundCost_ValidateBasic(t *testing.T) { + tests := []struct { + name string + msg MsgCoverIbcIprpcFundCost + valid bool + }{ + { + name: "valid", + msg: MsgCoverIbcIprpcFundCost{ + Creator: sample.AccAddress(), + Index: 1, + }, + valid: true, + }, + { + name: "invalid creator address", + msg: MsgCoverIbcIprpcFundCost{ + Creator: "invalid_address", + Index: 1, + }, + valid: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.valid { + require.NoError(t, err) + return + } + require.Error(t, err) + }) + } +} diff --git a/x/rewards/types/params.go b/x/rewards/types/params.go index c085a97409..58d6309e51 100644 --- a/x/rewards/types/params.go +++ b/x/rewards/types/params.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "time" sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" @@ -40,6 +41,11 @@ var ( DefaultValidatorsSubscriptionParticipation sdk.Dec = sdk.NewDecWithPrec(5, 2) // 0.05 ) +var ( + KeyIbcIprpcExpiration = []byte("IbcIprpcExpiration") + DefaultIbcIprpcExpiration time.Duration = time.Hour * 24 * 30 * 3 // 3 months +) + // ParamKeyTable the param key table for launch module func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) @@ -53,6 +59,7 @@ func NewParams( leftoverBurnRate sdk.Dec, maxRewardBoost uint64, validatorsSubscriptionParticipation sdk.Dec, + ibcIprpcExpiration time.Duration, ) Params { return Params{ MinBondedTarget: minBondedTarget, @@ -61,6 +68,7 @@ func NewParams( LeftoverBurnRate: leftoverBurnRate, MaxRewardBoost: maxRewardBoost, ValidatorsSubscriptionParticipation: validatorsSubscriptionParticipation, + IbcIprpcExpiration: ibcIprpcExpiration, } } @@ -73,6 +81,7 @@ func DefaultParams() Params { DefaultLeftOverBurnRate, DefaultMaxRewardBoost, DefaultValidatorsSubscriptionParticipation, + DefaultIbcIprpcExpiration, ) } @@ -85,6 +94,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { paramtypes.NewParamSetPair(KeyLeftoverBurnRate, &p.LeftoverBurnRate, validateDec), paramtypes.NewParamSetPair(KeyMaxRewardBoost, &p.MaxRewardBoost, validateuint64), paramtypes.NewParamSetPair(KeyValidatorsSubscriptionParticipation, &p.ValidatorsSubscriptionParticipation, validateDec), + paramtypes.NewParamSetPair(KeyIbcIprpcExpiration, &p.IbcIprpcExpiration, validateDuration), } } @@ -118,6 +128,10 @@ func (p Params) Validate() error { return fmt.Errorf("invalid ValidatorsSubscriptionParticipation. Error: %s", err.Error()) } + if err := validateDuration(p.IbcIprpcExpiration); err != nil { + return fmt.Errorf("invalid IbcIprpcExpiration. Error: %s", err.Error()) + } + return nil } @@ -149,3 +163,16 @@ func validateuint64(v interface{}) error { return nil } + +func validateDuration(v interface{}) error { + param, ok := v.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", v) + } + + if param.Seconds() == float64(0) { + return fmt.Errorf("invalid duration parameter") + } + + return nil +} diff --git a/x/rewards/types/params.pb.go b/x/rewards/types/params.pb.go index 248deb19ec..c436ea6649 100644 --- a/x/rewards/types/params.pb.go +++ b/x/rewards/types/params.pb.go @@ -8,15 +8,19 @@ import ( github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/durationpb" io "io" math "math" math_bits "math/bits" + time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. @@ -32,6 +36,7 @@ type Params struct { LeftoverBurnRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=leftover_burn_rate,json=leftoverBurnRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"leftover_burn_rate" yaml:"leftover_burn_rate"` MaxRewardBoost uint64 `protobuf:"varint,5,opt,name=max_reward_boost,json=maxRewardBoost,proto3" json:"max_reward_boost,omitempty"` ValidatorsSubscriptionParticipation github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=validators_subscription_participation,json=validatorsSubscriptionParticipation,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"validators_subscription_participation" yaml:"validators_subscription_participation"` + IbcIprpcExpiration time.Duration `protobuf:"bytes,7,opt,name=ibc_iprpc_expiration,json=ibcIprpcExpiration,proto3,stdduration" json:"ibc_iprpc_expiration" yaml:"ibc_iprpc_expiration"` } func (m *Params) Reset() { *m = Params{} } @@ -73,6 +78,13 @@ func (m *Params) GetMaxRewardBoost() uint64 { return 0 } +func (m *Params) GetIbcIprpcExpiration() time.Duration { + if m != nil { + return m.IbcIprpcExpiration + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "lavanet.lava.rewards.Params") } @@ -80,34 +92,39 @@ func init() { func init() { proto.RegisterFile("lavanet/lava/rewards/params.proto", fileDescriptor_12687c5fbcde5c39) } var fileDescriptor_12687c5fbcde5c39 = []byte{ - // 418 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xbf, 0x8e, 0x94, 0x50, - 0x14, 0xc6, 0x41, 0x67, 0x27, 0xd9, 0x5b, 0xe8, 0x2e, 0xd9, 0x02, 0x2d, 0x60, 0xc5, 0xa8, 0x53, - 0x28, 0x14, 0x76, 0xdb, 0x89, 0xc6, 0x42, 0x9b, 0x0d, 0x5a, 0x59, 0x78, 0x73, 0x80, 0xbb, 0x78, - 0x23, 0x70, 0xc8, 0xbd, 0x17, 0x86, 0x79, 0x0b, 0x4b, 0x4b, 0x9f, 0xc1, 0xda, 0x07, 0xd8, 0x72, - 0x4a, 0x63, 0x31, 0x31, 0x33, 0x6f, 0xe0, 0x13, 0x18, 0x2e, 0xe8, 0xfc, 0x6b, 0x9c, 0x58, 0x9d, - 0xc3, 0x97, 0x8f, 0xef, 0xf7, 0x05, 0x72, 0xc8, 0xbd, 0x1c, 0x1a, 0x28, 0x99, 0x0a, 0xba, 0x19, - 0x08, 0x36, 0x05, 0x91, 0xca, 0xa0, 0x02, 0x01, 0x85, 0xf4, 0x2b, 0x81, 0x0a, 0xad, 0xb3, 0xc1, - 0xe2, 0x77, 0xd3, 0x1f, 0x2c, 0x77, 0xcf, 0x32, 0xcc, 0x50, 0x1b, 0x82, 0x6e, 0xeb, 0xbd, 0xde, - 0xb7, 0x23, 0x32, 0xbe, 0xd4, 0x2f, 0x5b, 0x0d, 0x39, 0x2d, 0x78, 0x49, 0x63, 0x2c, 0x53, 0x96, - 0x52, 0x05, 0x22, 0x63, 0xca, 0x36, 0xcf, 0xcd, 0xc9, 0x71, 0xf8, 0xea, 0x7a, 0xe1, 0x1a, 0x3f, - 0x16, 0xee, 0xc3, 0x8c, 0xab, 0x0f, 0x75, 0xec, 0x27, 0x58, 0x04, 0x09, 0xca, 0x02, 0xe5, 0x30, - 0x9e, 0xc8, 0xf4, 0x63, 0xa0, 0x66, 0x15, 0x93, 0xfe, 0x0b, 0x96, 0xfc, 0x5a, 0xb8, 0xf6, 0x0c, - 0x8a, 0xfc, 0xc2, 0xdb, 0x0b, 0xf4, 0xa2, 0xdb, 0x05, 0x2f, 0x43, 0x2d, 0xbd, 0xd5, 0x8a, 0xe6, - 0x42, 0xbb, 0xc3, 0xbd, 0xf1, 0x9f, 0xdc, 0xdd, 0xc0, 0x8e, 0x0b, 0xed, 0x16, 0x37, 0x26, 0x24, - 0xc7, 0x29, 0xbd, 0x82, 0x44, 0xa1, 0xb0, 0x6f, 0x6a, 0xe0, 0xf3, 0x83, 0x81, 0xa7, 0x3d, 0x70, - 0x9d, 0xe4, 0x45, 0xc7, 0x39, 0x4e, 0x5f, 0xea, 0xdd, 0x9a, 0x11, 0x2b, 0x67, 0x57, 0x0a, 0x1b, - 0x26, 0x68, 0x5c, 0x8b, 0x92, 0x0a, 0x50, 0xcc, 0x1e, 0x69, 0xd6, 0xeb, 0x83, 0x59, 0x77, 0x06, - 0xd6, 0x5e, 0xa2, 0x17, 0x9d, 0xfc, 0x11, 0xc3, 0x5a, 0x94, 0x11, 0x28, 0x66, 0x4d, 0xc8, 0x49, - 0xf7, 0x15, 0xfa, 0xdf, 0x4f, 0x63, 0x44, 0xa9, 0xec, 0xa3, 0x73, 0x73, 0x32, 0x8a, 0x6e, 0x15, - 0xd0, 0x46, 0x5a, 0x0e, 0x3b, 0xd5, 0xfa, 0x6a, 0x92, 0x07, 0x0d, 0xe4, 0x3c, 0x05, 0x85, 0x42, - 0x52, 0x59, 0xc7, 0x32, 0x11, 0xbc, 0x52, 0x1c, 0x4b, 0x5a, 0x81, 0x50, 0x3c, 0xe1, 0x15, 0x74, - 0x4f, 0xf6, 0x58, 0x17, 0x7f, 0x7f, 0x70, 0xf1, 0xc7, 0x7d, 0xf1, 0x7f, 0x82, 0x78, 0xd1, 0xfd, - 0xb5, 0xef, 0xcd, 0x86, 0xed, 0x72, 0xd3, 0x75, 0x31, 0xfa, 0xfc, 0xc5, 0x35, 0xc2, 0x67, 0xd7, - 0x4b, 0xc7, 0x9c, 0x2f, 0x1d, 0xf3, 0xe7, 0xd2, 0x31, 0x3f, 0xad, 0x1c, 0x63, 0xbe, 0x72, 0x8c, - 0xef, 0x2b, 0xc7, 0x78, 0xf7, 0x68, 0xa3, 0xdc, 0xd6, 0xc9, 0xb4, 0x7f, 0x8f, 0x46, 0x37, 0x8c, - 0xc7, 0xfa, 0x10, 0x9e, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xed, 0xaf, 0xe7, 0x4f, 0x59, 0x03, - 0x00, 0x00, + // 497 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xb1, 0x6e, 0xd3, 0x40, + 0x1c, 0xc6, 0x6d, 0x08, 0x41, 0x35, 0x12, 0xb4, 0x56, 0x06, 0xb7, 0x48, 0x76, 0x30, 0x82, 0x66, + 0x00, 0x5b, 0x82, 0xad, 0x1b, 0xa6, 0x20, 0x01, 0x4b, 0x65, 0x98, 0x18, 0x38, 0xdd, 0xd9, 0x17, + 0x73, 0xc2, 0xf6, 0xdf, 0xba, 0x3b, 0x27, 0xce, 0x5b, 0x30, 0x76, 0xe4, 0x19, 0xfa, 0x14, 0x1d, + 0x3b, 0x22, 0x86, 0x80, 0x92, 0x37, 0xe8, 0x13, 0xa0, 0x3b, 0xbb, 0x6d, 0xda, 0x32, 0x10, 0x75, + 0x3a, 0xfb, 0x7f, 0x9f, 0xbe, 0xdf, 0xa7, 0xbf, 0xbe, 0xb3, 0x1e, 0xe5, 0x78, 0x82, 0x4b, 0x2a, + 0x43, 0x75, 0x86, 0x9c, 0x4e, 0x31, 0x4f, 0x45, 0x58, 0x61, 0x8e, 0x0b, 0x11, 0x54, 0x1c, 0x24, + 0xd8, 0x83, 0x4e, 0x12, 0xa8, 0x33, 0xe8, 0x24, 0x3b, 0x83, 0x0c, 0x32, 0xd0, 0x82, 0x50, 0x7d, + 0xb5, 0xda, 0x1d, 0x37, 0x03, 0xc8, 0x72, 0x1a, 0xea, 0x3f, 0x52, 0x8f, 0xc3, 0xb4, 0xe6, 0x58, + 0x32, 0x28, 0xdb, 0x7b, 0xff, 0xa8, 0x6f, 0xf5, 0x0f, 0xb4, 0xb9, 0x3d, 0xb1, 0xb6, 0x0a, 0x56, + 0x22, 0x02, 0x65, 0x4a, 0x53, 0x24, 0x31, 0xcf, 0xa8, 0x74, 0xcc, 0xa1, 0x39, 0xda, 0x88, 0xde, + 0x1f, 0xcf, 0x3d, 0xe3, 0xd7, 0xdc, 0x7b, 0x9a, 0x31, 0xf9, 0xb5, 0x26, 0x41, 0x02, 0x45, 0x98, + 0x80, 0x28, 0x40, 0x74, 0xc7, 0x73, 0x91, 0x7e, 0x0b, 0xe5, 0xac, 0xa2, 0x22, 0xd8, 0xa7, 0xc9, + 0xe9, 0xdc, 0x73, 0x66, 0xb8, 0xc8, 0xf7, 0xfc, 0x6b, 0x86, 0x7e, 0xfc, 0xa0, 0x60, 0x65, 0xa4, + 0x47, 0x9f, 0xf4, 0x44, 0x73, 0x71, 0x73, 0x85, 0x7b, 0xeb, 0x86, 0xdc, 0xab, 0x86, 0x8a, 0x8b, + 0x9b, 0x4b, 0x5c, 0x62, 0x59, 0x39, 0x4c, 0xd1, 0x18, 0x27, 0x12, 0xb8, 0x73, 0x5b, 0x03, 0x5f, + 0xaf, 0x0d, 0xdc, 0x6a, 0x81, 0x17, 0x4e, 0x7e, 0xbc, 0x91, 0xc3, 0xf4, 0xad, 0xfe, 0xb6, 0x67, + 0x96, 0x9d, 0xd3, 0xb1, 0x84, 0x09, 0xe5, 0x88, 0xd4, 0xbc, 0x44, 0x1c, 0x4b, 0xea, 0xf4, 0x34, + 0xeb, 0xc3, 0xda, 0xac, 0xed, 0x8e, 0x75, 0xcd, 0xd1, 0x8f, 0x37, 0xcf, 0x86, 0x51, 0xcd, 0xcb, + 0x18, 0x4b, 0x6a, 0x8f, 0xac, 0x4d, 0xb5, 0x85, 0xb6, 0x1e, 0x88, 0x00, 0x08, 0xe9, 0xdc, 0x19, + 0x9a, 0xa3, 0x5e, 0x7c, 0xbf, 0xc0, 0x4d, 0xac, 0xc7, 0x91, 0x9a, 0xda, 0x47, 0xa6, 0xf5, 0x64, + 0x82, 0x73, 0x96, 0x62, 0x09, 0x5c, 0x20, 0x51, 0x13, 0x91, 0x70, 0x56, 0xa9, 0x96, 0xa0, 0x0a, + 0x73, 0xc9, 0x12, 0x56, 0xe9, 0xce, 0x38, 0x7d, 0x1d, 0xfc, 0xcb, 0xda, 0xc1, 0x9f, 0xb5, 0xc1, + 0xff, 0x0b, 0xe2, 0xc7, 0x8f, 0x2f, 0x74, 0x1f, 0x57, 0x64, 0x07, 0xab, 0x2a, 0x5b, 0x5a, 0x03, + 0x46, 0x12, 0xc4, 0x2a, 0x5e, 0x25, 0x88, 0x36, 0x15, 0x6b, 0x6b, 0xed, 0xdc, 0x1d, 0x9a, 0xa3, + 0x7b, 0x2f, 0xb6, 0x83, 0xb6, 0xf7, 0xc1, 0x59, 0xef, 0x83, 0xfd, 0xae, 0xf7, 0xd1, 0xae, 0x4a, + 0x7f, 0x3a, 0xf7, 0x1e, 0xb6, 0x99, 0xfe, 0x65, 0xe2, 0x1f, 0xfe, 0xf6, 0xcc, 0xd8, 0x66, 0x24, + 0x79, 0xa7, 0x6e, 0xde, 0x9c, 0x5f, 0xec, 0xf5, 0x0e, 0x7f, 0x78, 0x46, 0xf4, 0xea, 0x78, 0xe1, + 0x9a, 0x27, 0x0b, 0xd7, 0xfc, 0xb3, 0x70, 0xcd, 0xef, 0x4b, 0xd7, 0x38, 0x59, 0xba, 0xc6, 0xcf, + 0xa5, 0x6b, 0x7c, 0xde, 0x5d, 0x59, 0xc9, 0xa5, 0x87, 0xdc, 0x9c, 0x3f, 0x65, 0xbd, 0x17, 0xd2, + 0xd7, 0xc1, 0x5e, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x76, 0x3d, 0xbd, 0xec, 0xef, 0x03, 0x00, + 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -130,6 +147,14 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.IbcIprpcExpiration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.IbcIprpcExpiration):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintParams(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x3a { size := m.ValidatorsSubscriptionParticipation.Size() i -= size @@ -218,6 +243,8 @@ func (m *Params) Size() (n int) { } l = m.ValidatorsSubscriptionParticipation.Size() n += 1 + l + sovParams(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.IbcIprpcExpiration) + n += 1 + l + sovParams(uint64(l)) return n } @@ -445,6 +472,39 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IbcIprpcExpiration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.IbcIprpcExpiration, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/rewards/types/query.pb.go b/x/rewards/types/query.pb.go index dfd1109e68..893b6314d8 100644 --- a/x/rewards/types/query.pb.go +++ b/x/rewards/types/query.pb.go @@ -800,6 +800,148 @@ func (m *QueryIprpcSpecRewardResponse) GetCurrentMonthId() uint64 { return 0 } +// QueryPendingIbcIprpcFundsRequest is request type for the Query/PendingIbcIprpcFund RPC method. +type QueryPendingIbcIprpcFundsRequest struct { + Filter string `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (m *QueryPendingIbcIprpcFundsRequest) Reset() { *m = QueryPendingIbcIprpcFundsRequest{} } +func (m *QueryPendingIbcIprpcFundsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPendingIbcIprpcFundsRequest) ProtoMessage() {} +func (*QueryPendingIbcIprpcFundsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_15bce9a904340007, []int{16} +} +func (m *QueryPendingIbcIprpcFundsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingIbcIprpcFundsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingIbcIprpcFundsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingIbcIprpcFundsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingIbcIprpcFundsRequest.Merge(m, src) +} +func (m *QueryPendingIbcIprpcFundsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingIbcIprpcFundsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingIbcIprpcFundsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingIbcIprpcFundsRequest proto.InternalMessageInfo + +func (m *QueryPendingIbcIprpcFundsRequest) GetFilter() string { + if m != nil { + return m.Filter + } + return "" +} + +type PendingIbcIprpcFundInfo struct { + PendingIbcIprpcFund PendingIbcIprpcFund `protobuf:"bytes,1,opt,name=pending_ibc_iprpc_fund,json=pendingIbcIprpcFund,proto3" json:"pending_ibc_iprpc_fund"` + Cost types.Coin `protobuf:"bytes,2,opt,name=cost,proto3" json:"cost"` +} + +func (m *PendingIbcIprpcFundInfo) Reset() { *m = PendingIbcIprpcFundInfo{} } +func (m *PendingIbcIprpcFundInfo) String() string { return proto.CompactTextString(m) } +func (*PendingIbcIprpcFundInfo) ProtoMessage() {} +func (*PendingIbcIprpcFundInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_15bce9a904340007, []int{17} +} +func (m *PendingIbcIprpcFundInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PendingIbcIprpcFundInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PendingIbcIprpcFundInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PendingIbcIprpcFundInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingIbcIprpcFundInfo.Merge(m, src) +} +func (m *PendingIbcIprpcFundInfo) XXX_Size() int { + return m.Size() +} +func (m *PendingIbcIprpcFundInfo) XXX_DiscardUnknown() { + xxx_messageInfo_PendingIbcIprpcFundInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingIbcIprpcFundInfo proto.InternalMessageInfo + +func (m *PendingIbcIprpcFundInfo) GetPendingIbcIprpcFund() PendingIbcIprpcFund { + if m != nil { + return m.PendingIbcIprpcFund + } + return PendingIbcIprpcFund{} +} + +func (m *PendingIbcIprpcFundInfo) GetCost() types.Coin { + if m != nil { + return m.Cost + } + return types.Coin{} +} + +// QueryPendingIbcIprpcFundsResponse is response type for the Query/PendingIbcIprpcFund RPC method. +type QueryPendingIbcIprpcFundsResponse struct { + PendingIbcIprpcFundsInfo []PendingIbcIprpcFundInfo `protobuf:"bytes,1,rep,name=pending_ibc_iprpc_funds_info,json=pendingIbcIprpcFundsInfo,proto3" json:"pending_ibc_iprpc_funds_info"` +} + +func (m *QueryPendingIbcIprpcFundsResponse) Reset() { *m = QueryPendingIbcIprpcFundsResponse{} } +func (m *QueryPendingIbcIprpcFundsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPendingIbcIprpcFundsResponse) ProtoMessage() {} +func (*QueryPendingIbcIprpcFundsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15bce9a904340007, []int{18} +} +func (m *QueryPendingIbcIprpcFundsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingIbcIprpcFundsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingIbcIprpcFundsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingIbcIprpcFundsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingIbcIprpcFundsResponse.Merge(m, src) +} +func (m *QueryPendingIbcIprpcFundsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingIbcIprpcFundsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingIbcIprpcFundsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingIbcIprpcFundsResponse proto.InternalMessageInfo + +func (m *QueryPendingIbcIprpcFundsResponse) GetPendingIbcIprpcFundsInfo() []PendingIbcIprpcFundInfo { + if m != nil { + return m.PendingIbcIprpcFundsInfo + } + return nil +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "lavanet.lava.rewards.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "lavanet.lava.rewards.QueryParamsResponse") @@ -817,78 +959,89 @@ func init() { proto.RegisterType((*QueryIprpcProviderRewardEstimationResponse)(nil), "lavanet.lava.rewards.QueryIprpcProviderRewardEstimationResponse") proto.RegisterType((*QueryIprpcSpecRewardRequest)(nil), "lavanet.lava.rewards.QueryIprpcSpecRewardRequest") proto.RegisterType((*QueryIprpcSpecRewardResponse)(nil), "lavanet.lava.rewards.QueryIprpcSpecRewardResponse") + proto.RegisterType((*QueryPendingIbcIprpcFundsRequest)(nil), "lavanet.lava.rewards.QueryPendingIbcIprpcFundsRequest") + proto.RegisterType((*PendingIbcIprpcFundInfo)(nil), "lavanet.lava.rewards.PendingIbcIprpcFundInfo") + proto.RegisterType((*QueryPendingIbcIprpcFundsResponse)(nil), "lavanet.lava.rewards.QueryPendingIbcIprpcFundsResponse") } func init() { proto.RegisterFile("lavanet/lava/rewards/query.proto", fileDescriptor_15bce9a904340007) } var fileDescriptor_15bce9a904340007 = []byte{ - // 1041 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcf, 0x6f, 0xdc, 0x44, - 0x14, 0x8e, 0xf3, 0x3b, 0x2f, 0x6d, 0x80, 0x49, 0xa4, 0x6e, 0x9c, 0x74, 0x9b, 0x9a, 0xa0, 0x6c, - 0x23, 0xc5, 0x4e, 0x82, 0x54, 0x10, 0x08, 0x28, 0x09, 0x3f, 0x14, 0xa9, 0x48, 0xad, 0x97, 0x13, - 0x17, 0x6b, 0xd6, 0x3b, 0xbb, 0x6b, 0xd5, 0xf6, 0x38, 0x9e, 0xd9, 0x84, 0x0a, 0x2a, 0x24, 0x10, - 0x07, 0x24, 0x0e, 0x48, 0x48, 0x88, 0x2b, 0x12, 0x27, 0x4e, 0xfc, 0x19, 0x3d, 0x56, 0xe2, 0xc2, - 0x09, 0x50, 0xc2, 0x5f, 0xc0, 0x5f, 0x80, 0xe6, 0xcd, 0x78, 0xb3, 0x9b, 0x38, 0x9b, 0x6d, 0x4f, - 0x6b, 0xcf, 0x7c, 0xef, 0x7d, 0xdf, 0xf7, 0xe6, 0xcd, 0x5b, 0xc3, 0x5a, 0x4c, 0x8f, 0x68, 0xca, - 0xa4, 0xa7, 0x7e, 0xbd, 0x9c, 0x1d, 0xd3, 0xbc, 0x29, 0xbc, 0xc3, 0x2e, 0xcb, 0x1f, 0xbb, 0x59, - 0xce, 0x25, 0x27, 0x4b, 0x06, 0xe1, 0xaa, 0x5f, 0xd7, 0x20, 0xec, 0xa5, 0x36, 0x6f, 0x73, 0x04, - 0x78, 0xea, 0x49, 0x63, 0xed, 0xd5, 0x36, 0xe7, 0xed, 0x98, 0x79, 0x34, 0x8b, 0x3c, 0x9a, 0xa6, - 0x5c, 0x52, 0x19, 0xf1, 0x54, 0x98, 0xdd, 0xcd, 0x90, 0x8b, 0x84, 0x0b, 0xaf, 0x41, 0x05, 0xd3, - 0x14, 0xde, 0xd1, 0x4e, 0x83, 0x49, 0xba, 0xe3, 0x65, 0xb4, 0x1d, 0xa5, 0x08, 0x36, 0xd8, 0xdb, - 0xa5, 0xba, 0x32, 0x9a, 0xd3, 0xa4, 0x48, 0x57, 0x2e, 0x3d, 0xca, 0xf2, 0x2c, 0x34, 0x88, 0x6a, - 0x3f, 0x61, 0x41, 0x15, 0xf2, 0xc8, 0x90, 0x38, 0x4b, 0x40, 0x1e, 0x2a, 0x19, 0x0f, 0x30, 0xad, - 0xcf, 0x0e, 0xbb, 0x4c, 0x48, 0xe7, 0x21, 0x2c, 0x0e, 0xac, 0x8a, 0x8c, 0xa7, 0x82, 0x91, 0xb7, - 0x60, 0x5a, 0xd3, 0x57, 0xac, 0x35, 0xab, 0x36, 0xbf, 0xbb, 0xea, 0x96, 0x15, 0xc6, 0xd5, 0x51, - 0x7b, 0x93, 0x4f, 0xff, 0xba, 0x35, 0xe6, 0x9b, 0x08, 0x67, 0x11, 0x5e, 0xd1, 0x29, 0x39, 0x8f, - 0x7b, 0x3c, 0xdf, 0x5a, 0x30, 0xab, 0x16, 0x0e, 0xd2, 0x16, 0x27, 0x04, 0x26, 0x53, 0x9a, 0x30, - 0xcc, 0x3d, 0xe7, 0xe3, 0x33, 0x61, 0x30, 0xd3, 0xa0, 0x31, 0x4d, 0x43, 0x56, 0x19, 0x5f, 0x9b, - 0xa8, 0xcd, 0xef, 0x2e, 0xbb, 0xda, 0x90, 0xab, 0x0c, 0xb9, 0xc6, 0x90, 0xbb, 0xcf, 0xa3, 0x74, - 0x6f, 0x5b, 0xf1, 0xfd, 0xf6, 0xf7, 0xad, 0x5a, 0x3b, 0x92, 0x9d, 0x6e, 0xc3, 0x0d, 0x79, 0xe2, - 0x19, 0xf7, 0xfa, 0x67, 0x4b, 0x34, 0x1f, 0x79, 0xf2, 0x71, 0xc6, 0x04, 0x06, 0x08, 0xbf, 0xc8, - 0xed, 0xfc, 0x67, 0x15, 0x65, 0xd0, 0xea, 0x7a, 0x7e, 0xa7, 0x32, 0xb5, 0x50, 0xb1, 0x90, 0xbb, - 0x7a, 0x89, 0x5d, 0x63, 0xc0, 0x18, 0xd6, 0x21, 0x64, 0x1d, 0x16, 0x64, 0x94, 0xb0, 0x40, 0xf2, - 0x20, 0x67, 0xad, 0x28, 0x8e, 0x2b, 0xe3, 0x6b, 0x56, 0x6d, 0xc2, 0xbf, 0xa6, 0x56, 0x3f, 0xe5, - 0x3e, 0xae, 0x91, 0xb7, 0xc1, 0x66, 0x42, 0x46, 0x09, 0x95, 0xac, 0x19, 0x34, 0x62, 0x1e, 0x3e, - 0x12, 0x7d, 0x11, 0x13, 0x18, 0x71, 0xa3, 0x87, 0xd8, 0x43, 0x40, 0x2f, 0xf8, 0x1d, 0x58, 0xa1, - 0x71, 0xcc, 0x43, 0x6c, 0x9a, 0x40, 0xd1, 0x06, 0x09, 0x4f, 0x65, 0x47, 0x04, 0x31, 0x6b, 0xc9, - 0xca, 0x24, 0x46, 0x57, 0xce, 0x20, 0x4a, 0xe8, 0x27, 0x08, 0xb8, 0xcf, 0x5a, 0xd2, 0x59, 0x86, - 0x1b, 0xe8, 0x19, 0xb3, 0xfa, 0x68, 0xa6, 0x38, 0x97, 0x3a, 0x54, 0x2e, 0x6e, 0x99, 0xa2, 0xbc, - 0x01, 0xd3, 0xda, 0xb9, 0x69, 0x82, 0x21, 0x27, 0x62, 0x3a, 0x40, 0xc3, 0x9d, 0x15, 0x58, 0xc6, - 0xa4, 0xf5, 0x0e, 0x3f, 0x3e, 0x50, 0x2d, 0xfa, 0x01, 0x95, 0xb4, 0x60, 0xfc, 0xce, 0x02, 0xbb, - 0x6c, 0xb7, 0x77, 0x12, 0xb3, 0x49, 0x94, 0x06, 0x21, 0x17, 0x72, 0x54, 0xda, 0x99, 0x24, 0x4a, - 0xf7, 0xb9, 0x90, 0xc4, 0x83, 0x45, 0xbc, 0x11, 0x81, 0xe8, 0x36, 0x44, 0x98, 0x47, 0x19, 0x5e, - 0x48, 0xec, 0xa7, 0x39, 0x9f, 0xe0, 0x56, 0xbd, 0x7f, 0xc7, 0xa9, 0x1b, 0x29, 0x0f, 0x72, 0x7e, - 0x14, 0x35, 0x59, 0x3e, 0x50, 0x1b, 0xb2, 0x0c, 0xb3, 0x61, 0x87, 0x46, 0x69, 0x10, 0x35, 0x4d, - 0xab, 0xce, 0xe0, 0xfb, 0x41, 0x93, 0xd8, 0x30, 0x9b, 0x99, 0x18, 0x3c, 0xed, 0x39, 0xbf, 0xf7, - 0xee, 0x7c, 0x09, 0xa0, 0xf3, 0x60, 0xaf, 0xbf, 0x58, 0x12, 0x55, 0x7b, 0x9a, 0xf0, 0x6e, 0x2a, - 0xb1, 0x35, 0x46, 0xa9, 0xbd, 0x86, 0x3b, 0x01, 0xac, 0x94, 0x5a, 0x32, 0xe5, 0xbd, 0x07, 0x33, - 0xa6, 0x9b, 0x4d, 0xab, 0xaf, 0x95, 0xb7, 0xfa, 0x99, 0x83, 0xa2, 0xc8, 0x66, 0xc7, 0xf9, 0x18, - 0xee, 0x20, 0x01, 0x1e, 0xdd, 0x20, 0xcb, 0x87, 0xba, 0x7d, 0x23, 0x9e, 0x16, 0x25, 0xec, 0xb7, - 0x68, 0x9d, 0xab, 0xd3, 0x21, 0x6c, 0x8e, 0x92, 0xc8, 0x08, 0xdf, 0x07, 0x10, 0x19, 0x0b, 0x83, - 0x56, 0x37, 0x6d, 0x5e, 0x71, 0x4d, 0xeb, 0x19, 0x0b, 0x15, 0xcc, 0x28, 0x9f, 0x53, 0x71, 0x1f, - 0xa9, 0x30, 0x67, 0xc7, 0x14, 0x07, 0x29, 0x15, 0x6c, 0xf0, 0xc0, 0x09, 0x4c, 0x2a, 0x6c, 0x31, - 0x97, 0xd4, 0xb3, 0xf3, 0x93, 0x05, 0xab, 0xe5, 0x31, 0x46, 0xd8, 0x7d, 0xb8, 0xae, 0x9b, 0x6e, - 0xb0, 0xae, 0xb7, 0xcb, 0xb5, 0x61, 0x16, 0x9d, 0xc1, 0xc8, 0xbb, 0x16, 0x9d, 0x2d, 0x09, 0x52, - 0x83, 0x97, 0xc3, 0x6e, 0x9e, 0xb3, 0x54, 0xea, 0x1b, 0xae, 0xda, 0x46, 0xf5, 0xc6, 0xa4, 0xbf, - 0x60, 0xd6, 0xf1, 0x5e, 0x1f, 0x34, 0x77, 0xbf, 0x9f, 0x83, 0x29, 0x14, 0x46, 0xbe, 0xb1, 0x60, - 0x5a, 0x4f, 0x62, 0x52, 0x2b, 0x67, 0xbd, 0x38, 0xf8, 0xed, 0x3b, 0x23, 0x20, 0xb5, 0x43, 0x67, - 0xfd, 0xeb, 0x3f, 0xfe, 0xfd, 0x71, 0xbc, 0x4a, 0x56, 0xbd, 0x21, 0xff, 0x53, 0xe4, 0x2b, 0x98, - 0xc2, 0x99, 0x4a, 0x36, 0x86, 0x65, 0xee, 0xfb, 0x4f, 0xb0, 0x6b, 0x57, 0x03, 0x8d, 0x82, 0x57, - 0x51, 0xc1, 0x4d, 0xb2, 0x72, 0x89, 0x02, 0xe4, 0xfd, 0xd9, 0x82, 0xf9, 0xbe, 0x31, 0x46, 0xb6, - 0x86, 0xa4, 0xbf, 0x38, 0x09, 0x6d, 0x77, 0x54, 0xb8, 0xd1, 0xb4, 0x89, 0x9a, 0xd6, 0x89, 0x53, - 0xae, 0x09, 0x47, 0xbc, 0xe9, 0x09, 0xf2, 0x8b, 0x05, 0xd7, 0x07, 0xc6, 0x1d, 0xf1, 0x86, 0xb0, - 0x95, 0x8d, 0x4d, 0x7b, 0x7b, 0xf4, 0x00, 0x23, 0x70, 0x0b, 0x05, 0x6e, 0x90, 0xd7, 0xca, 0x05, - 0x8a, 0x0e, 0x3f, 0x0e, 0x74, 0xe7, 0x36, 0x95, 0xa2, 0x5f, 0x2d, 0x58, 0x18, 0xbc, 0x85, 0x64, - 0x18, 0x67, 0xe9, 0xc8, 0xb4, 0x77, 0x9e, 0x23, 0x62, 0x34, 0x99, 0xc5, 0xb8, 0x28, 0x4a, 0x79, - 0x62, 0xc1, 0xcd, 0xa1, 0x13, 0x83, 0xbc, 0x37, 0x44, 0xc3, 0x28, 0x43, 0xcb, 0xbe, 0xf7, 0xe2, - 0x09, 0x8c, 0xa7, 0x77, 0xd1, 0xd3, 0x9b, 0xe4, 0xae, 0x77, 0xf9, 0x67, 0x5b, 0x70, 0xce, 0x99, - 0xf7, 0x45, 0xb1, 0xf0, 0x84, 0xfc, 0x6e, 0xc1, 0x4b, 0xe7, 0xe6, 0x0d, 0xd9, 0xb9, 0x4a, 0xd5, - 0x85, 0x79, 0x66, 0xef, 0x3e, 0x4f, 0x88, 0x91, 0x7e, 0x17, 0xa5, 0x6f, 0x13, 0x77, 0x98, 0x74, - 0x9c, 0xc4, 0x85, 0x6c, 0xf5, 0xf2, 0x64, 0xef, 0xfd, 0xa7, 0x27, 0x55, 0xeb, 0xd9, 0x49, 0xd5, - 0xfa, 0xe7, 0xa4, 0x6a, 0xfd, 0x70, 0x5a, 0x1d, 0x7b, 0x76, 0x5a, 0x1d, 0xfb, 0xf3, 0xb4, 0x3a, - 0xf6, 0xd9, 0x46, 0xdf, 0x57, 0xda, 0x40, 0xce, 0xcf, 0x7b, 0x59, 0xf1, 0x53, 0xad, 0x31, 0x8d, - 0x1f, 0xaa, 0xaf, 0xff, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x0e, 0xc7, 0x33, 0xa7, 0x0b, 0x00, - 0x00, + // 1180 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xcf, 0x6f, 0x1b, 0xc5, + 0x17, 0xcf, 0xa6, 0x6e, 0x7e, 0xbc, 0xb4, 0xf9, 0x7e, 0x99, 0x44, 0x8d, 0xb3, 0x49, 0x5d, 0x67, + 0x09, 0x8a, 0x13, 0x29, 0xbb, 0x49, 0x2a, 0xa5, 0xa8, 0x08, 0x28, 0x09, 0x3f, 0x14, 0xa9, 0x48, + 0xad, 0xc3, 0x89, 0xcb, 0x6a, 0xbd, 0x1e, 0x3b, 0xa3, 0xda, 0x3b, 0x9b, 0x9d, 0x75, 0x42, 0x55, + 0x22, 0x24, 0x10, 0x07, 0x6e, 0x48, 0x48, 0xa8, 0x57, 0x24, 0x4e, 0x9c, 0x38, 0xf0, 0x3f, 0xd0, + 0x63, 0x25, 0x24, 0xc4, 0x09, 0x50, 0xc2, 0x5f, 0xc0, 0x5f, 0x80, 0xe6, 0xcd, 0xac, 0x63, 0xc7, + 0xeb, 0xcd, 0xa6, 0x27, 0xef, 0xce, 0x7c, 0xde, 0x7b, 0x9f, 0xcf, 0x9b, 0xf7, 0xe6, 0xad, 0xa1, + 0xdc, 0xf2, 0x8e, 0xbc, 0x80, 0xc6, 0x8e, 0xfc, 0x75, 0x22, 0x7a, 0xec, 0x45, 0x75, 0xe1, 0x1c, + 0x76, 0x68, 0xf4, 0xd4, 0x0e, 0x23, 0x1e, 0x73, 0x32, 0xab, 0x11, 0xb6, 0xfc, 0xb5, 0x35, 0xc2, + 0x9c, 0x6d, 0xf2, 0x26, 0x47, 0x80, 0x23, 0x9f, 0x14, 0xd6, 0x5c, 0x6c, 0x72, 0xde, 0x6c, 0x51, + 0xc7, 0x0b, 0x99, 0xe3, 0x05, 0x01, 0x8f, 0xbd, 0x98, 0xf1, 0x40, 0xe8, 0xdd, 0x35, 0x9f, 0x8b, + 0x36, 0x17, 0x4e, 0xcd, 0x13, 0x54, 0x85, 0x70, 0x8e, 0x36, 0x6b, 0x34, 0xf6, 0x36, 0x9d, 0xd0, + 0x6b, 0xb2, 0x00, 0xc1, 0x1a, 0xbb, 0x94, 0xca, 0x2b, 0xf4, 0x22, 0xaf, 0x9d, 0xb8, 0x4b, 0xa7, + 0xce, 0xc2, 0x28, 0xf4, 0x35, 0xa2, 0xd4, 0x1b, 0x30, 0x09, 0xe5, 0x73, 0xa6, 0x83, 0x58, 0xb3, + 0x40, 0x1e, 0x4b, 0x1a, 0x8f, 0xd0, 0x6d, 0x95, 0x1e, 0x76, 0xa8, 0x88, 0xad, 0xc7, 0x30, 0xd3, + 0xb7, 0x2a, 0x42, 0x1e, 0x08, 0x4a, 0xee, 0xc3, 0x98, 0x0a, 0x5f, 0x34, 0xca, 0x46, 0x65, 0x6a, + 0x6b, 0xd1, 0x4e, 0x4b, 0x8c, 0xad, 0xac, 0x76, 0x0a, 0x2f, 0xfe, 0xbc, 0x33, 0x52, 0xd5, 0x16, + 0xd6, 0x0c, 0xbc, 0xa6, 0x5c, 0x72, 0xde, 0xea, 0xc6, 0xf9, 0xda, 0x80, 0x09, 0xb9, 0xb0, 0x17, + 0x34, 0x38, 0x21, 0x50, 0x08, 0xbc, 0x36, 0x45, 0xdf, 0x93, 0x55, 0x7c, 0x26, 0x14, 0xc6, 0x6b, + 0x5e, 0xcb, 0x0b, 0x7c, 0x5a, 0x1c, 0x2d, 0x5f, 0xab, 0x4c, 0x6d, 0xcd, 0xdb, 0x4a, 0x90, 0x2d, + 0x05, 0xd9, 0x5a, 0x90, 0xbd, 0xcb, 0x59, 0xb0, 0xb3, 0x21, 0xe3, 0xfd, 0xf4, 0xd7, 0x9d, 0x4a, + 0x93, 0xc5, 0x07, 0x9d, 0x9a, 0xed, 0xf3, 0xb6, 0xa3, 0xd5, 0xab, 0x9f, 0x75, 0x51, 0x7f, 0xe2, + 0xc4, 0x4f, 0x43, 0x2a, 0xd0, 0x40, 0x54, 0x13, 0xdf, 0xd6, 0xbf, 0x46, 0x92, 0x06, 0xc5, 0xae, + 0xab, 0xf7, 0x7a, 0x28, 0x17, 0x8a, 0x06, 0xc6, 0x2e, 0x0d, 0x91, 0xab, 0x05, 0x68, 0xc1, 0xca, + 0x84, 0x2c, 0xc3, 0x74, 0xcc, 0xda, 0xd4, 0x8d, 0xb9, 0x1b, 0xd1, 0x06, 0x6b, 0xb5, 0x8a, 0xa3, + 0x65, 0xa3, 0x72, 0xad, 0x7a, 0x43, 0xae, 0x7e, 0xc2, 0xab, 0xb8, 0x46, 0xde, 0x02, 0x93, 0x8a, + 0x98, 0xb5, 0xbd, 0x98, 0xd6, 0xdd, 0x5a, 0x8b, 0xfb, 0x4f, 0x44, 0x8f, 0xc5, 0x35, 0xb4, 0x98, + 0xeb, 0x22, 0x76, 0x10, 0xd0, 0x35, 0x7e, 0x1b, 0x16, 0xbc, 0x56, 0x8b, 0xfb, 0x58, 0x34, 0xae, + 0x0c, 0xeb, 0xb6, 0x79, 0x10, 0x1f, 0x08, 0xb7, 0x45, 0x1b, 0x71, 0xb1, 0x80, 0xd6, 0xc5, 0x73, + 0x88, 0x24, 0xfa, 0x31, 0x02, 0x1e, 0xd2, 0x46, 0x6c, 0xcd, 0xc3, 0x1c, 0x6a, 0x46, 0xaf, 0x55, + 0x14, 0x93, 0x9c, 0xcb, 0x3e, 0x14, 0x07, 0xb7, 0x74, 0x52, 0xee, 0xc1, 0x98, 0x52, 0xae, 0x8b, + 0x20, 0xe3, 0x44, 0x74, 0x05, 0x28, 0xb8, 0xb5, 0x00, 0xf3, 0xe8, 0x74, 0xff, 0x80, 0x1f, 0xef, + 0xc9, 0x12, 0x7d, 0xdf, 0x8b, 0xbd, 0x24, 0xe2, 0x37, 0x06, 0x98, 0x69, 0xbb, 0xdd, 0x93, 0x98, + 0x68, 0xb3, 0xc0, 0xf5, 0xb9, 0x88, 0xf3, 0x86, 0x1d, 0x6f, 0xb3, 0x60, 0x97, 0x8b, 0x98, 0x38, + 0x30, 0x83, 0x1d, 0xe1, 0x8a, 0x4e, 0x4d, 0xf8, 0x11, 0x0b, 0xb1, 0x21, 0xb1, 0x9e, 0x26, 0xab, + 0x04, 0xb7, 0xf6, 0x7b, 0x77, 0xac, 0x7d, 0x4d, 0xe5, 0x51, 0xc4, 0x8f, 0x58, 0x9d, 0x46, 0x7d, + 0xb9, 0x21, 0xf3, 0x30, 0xe1, 0x1f, 0x78, 0x2c, 0x70, 0x59, 0x5d, 0x97, 0xea, 0x38, 0xbe, 0xef, + 0xd5, 0x89, 0x09, 0x13, 0xa1, 0xb6, 0xc1, 0xd3, 0x9e, 0xac, 0x76, 0xdf, 0xad, 0xcf, 0x01, 0x94, + 0x1f, 0xac, 0xf5, 0x57, 0x73, 0x22, 0x73, 0xef, 0xb5, 0x79, 0x27, 0x88, 0xb1, 0x34, 0xf2, 0xe4, + 0x5e, 0xc1, 0x2d, 0x17, 0x16, 0x52, 0x25, 0xe9, 0xf4, 0x3e, 0x80, 0x71, 0x5d, 0xcd, 0xba, 0xd4, + 0xcb, 0xe9, 0xa5, 0x7e, 0xae, 0x20, 0x49, 0xb2, 0xde, 0xb1, 0x3e, 0x82, 0x55, 0x0c, 0x80, 0x47, + 0xd7, 0x1f, 0xe5, 0x03, 0x55, 0xbe, 0x8c, 0x07, 0x49, 0x0a, 0x7b, 0x25, 0x1a, 0x17, 0xf2, 0x74, + 0x08, 0x6b, 0x79, 0x1c, 0x69, 0xe2, 0xbb, 0x00, 0x22, 0xa4, 0xbe, 0xdb, 0xe8, 0x04, 0xf5, 0x4b, + 0xda, 0x74, 0x3f, 0xa4, 0xbe, 0x84, 0x69, 0xe6, 0x93, 0xd2, 0xee, 0x43, 0x69, 0x66, 0x6d, 0xea, + 0xe4, 0x60, 0x48, 0x09, 0xeb, 0x3f, 0x70, 0x02, 0x05, 0x89, 0x4d, 0xee, 0x25, 0xf9, 0x6c, 0x7d, + 0x6f, 0xc0, 0x62, 0xba, 0x8d, 0x26, 0xf6, 0x10, 0x6e, 0xaa, 0xa2, 0xeb, 0xcf, 0xeb, 0x52, 0x3a, + 0x37, 0xf4, 0xa2, 0x3c, 0x68, 0x7a, 0x37, 0xd8, 0xf9, 0x92, 0x20, 0x15, 0xf8, 0xbf, 0xdf, 0x89, + 0x22, 0x1a, 0xc4, 0xaa, 0xc3, 0x65, 0xd9, 0xc8, 0xda, 0x28, 0x54, 0xa7, 0xf5, 0x3a, 0xf6, 0xf5, + 0x5e, 0xdd, 0xba, 0x0f, 0x65, 0x75, 0xd0, 0x34, 0xa8, 0xb3, 0xa0, 0xb9, 0x57, 0xf3, 0xd1, 0x37, + 0x0a, 0x4d, 0x04, 0xdd, 0x82, 0xb1, 0x06, 0x6b, 0xc5, 0xdd, 0xe4, 0xeb, 0x37, 0xeb, 0x17, 0x03, + 0xe6, 0x52, 0xec, 0xb0, 0x60, 0xeb, 0x70, 0x2b, 0x54, 0x5b, 0x2e, 0xab, 0xf9, 0xae, 0xd2, 0x26, + 0xd3, 0xa9, 0xdb, 0x71, 0x75, 0xc8, 0xdd, 0x38, 0xe8, 0x4e, 0x0b, 0x9c, 0x09, 0x07, 0xb7, 0xc8, + 0x5d, 0x28, 0x60, 0x8b, 0x8f, 0xe6, 0xab, 0x6e, 0x04, 0x5b, 0xcf, 0x0d, 0x58, 0xca, 0xd0, 0xac, + 0x0f, 0x44, 0xc0, 0x62, 0xba, 0x00, 0xe1, 0xb2, 0xa0, 0xc1, 0xf5, 0xf9, 0xac, 0xe7, 0x96, 0xd1, + 0xd3, 0x04, 0xc5, 0x14, 0x29, 0x42, 0xee, 0x6f, 0xfd, 0x0e, 0x70, 0x1d, 0xa9, 0x91, 0xaf, 0x0c, + 0x18, 0x53, 0x73, 0x91, 0x54, 0xd2, 0x63, 0x0c, 0x8e, 0x61, 0x73, 0x35, 0x07, 0x52, 0xc9, 0xb3, + 0x96, 0xbf, 0xfc, 0xed, 0x9f, 0xef, 0x46, 0x4b, 0x64, 0xd1, 0xc9, 0xf8, 0x6a, 0x20, 0x5f, 0xc0, + 0x75, 0x9c, 0x70, 0x64, 0x25, 0xcb, 0x73, 0xcf, 0x84, 0x36, 0x2b, 0x97, 0x03, 0x35, 0x83, 0xd7, + 0x91, 0xc1, 0x6d, 0xb2, 0x30, 0x84, 0x01, 0xc6, 0x7d, 0x6e, 0xc0, 0x54, 0xcf, 0x50, 0x21, 0xeb, + 0x19, 0xee, 0x07, 0xe7, 0x92, 0x69, 0xe7, 0x85, 0x6b, 0x4e, 0x6b, 0xc8, 0x69, 0x99, 0x58, 0xe9, + 0x9c, 0x70, 0xe0, 0xea, 0x0e, 0x25, 0x3f, 0x18, 0x70, 0xb3, 0x6f, 0xf8, 0x10, 0x27, 0x23, 0x5a, + 0xda, 0x10, 0x33, 0x37, 0xf2, 0x1b, 0x68, 0x82, 0xeb, 0x48, 0x70, 0x85, 0xbc, 0x91, 0x4e, 0x50, + 0x1c, 0xf0, 0x63, 0x5d, 0xaa, 0x75, 0xc9, 0xe8, 0x47, 0x03, 0xa6, 0xfb, 0xef, 0x44, 0x92, 0x15, + 0x33, 0x75, 0x80, 0x99, 0x9b, 0x57, 0xb0, 0xc8, 0x47, 0x33, 0xb9, 0xbc, 0x93, 0x54, 0x9e, 0x1a, + 0x70, 0x3b, 0xf3, 0xfe, 0x26, 0xef, 0x66, 0x70, 0xc8, 0x33, 0x42, 0xcc, 0x07, 0xaf, 0xee, 0x40, + 0x6b, 0x7a, 0x07, 0x35, 0xbd, 0x49, 0xb6, 0x9d, 0xe1, 0x1f, 0xd1, 0xee, 0x05, 0x65, 0xce, 0xb3, + 0x64, 0xe1, 0x84, 0xfc, 0x6c, 0xc0, 0xff, 0x2e, 0xdc, 0xfe, 0x64, 0xf3, 0x32, 0x56, 0x03, 0xd3, + 0xc5, 0xdc, 0xba, 0x8a, 0x89, 0xa6, 0xbe, 0x8d, 0xd4, 0x37, 0x88, 0x9d, 0x45, 0x1d, 0xe7, 0x62, + 0x42, 0x5b, 0xbe, 0x9c, 0x90, 0x5f, 0x0d, 0x98, 0x4d, 0xbb, 0x24, 0xc9, 0x76, 0x56, 0x49, 0x0c, + 0x9f, 0x24, 0xe6, 0xbd, 0x2b, 0xdb, 0xe5, 0x4b, 0xfe, 0x90, 0x9b, 0xda, 0x79, 0xa6, 0x26, 0xd5, + 0xc9, 0xce, 0x7b, 0x2f, 0x4e, 0x4b, 0xc6, 0xcb, 0xd3, 0x92, 0xf1, 0xf7, 0x69, 0xc9, 0xf8, 0xf6, + 0xac, 0x34, 0xf2, 0xf2, 0xac, 0x34, 0xf2, 0xc7, 0x59, 0x69, 0xe4, 0xd3, 0x95, 0x9e, 0xaf, 0xff, + 0x3e, 0xdf, 0x9f, 0x75, 0xbd, 0xe3, 0x5f, 0x80, 0xda, 0x18, 0xfe, 0x01, 0xba, 0xfb, 0x5f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x0b, 0xa7, 0x5f, 0x9f, 0xff, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -917,6 +1070,8 @@ type QueryClient interface { IprpcProviderRewardEstimation(ctx context.Context, in *QueryIprpcProviderRewardEstimationRequest, opts ...grpc.CallOption) (*QueryIprpcProviderRewardEstimationResponse, error) // IprpcSpecReward queries for a spec's IPRPC reward IprpcSpecReward(ctx context.Context, in *QueryIprpcSpecRewardRequest, opts ...grpc.CallOption) (*QueryIprpcSpecRewardResponse, error) + // PendingIbcIprpcFunds queries for a spec's IPRPC reward + PendingIbcIprpcFunds(ctx context.Context, in *QueryPendingIbcIprpcFundsRequest, opts ...grpc.CallOption) (*QueryPendingIbcIprpcFundsResponse, error) } type queryClient struct { @@ -990,6 +1145,15 @@ func (c *queryClient) IprpcSpecReward(ctx context.Context, in *QueryIprpcSpecRew return out, nil } +func (c *queryClient) PendingIbcIprpcFunds(ctx context.Context, in *QueryPendingIbcIprpcFundsRequest, opts ...grpc.CallOption) (*QueryPendingIbcIprpcFundsResponse, error) { + out := new(QueryPendingIbcIprpcFundsResponse) + err := c.cc.Invoke(ctx, "/lavanet.lava.rewards.Query/PendingIbcIprpcFunds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. @@ -1006,6 +1170,8 @@ type QueryServer interface { IprpcProviderRewardEstimation(context.Context, *QueryIprpcProviderRewardEstimationRequest) (*QueryIprpcProviderRewardEstimationResponse, error) // IprpcSpecReward queries for a spec's IPRPC reward IprpcSpecReward(context.Context, *QueryIprpcSpecRewardRequest) (*QueryIprpcSpecRewardResponse, error) + // PendingIbcIprpcFunds queries for a spec's IPRPC reward + PendingIbcIprpcFunds(context.Context, *QueryPendingIbcIprpcFundsRequest) (*QueryPendingIbcIprpcFundsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -1033,6 +1199,9 @@ func (*UnimplementedQueryServer) IprpcProviderRewardEstimation(ctx context.Conte func (*UnimplementedQueryServer) IprpcSpecReward(ctx context.Context, req *QueryIprpcSpecRewardRequest) (*QueryIprpcSpecRewardResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method IprpcSpecReward not implemented") } +func (*UnimplementedQueryServer) PendingIbcIprpcFunds(ctx context.Context, req *QueryPendingIbcIprpcFundsRequest) (*QueryPendingIbcIprpcFundsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PendingIbcIprpcFunds not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1164,6 +1333,24 @@ func _Query_IprpcSpecReward_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _Query_PendingIbcIprpcFunds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPendingIbcIprpcFundsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PendingIbcIprpcFunds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lavanet.lava.rewards.Query/PendingIbcIprpcFunds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PendingIbcIprpcFunds(ctx, req.(*QueryPendingIbcIprpcFundsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "lavanet.lava.rewards.Query", HandlerType: (*QueryServer)(nil), @@ -1196,6 +1383,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "IprpcSpecReward", Handler: _Query_IprpcSpecReward_Handler, }, + { + MethodName: "PendingIbcIprpcFunds", + Handler: _Query_PendingIbcIprpcFunds_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "lavanet/lava/rewards/query.proto", @@ -1757,6 +1948,116 @@ func (m *QueryIprpcSpecRewardResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *QueryPendingIbcIprpcFundsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingIbcIprpcFundsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingIbcIprpcFundsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Filter) > 0 { + i -= len(m.Filter) + copy(dAtA[i:], m.Filter) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Filter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PendingIbcIprpcFundInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PendingIbcIprpcFundInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PendingIbcIprpcFundInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Cost.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.PendingIbcIprpcFund.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryPendingIbcIprpcFundsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingIbcIprpcFundsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingIbcIprpcFundsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PendingIbcIprpcFundsInfo) > 0 { + for iNdEx := len(m.PendingIbcIprpcFundsInfo) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PendingIbcIprpcFundsInfo[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1996,6 +2297,47 @@ func (m *QueryIprpcSpecRewardResponse) Size() (n int) { return n } +func (m *QueryPendingIbcIprpcFundsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Filter) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *PendingIbcIprpcFundInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.PendingIbcIprpcFund.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.Cost.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPendingIbcIprpcFundsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.PendingIbcIprpcFundsInfo) > 0 { + for _, e := range m.PendingIbcIprpcFundsInfo { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3436,6 +3778,288 @@ func (m *QueryIprpcSpecRewardResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryPendingIbcIprpcFundsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingIbcIprpcFundsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingIbcIprpcFundsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Filter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Filter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PendingIbcIprpcFundInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PendingIbcIprpcFundInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PendingIbcIprpcFundInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingIbcIprpcFund", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PendingIbcIprpcFund.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cost", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Cost.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPendingIbcIprpcFundsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingIbcIprpcFundsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingIbcIprpcFundsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingIbcIprpcFundsInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PendingIbcIprpcFundsInfo = append(m.PendingIbcIprpcFundsInfo, PendingIbcIprpcFundInfo{}) + if err := m.PendingIbcIprpcFundsInfo[len(m.PendingIbcIprpcFundsInfo)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/rewards/types/query.pb.gw.go b/x/rewards/types/query.pb.gw.go index 94fe230994..ce5ca78063 100644 --- a/x/rewards/types/query.pb.gw.go +++ b/x/rewards/types/query.pb.gw.go @@ -249,6 +249,60 @@ func local_request_Query_IprpcSpecReward_0(ctx context.Context, marshaler runtim } +func request_Query_PendingIbcIprpcFunds_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingIbcIprpcFundsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["filter"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "filter") + } + + protoReq.Filter, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "filter", err) + } + + msg, err := client.PendingIbcIprpcFunds(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PendingIbcIprpcFunds_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingIbcIprpcFundsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["filter"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "filter") + } + + protoReq.Filter, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "filter", err) + } + + msg, err := server.PendingIbcIprpcFunds(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -416,6 +470,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_PendingIbcIprpcFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PendingIbcIprpcFunds_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingIbcIprpcFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -597,6 +674,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_PendingIbcIprpcFunds_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PendingIbcIprpcFunds_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingIbcIprpcFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -614,6 +711,8 @@ var ( pattern_Query_IprpcProviderRewardEstimation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"lavanet", "lava", "rewards", "iprpc_provider_reward", "provider"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_IprpcSpecReward_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"lavanet", "lava", "rewards", "iprpc_spec_reward", "spec"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PendingIbcIprpcFunds_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"lavanet", "lava", "rewards", "pending_ibc_iprpc_funds", "filter"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -630,4 +729,6 @@ var ( forward_Query_IprpcProviderRewardEstimation_0 = runtime.ForwardResponseMessage forward_Query_IprpcSpecReward_0 = runtime.ForwardResponseMessage + + forward_Query_PendingIbcIprpcFunds_0 = runtime.ForwardResponseMessage ) diff --git a/x/rewards/types/tx.pb.go b/x/rewards/types/tx.pb.go index 36a497f99f..baafc963e4 100644 --- a/x/rewards/types/tx.pb.go +++ b/x/rewards/types/tx.pb.go @@ -230,45 +230,139 @@ func (m *MsgFundIprpcResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgFundIprpcResponse proto.InternalMessageInfo +type MsgCoverIbcIprpcFundCost struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + Index uint64 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` +} + +func (m *MsgCoverIbcIprpcFundCost) Reset() { *m = MsgCoverIbcIprpcFundCost{} } +func (m *MsgCoverIbcIprpcFundCost) String() string { return proto.CompactTextString(m) } +func (*MsgCoverIbcIprpcFundCost) ProtoMessage() {} +func (*MsgCoverIbcIprpcFundCost) Descriptor() ([]byte, []int) { + return fileDescriptor_6a4c66e189226d78, []int{4} +} +func (m *MsgCoverIbcIprpcFundCost) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCoverIbcIprpcFundCost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCoverIbcIprpcFundCost.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCoverIbcIprpcFundCost) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCoverIbcIprpcFundCost.Merge(m, src) +} +func (m *MsgCoverIbcIprpcFundCost) XXX_Size() int { + return m.Size() +} +func (m *MsgCoverIbcIprpcFundCost) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCoverIbcIprpcFundCost.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCoverIbcIprpcFundCost proto.InternalMessageInfo + +func (m *MsgCoverIbcIprpcFundCost) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *MsgCoverIbcIprpcFundCost) GetIndex() uint64 { + if m != nil { + return m.Index + } + return 0 +} + +type MsgCoverIbcIprpcFundCostResponse struct { +} + +func (m *MsgCoverIbcIprpcFundCostResponse) Reset() { *m = MsgCoverIbcIprpcFundCostResponse{} } +func (m *MsgCoverIbcIprpcFundCostResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCoverIbcIprpcFundCostResponse) ProtoMessage() {} +func (*MsgCoverIbcIprpcFundCostResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6a4c66e189226d78, []int{5} +} +func (m *MsgCoverIbcIprpcFundCostResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCoverIbcIprpcFundCostResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCoverIbcIprpcFundCostResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCoverIbcIprpcFundCostResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCoverIbcIprpcFundCostResponse.Merge(m, src) +} +func (m *MsgCoverIbcIprpcFundCostResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCoverIbcIprpcFundCostResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCoverIbcIprpcFundCostResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCoverIbcIprpcFundCostResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSetIprpcData)(nil), "lavanet.lava.rewards.MsgSetIprpcData") proto.RegisterType((*MsgSetIprpcDataResponse)(nil), "lavanet.lava.rewards.MsgSetIprpcDataResponse") proto.RegisterType((*MsgFundIprpc)(nil), "lavanet.lava.rewards.MsgFundIprpc") proto.RegisterType((*MsgFundIprpcResponse)(nil), "lavanet.lava.rewards.MsgFundIprpcResponse") + proto.RegisterType((*MsgCoverIbcIprpcFundCost)(nil), "lavanet.lava.rewards.MsgCoverIbcIprpcFundCost") + proto.RegisterType((*MsgCoverIbcIprpcFundCostResponse)(nil), "lavanet.lava.rewards.MsgCoverIbcIprpcFundCostResponse") } func init() { proto.RegisterFile("lavanet/lava/rewards/tx.proto", fileDescriptor_6a4c66e189226d78) } var fileDescriptor_6a4c66e189226d78 = []byte{ - // 443 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcd, 0x6e, 0xd4, 0x30, - 0x10, 0xc7, 0xd7, 0xec, 0x8a, 0xb2, 0x66, 0x05, 0x92, 0x59, 0x41, 0x1a, 0x41, 0xba, 0x8a, 0x84, - 0x88, 0x90, 0x6a, 0xd3, 0xf2, 0x04, 0xb4, 0x80, 0xc4, 0x61, 0x2f, 0xe9, 0x0d, 0x0e, 0x95, 0x93, - 0x58, 0xa9, 0x05, 0xb1, 0x23, 0x8f, 0x53, 0xda, 0xb7, 0xe0, 0x2d, 0x90, 0x78, 0x09, 0xae, 0x7b, - 0xec, 0x91, 0x13, 0xa0, 0xdd, 0x17, 0x41, 0x76, 0x92, 0xee, 0xb6, 0xe2, 0xeb, 0x34, 0x63, 0xff, - 0x67, 0xc6, 0xbf, 0x19, 0x0f, 0x7e, 0xf4, 0x81, 0x9f, 0x72, 0x25, 0x2c, 0x73, 0x96, 0x19, 0xf1, - 0x91, 0x9b, 0x02, 0x98, 0x3d, 0xa3, 0xb5, 0xd1, 0x56, 0x93, 0x69, 0x27, 0x53, 0x67, 0x69, 0x27, - 0x87, 0x51, 0xae, 0xa1, 0xd2, 0xc0, 0x32, 0x0e, 0x82, 0x9d, 0xee, 0x65, 0xc2, 0xf2, 0x3d, 0x96, - 0x6b, 0xa9, 0xda, 0xac, 0x70, 0x5a, 0xea, 0x52, 0x7b, 0x97, 0x39, 0xaf, 0xbd, 0x8d, 0x3f, 0x23, - 0x7c, 0x77, 0x0e, 0xe5, 0x91, 0xb0, 0x6f, 0x6a, 0x53, 0xe7, 0x2f, 0xb9, 0xe5, 0xe4, 0x21, 0x1e, - 0xf3, 0xc6, 0x9e, 0x68, 0x23, 0xed, 0x79, 0x80, 0x66, 0x28, 0x19, 0xa7, 0xeb, 0x0b, 0xf2, 0x0a, - 0xdf, 0xa9, 0xa4, 0x3a, 0x96, 0x2e, 0xfc, 0x38, 0xd7, 0x60, 0x83, 0x1b, 0x33, 0x94, 0xdc, 0xde, - 0xdf, 0xa6, 0x2d, 0x00, 0x75, 0x00, 0xb4, 0x03, 0xa0, 0x87, 0x5a, 0xaa, 0x83, 0xd1, 0xe2, 0xfb, - 0xce, 0x20, 0x9d, 0x54, 0x52, 0xf9, 0x47, 0x0e, 0x35, 0x58, 0xc2, 0xf0, 0xbd, 0xb6, 0x04, 0x34, - 0x19, 0xe4, 0x46, 0xd6, 0x56, 0x6a, 0x05, 0xc1, 0x70, 0x36, 0x4c, 0xc6, 0x29, 0xf1, 0xd2, 0xd1, - 0xa6, 0x12, 0x6f, 0xe3, 0x07, 0xd7, 0x40, 0x53, 0x01, 0xb5, 0x56, 0x20, 0xe2, 0xaf, 0x08, 0x4f, - 0xe6, 0x50, 0xbe, 0x6e, 0x54, 0xe1, 0x45, 0x12, 0xe0, 0xad, 0xdc, 0x08, 0x6e, 0xb5, 0xe9, 0xf8, - 0xfb, 0x23, 0x09, 0xf1, 0xad, 0xa2, 0x31, 0xdc, 0x95, 0xf4, 0xdc, 0xa3, 0xf4, 0xf2, 0x4c, 0x04, - 0xde, 0xe2, 0x95, 0x6e, 0x94, 0x6d, 0x31, 0xfe, 0xda, 0xd2, 0x33, 0xd7, 0xd2, 0x97, 0x1f, 0x3b, - 0x49, 0x29, 0xed, 0x49, 0x93, 0xd1, 0x5c, 0x57, 0xac, 0xfb, 0x80, 0xd6, 0xec, 0x42, 0xf1, 0x9e, - 0xd9, 0xf3, 0x5a, 0x80, 0x4f, 0x80, 0xb4, 0xaf, 0x4d, 0x08, 0x1e, 0x41, 0x2d, 0xf2, 0x60, 0xe4, - 0xc9, 0xbc, 0x1f, 0xdf, 0xc7, 0xd3, 0xcd, 0x06, 0xfa, 0xce, 0xf6, 0x17, 0x08, 0x0f, 0xe7, 0x50, - 0x92, 0x02, 0x4f, 0xae, 0x7c, 0xd1, 0x63, 0xfa, 0xbb, 0x1d, 0xa0, 0xd7, 0x06, 0x14, 0xee, 0xfe, - 0x57, 0x58, 0xff, 0x1a, 0x79, 0x87, 0xc7, 0xeb, 0x19, 0xc6, 0x7f, 0xcc, 0xbd, 0x8c, 0x09, 0x9f, - 0xfe, 0x3b, 0xa6, 0x2f, 0x7e, 0xf0, 0x62, 0xb1, 0x8c, 0xd0, 0xc5, 0x32, 0x42, 0x3f, 0x97, 0x11, - 0xfa, 0xb4, 0x8a, 0x06, 0x17, 0xab, 0x68, 0xf0, 0x6d, 0x15, 0x0d, 0xde, 0x3e, 0xd9, 0x98, 0xe1, - 0x95, 0xcd, 0x3f, 0x5b, 0xef, 0xbe, 0x1b, 0x64, 0x76, 0xd3, 0xef, 0xec, 0xf3, 0x5f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x87, 0x10, 0x57, 0xff, 0x20, 0x03, 0x00, 0x00, + // 501 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x4f, 0x6f, 0xd3, 0x3e, + 0x18, 0x6e, 0xd6, 0xfe, 0x7e, 0xa3, 0xa6, 0x02, 0xc9, 0x14, 0xc8, 0x22, 0xc8, 0xaa, 0x48, 0x88, + 0x0a, 0x69, 0x36, 0x1b, 0x12, 0x77, 0x56, 0x40, 0x1a, 0x52, 0x2f, 0xd9, 0x0d, 0x0e, 0x93, 0x93, + 0x58, 0x99, 0x05, 0xb1, 0x23, 0xbf, 0x4e, 0xe9, 0x4e, 0x7c, 0x05, 0xbe, 0x05, 0x12, 0x5f, 0x82, + 0xeb, 0xb8, 0xed, 0xc8, 0x09, 0x50, 0xfb, 0x45, 0x50, 0x9c, 0xa4, 0xed, 0xa6, 0x96, 0x3f, 0x27, + 0xff, 0x79, 0x1e, 0x3f, 0x7e, 0x1e, 0xfb, 0x7d, 0xd1, 0xfd, 0x77, 0x6c, 0xc2, 0x24, 0x37, 0xb4, + 0x1c, 0xa9, 0xe6, 0xef, 0x99, 0x4e, 0x80, 0x9a, 0x29, 0xc9, 0xb5, 0x32, 0x0a, 0xf7, 0x6b, 0x98, + 0x94, 0x23, 0xa9, 0x61, 0xcf, 0x8f, 0x15, 0x64, 0x0a, 0x68, 0xc4, 0x80, 0xd3, 0xc9, 0x7e, 0xc4, + 0x0d, 0xdb, 0xa7, 0xb1, 0x12, 0xb2, 0x3a, 0xe5, 0xf5, 0x53, 0x95, 0x2a, 0x3b, 0xa5, 0xe5, 0xac, + 0xda, 0x0d, 0x3e, 0x39, 0xe8, 0xe6, 0x18, 0xd2, 0x63, 0x6e, 0x8e, 0x72, 0x9d, 0xc7, 0xcf, 0x99, + 0x61, 0xf8, 0x1e, 0xea, 0xb2, 0xc2, 0x9c, 0x2a, 0x2d, 0xcc, 0x99, 0xeb, 0x0c, 0x9c, 0x61, 0x37, + 0x5c, 0x6e, 0xe0, 0x17, 0xe8, 0x46, 0x26, 0xe4, 0x89, 0x28, 0xe9, 0x27, 0xb1, 0x02, 0xe3, 0x6e, + 0x0d, 0x9c, 0xe1, 0xf5, 0x83, 0x1d, 0x52, 0x19, 0x20, 0xa5, 0x01, 0x52, 0x1b, 0x20, 0x23, 0x25, + 0xe4, 0x61, 0xe7, 0xfc, 0xfb, 0x6e, 0x2b, 0xec, 0x65, 0x42, 0xda, 0x4b, 0x46, 0x0a, 0x0c, 0xa6, + 0xe8, 0x56, 0x25, 0x01, 0x45, 0x04, 0xb1, 0x16, 0xb9, 0x11, 0x4a, 0x82, 0xdb, 0x1e, 0xb4, 0x87, + 0xdd, 0x10, 0x5b, 0xe8, 0x78, 0x15, 0x09, 0x76, 0xd0, 0xdd, 0x2b, 0x46, 0x43, 0x0e, 0xb9, 0x92, + 0xc0, 0x83, 0x2f, 0x0e, 0xea, 0x8d, 0x21, 0x7d, 0x59, 0xc8, 0xc4, 0x82, 0xd8, 0x45, 0xdb, 0xb1, + 0xe6, 0xcc, 0x28, 0x5d, 0xfb, 0x6f, 0x96, 0xd8, 0x43, 0xd7, 0x92, 0x42, 0xb3, 0x52, 0xd2, 0xfa, + 0xee, 0x84, 0x8b, 0x35, 0xe6, 0x68, 0x9b, 0x65, 0xaa, 0x90, 0xa6, 0xb2, 0xf1, 0xdb, 0x48, 0x8f, + 0xcb, 0x48, 0x9f, 0x7f, 0xec, 0x0e, 0x53, 0x61, 0x4e, 0x8b, 0x88, 0xc4, 0x2a, 0xa3, 0xf5, 0x07, + 0x54, 0xc3, 0x1e, 0x24, 0x6f, 0xa9, 0x39, 0xcb, 0x39, 0xd8, 0x03, 0x10, 0x36, 0xda, 0x18, 0xa3, + 0x0e, 0xe4, 0x3c, 0x76, 0x3b, 0xd6, 0x99, 0x9d, 0x07, 0x77, 0x50, 0x7f, 0x35, 0xc0, 0x22, 0xd9, + 0x2b, 0xe4, 0x8e, 0x21, 0x1d, 0xa9, 0x09, 0xd7, 0x47, 0x51, 0x6c, 0xb1, 0x92, 0x64, 0x5f, 0x70, + 0x73, 0xc8, 0x3e, 0xfa, 0x4f, 0xc8, 0x84, 0x4f, 0xeb, 0x84, 0xd5, 0x22, 0x08, 0xd0, 0x60, 0x93, + 0x56, 0x73, 0xdf, 0xc1, 0xd7, 0x2d, 0xd4, 0x1e, 0x43, 0x8a, 0x13, 0xd4, 0xbb, 0x54, 0x12, 0x0f, + 0xc8, 0xba, 0x9a, 0x23, 0x57, 0x3e, 0xc4, 0xdb, 0xfb, 0x2b, 0x5a, 0x73, 0x1b, 0x7e, 0x83, 0xba, + 0xcb, 0x3f, 0x0b, 0x36, 0x9e, 0x5d, 0x70, 0xbc, 0x47, 0x7f, 0xe6, 0x2c, 0xc4, 0x3f, 0xa0, 0xdb, + 0xeb, 0xdf, 0x8d, 0x6c, 0x14, 0x59, 0xcb, 0xf7, 0x9e, 0xfe, 0x1b, 0xbf, 0x31, 0x70, 0xf8, 0xec, + 0x7c, 0xe6, 0x3b, 0x17, 0x33, 0xdf, 0xf9, 0x39, 0xf3, 0x9d, 0x8f, 0x73, 0xbf, 0x75, 0x31, 0xf7, + 0x5b, 0xdf, 0xe6, 0x7e, 0xeb, 0xf5, 0xc3, 0x95, 0xa2, 0xb9, 0xd4, 0xea, 0xd3, 0x65, 0xb3, 0x97, + 0x95, 0x13, 0xfd, 0x6f, 0x9b, 0xf4, 0xc9, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x38, 0x41, 0xbe, + 0x81, 0x11, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -285,6 +379,7 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { SetIprpcData(ctx context.Context, in *MsgSetIprpcData, opts ...grpc.CallOption) (*MsgSetIprpcDataResponse, error) FundIprpc(ctx context.Context, in *MsgFundIprpc, opts ...grpc.CallOption) (*MsgFundIprpcResponse, error) + CoverIbcIprpcFundCost(ctx context.Context, in *MsgCoverIbcIprpcFundCost, opts ...grpc.CallOption) (*MsgCoverIbcIprpcFundCostResponse, error) } type msgClient struct { @@ -313,10 +408,20 @@ func (c *msgClient) FundIprpc(ctx context.Context, in *MsgFundIprpc, opts ...grp return out, nil } +func (c *msgClient) CoverIbcIprpcFundCost(ctx context.Context, in *MsgCoverIbcIprpcFundCost, opts ...grpc.CallOption) (*MsgCoverIbcIprpcFundCostResponse, error) { + out := new(MsgCoverIbcIprpcFundCostResponse) + err := c.cc.Invoke(ctx, "/lavanet.lava.rewards.Msg/CoverIbcIprpcFundCost", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { SetIprpcData(context.Context, *MsgSetIprpcData) (*MsgSetIprpcDataResponse, error) FundIprpc(context.Context, *MsgFundIprpc) (*MsgFundIprpcResponse, error) + CoverIbcIprpcFundCost(context.Context, *MsgCoverIbcIprpcFundCost) (*MsgCoverIbcIprpcFundCostResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -329,6 +434,9 @@ func (*UnimplementedMsgServer) SetIprpcData(ctx context.Context, req *MsgSetIprp func (*UnimplementedMsgServer) FundIprpc(ctx context.Context, req *MsgFundIprpc) (*MsgFundIprpcResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FundIprpc not implemented") } +func (*UnimplementedMsgServer) CoverIbcIprpcFundCost(ctx context.Context, req *MsgCoverIbcIprpcFundCost) (*MsgCoverIbcIprpcFundCostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CoverIbcIprpcFundCost not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -370,6 +478,24 @@ func _Msg_FundIprpc_Handler(srv interface{}, ctx context.Context, dec func(inter return interceptor(ctx, in, info, handler) } +func _Msg_CoverIbcIprpcFundCost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCoverIbcIprpcFundCost) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CoverIbcIprpcFundCost(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lavanet.lava.rewards.Msg/CoverIbcIprpcFundCost", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CoverIbcIprpcFundCost(ctx, req.(*MsgCoverIbcIprpcFundCost)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "lavanet.lava.rewards.Msg", HandlerType: (*MsgServer)(nil), @@ -382,6 +508,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "FundIprpc", Handler: _Msg_FundIprpc_Handler, }, + { + MethodName: "CoverIbcIprpcFundCost", + Handler: _Msg_CoverIbcIprpcFundCost_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "lavanet/lava/rewards/tx.proto", @@ -538,6 +668,64 @@ func (m *MsgFundIprpcResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MsgCoverIbcIprpcFundCost) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCoverIbcIprpcFundCost) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCoverIbcIprpcFundCost) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Index != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Index)) + i-- + dAtA[i] = 0x10 + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCoverIbcIprpcFundCostResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCoverIbcIprpcFundCostResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCoverIbcIprpcFundCostResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -614,6 +802,31 @@ func (m *MsgFundIprpcResponse) Size() (n int) { return n } +func (m *MsgCoverIbcIprpcFundCost) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Index != 0 { + n += 1 + sovTx(uint64(m.Index)) + } + return n +} + +func (m *MsgCoverIbcIprpcFundCostResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1034,6 +1247,157 @@ func (m *MsgFundIprpcResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgCoverIbcIprpcFundCost) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCoverIbcIprpcFundCost: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCoverIbcIprpcFundCost: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCoverIbcIprpcFundCostResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCoverIbcIprpcFundCostResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCoverIbcIprpcFundCostResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 04865f112723edb9e40580ca538988699a49bfe7 Mon Sep 17 00:00:00 2001 From: Oren Date: Mon, 8 Jul 2024 17:25:53 +0300 Subject: [PATCH 14/16] CNS-960: small fix send to pending iprpc pool without leftovers --- x/rewards/ibc_middleware.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 856f713066..233cc4b6cd 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -140,7 +140,7 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet } // send the IBC tokens from IbcIprpcReceiver to PendingIprpcPool - err = im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens) + err = im.keeper.SendIbcIprpcReceiverTokensToPendingIprpcPool(ctx, ibcTokens.Sub(leftovers)) if err != nil { detailedErr := utils.LavaFormatError("sending token from IbcIprpcReceiver to the PendingIprpcPool failed", err, utils.LogAttr("sender", data.Sender), From d853bffa7e99917384805f6f063500d8f07aceea Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 29 Aug 2024 13:31:53 +0300 Subject: [PATCH 15/16] CNS-960: code rabbit suggestions --- x/rewards/README.md | 4 ++-- x/rewards/ibc_middleware.go | 24 ++++++++++++++---------- x/rewards/types/ibc_iprpc.go | 4 ++++ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/x/rewards/README.md b/x/rewards/README.md index 2c2e1f7ed3..12b5c6a64a 100644 --- a/x/rewards/README.md +++ b/x/rewards/README.md @@ -130,7 +130,7 @@ In order to fund the IPRPC pool, the user's funds must cover the monthly minimum #### IPRPC over IBC -To ease the funding process of funding the IPRPC pool, users can send an `ibc-transfer` transaction to fund the IPRPC pool directly from the source chain. For example, using IPRPC over IBC allows funding the IPRPC pool by sending a single `ibc-transfer` to an Osmosis node. +To ease the process of funding the IPRPC pool, users can send an `ibc-transfer` transaction directly from the source chain. For example, using IPRPC over IBC allows funding the IPRPC pool by sending a single `ibc-transfer` to an Osmosis node. To make the `ibc-transfer` transaction fund the IPRPC pool, it must contain a custom memo with the following format: @@ -146,7 +146,7 @@ To make the `ibc-transfer` transaction fund the IPRPC pool, it must contain a cu The creator field can be any name, the spec field must be an on-chain spec ID, and the duration should be the funding period (in months). Users can use the `generate-ibc-iprpc-tx` helper command with the `--memo-only` flag to generate a valid IPRPC memo. If the flag is not included, this command creates a valid `ibc-transfer` transaction JSON with a custom memo. The only remaining step is to sign and send the transaction. -As mentioned above, funding the IPRPC pool requires a fee derived from the monthly minimum IPRPC cost. Because of that, all IPRPC over IBC requests are considered a pending IPRPC fund request until the fee is paid. To view the current pending requests, users can use the `pending-ibc-iprpc-funds` query and cover the minimum cost using the `cover-ibc-iprpc-fund-cost` transaction. The expiration of pending IPRPC fund requests is dictated by the `IbcIprpcExpiration`, which equals the expiration period in months. +As mentioned above, funding the IPRPC pool requires a fee derived from the monthly minimum IPRPC cost. Consequently, all IPRPC over IBC requests are considered pending IPRPC fund requests until the fee is paid. To view the current pending requests, users can use the `pending-ibc-iprpc-funds` query and cover the minimum cost using the `cover-ibc-iprpc-fund-cost` transaction. The expiration of pending IPRPC fund requests is dictated by the `IbcIprpcExpiration`, which equals the expiration period in months. ## Parameters diff --git a/x/rewards/ibc_middleware.go b/x/rewards/ibc_middleware.go index 233cc4b6cd..888c4f6d39 100644 --- a/x/rewards/ibc_middleware.go +++ b/x/rewards/ibc_middleware.go @@ -105,9 +105,9 @@ func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet return im.app.OnRecvPacket(ctx, packet, relayer) } else if errors.Is(err, types.ErrIprpcMemoInvalid) { // memo is in the right format of IPRPC over IBC but the data is invalid - utils.LavaFormatWarning("rewards module IBC middleware processing failed, memo data is invalid", err, + detailedErr := utils.LavaFormatWarning("rewards module IBC middleware processing failed, memo data is invalid", err, utils.LogAttr("memo", memo)) - return channeltypes.NewErrorAcknowledgement(err) + return channeltypes.NewErrorAcknowledgement(detailedErr) } // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress @@ -197,10 +197,17 @@ func (im IBCMiddleware) SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Cap // WriteAcknowledgement is called when handling async acks. Since the OnRecvPacket code returns on a nil ack (which indicates // that an async ack will occur), funds can stay stuck in the IbcIprpcReceiver account (which is a temp account that should -// not hold funds). This code simply does the missing functionally that OnRecvPacket would do if the ack was not nil. +// not hold funds). This code simply does the missing functionality that OnRecvPacket would do if the ack was not nil. func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.PacketI, ack exported.Acknowledgement, ) error { + details := []utils.Attribute{ + utils.LogAttr("src_channel", packet.GetSourceChannel()), + utils.LogAttr("src_port", packet.GetSourcePort()), + utils.LogAttr("dest_channel", packet.GetDestChannel()), + utils.LogAttr("dest_port", packet.GetDestPort()), + utils.LogAttr("sequence", packet.GetSequence()), + } err := im.keeper.WriteAcknowledgement(ctx, chanCap, packet, ack) if err != nil { return err @@ -209,22 +216,19 @@ func (im IBCMiddleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilit // unmarshal the packet's data with the transfer module codec (expect an ibc-transfer packet) var data transfertypes.FungibleTokenPacketData if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return err + return utils.LavaFormatError("WriteAcknowledgement: unmarshal ibc transfer packet failed", err, details...) } // sanity check: validate data + details = append(details, utils.LogAttr("data", data)) if err := data.ValidateBasic(); err != nil { - return utils.LavaFormatError("handling async transfer packet failed", err, - utils.LogAttr("data", data), - ) + return utils.LavaFormatError("WriteAcknowledgement: handling async transfer packet failed", err, details...) } // get the IBC tokens that were transferred to the IbcIprpcReceiverAddress amount, ok := sdk.NewIntFromString(data.Amount) if !ok { - return utils.LavaFormatError("handling async transfer packet failed", fmt.Errorf("invalid amount in ibc-transfer data"), - utils.LogAttr("data", data), - ) + return utils.LavaFormatError("WriteAcknowledgement: handling async transfer packet failed", fmt.Errorf("invalid amount in ibc-transfer data"), details...) } ibcTokens := transfertypes.GetTransferCoin(packet.GetDestPort(), packet.GetDestChannel(), data.Denom, amount) diff --git a/x/rewards/types/ibc_iprpc.go b/x/rewards/types/ibc_iprpc.go index 35535c717e..9bfa813d15 100644 --- a/x/rewards/types/ibc_iprpc.go +++ b/x/rewards/types/ibc_iprpc.go @@ -2,6 +2,7 @@ package types import ( "encoding/json" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -12,6 +13,9 @@ func (im IprpcMemo) IsEqual(other IprpcMemo) bool { } func CreateIprpcMemo(creator string, spec string, duration uint64) (memoStr string, err error) { + if creator == "" || spec == "" { + return "", fmt.Errorf("CreateIprpcMemo: creator and spec cannot be empty") + } memo := IprpcMemo{ Creator: creator, Spec: spec, From 894dfeec49443fdb4f713cf9b036c8567795532d Mon Sep 17 00:00:00 2001 From: Oren Date: Thu, 29 Aug 2024 14:15:28 +0300 Subject: [PATCH 16/16] CNS-960: lint fix --- x/rewards/keeper/ibc_iprpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index 3450d49bc4..6f4e2b50ea 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -73,7 +73,7 @@ func (k Keeper) validateIprpcMemo(ctx sdk.Context, memo types.IprpcMemo) error { } func printInvalidMemoWarning(memo types.IprpcMemo, description string) error { - utils.LavaFormatWarning("invalid ibc over iprpc memo", fmt.Errorf(description), + utils.LavaFormatWarning("invalid ibc over iprpc memo", fmt.Errorf("%s", description), utils.LogAttr("memo", memo.String()), ) return types.ErrIprpcMemoInvalid