diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6f83dd706..2b3c94f1fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#7689](https://github.com/osmosis-labs/osmosis/pull/7689) Make CL price estimations not cause state writes (speed and gas improvements) * [#7745](https://github.com/osmosis-labs/osmosis/pull/7745) Add gauge id query to stargate whitelist * [#7747](https://github.com/osmosis-labs/osmosis/pull/7747) Remove redundant call to incentive collection in CL position withdrawal logic +* [#7746](https://github.com/osmosis-labs/osmosis/pull/7746) Make forfeited incentives redeposit into the pool instead of sending to community pool * [#7785](https://github.com/osmosis-labs/osmosis/pull/7785) Remove reward claiming during position transfers ## v23.0.8-iavl-v1 & v23.0.8 diff --git a/go.mod b/go.mod index 83fab0fb1b2..538e4bb67ba 100644 --- a/go.mod +++ b/go.mod @@ -256,7 +256,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/websocket v1.5.0 // indirect + github.com/gorilla/websocket v1.5.0 github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect diff --git a/proto/osmosis/bridge/v1beta1/bridge.proto b/proto/osmosis/bridge/v1beta1/bridge.proto index 761a12eaf09..5787bcaa897 100644 --- a/proto/osmosis/bridge/v1beta1/bridge.proto +++ b/proto/osmosis/bridge/v1beta1/bridge.proto @@ -54,4 +54,32 @@ enum AssetStatus { ASSET_STATUS_BLOCKED_INBOUND = 2; ASSET_STATUS_BLOCKED_OUTBOUND = 3; ASSET_STATUS_BLOCKED_BOTH = 4; +} + +// InboundTransfer is a representation of the inbound transfer. +message InboundTransfer { + // ExternalId is a unique ID of the transfer coming from outside. + // Serves the purpose of uniquely identifying the transfer in another chain + // (e.g., this might be the BTC tx hash). + string external_id = 1 [ (gogoproto.moretags) = "yaml:\"external_id\"" ]; + // DestAddr is a destination Osmosis address + string dest_addr = 2 [ (gogoproto.moretags) = "yaml:\"dest_addr\"" ]; + // AssetID is the ID of the asset being transferred + AssetID asset_id = 3 [ + (gogoproto.moretags) = "yaml:\"asset_id\"", + (gogoproto.nullable) = false + ]; + // Amount of coins to transfer + string amount = 4 [ + (gogoproto.moretags) = "yaml:\"amount\"", + (gogoproto.customtype) = "cosmossdk.io/math.Int", + (gogoproto.nullable) = false + ]; + // Voters is a list of validators signed this transfer + repeated string voters = 5 [ (gogoproto.moretags) = "yaml:\"voters\"" ]; + // Finalized indicates whether the transfer needs more votes or has + // already accumulated a sufficient number. The finalised flag is set + // to true as soon as length(voters) is greater than or equal to + // the module's param votes_needed. + bool finalized = 6 [ (gogoproto.moretags) = "yaml:\"finalized\"" ]; } \ No newline at end of file diff --git a/proto/osmosis/bridge/v1beta1/tx.proto b/proto/osmosis/bridge/v1beta1/tx.proto index cf0f38489cb..82dd475c494 100644 --- a/proto/osmosis/bridge/v1beta1/tx.proto +++ b/proto/osmosis/bridge/v1beta1/tx.proto @@ -34,17 +34,21 @@ service Msg { message MsgInboundTransfer { option (amino.name) = "osmosis/bridge/inbound-transfer"; + // ExternalId is a unique ID of the transfer coming from outside. + // Serves the purpose of uniquely identifying the transfer in another chain + // (e.g., this might be the BTC tx hash) + string external_id = 1 [ (gogoproto.moretags) = "yaml:\"external_id\"" ]; // Sender is a sender's address - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + string sender = 2 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; // DestAddr is a destination Osmosis address - string dest_addr = 2 [ (gogoproto.moretags) = "yaml:\"dest_addr\"" ]; + string dest_addr = 3 [ (gogoproto.moretags) = "yaml:\"dest_addr\"" ]; // AssetID is the ID of the asset being transferred - AssetID asset_id = 3 [ + AssetID asset_id = 4 [ (gogoproto.moretags) = "yaml:\"asset_id\"", (gogoproto.nullable) = false ]; // Amount of coins to transfer - string amount = 4 [ + string amount = 5 [ (gogoproto.moretags) = "yaml:\"amount\"", (gogoproto.customtype) = "cosmossdk.io/math.Int", (gogoproto.nullable) = false diff --git a/x/bridge/images/inbound_transfer.png b/x/bridge/images/inbound_transfer.png new file mode 100644 index 00000000000..1f95cc38f00 Binary files /dev/null and b/x/bridge/images/inbound_transfer.png differ diff --git a/x/bridge/images/inbound_transfer.puml b/x/bridge/images/inbound_transfer.puml new file mode 100644 index 00000000000..6aea3b296db --- /dev/null +++ b/x/bridge/images/inbound_transfer.puml @@ -0,0 +1,98 @@ +@startuml + +actor "Client" as client +node "BTC vault" as vault + +client --> vault : send BTC + +folder "Valset" as valset1 { + cloud "Validator 1" as val1 #lightgreen + cloud "Validator 2" as val2 + cloud "Validator 3" as val3 #lightgreen + cloud "Validator 4" as val4 + cloud "Validator 5" as val5 #lightgreen +} + +note bottom of valset1 + Green validators are + running x/bridge observers. + + Let's say that we need **three** + votes to process the transfer. +end note + +folder "Valset" as valset2 { + cloud "Validator 1" as val1_2 #lightgreen + cloud "Validator 2" as val2_2 + cloud "Validator 3" as val3_2 #lightgreen + cloud "Validator 4" as val4_2 + cloud "Validator 5" as val5_2 #lightgreen +} + +vault <-- val1 : observe +vault <-- val3 : observe +vault <-- val5 : observe + +node "Chain proposer" as proposer + +val1 --> proposer : MsgInboundTransfer +val3 --> proposer : MsgInboundTransfer +val5 --> proposer : MsgInboundTransfer + +json Block { + "1":"MsgInboundTransfer", + "2":"MsgInboundTransfer", + "3":"MsgInboundTransfer" +} + +proposer --> Block : forms a block to process + +Block <-- val1_2 : process +Block <-- val2_2 : process +Block <-- val3_2 : process +Block <-- val4_2 : process +Block <-- val5_2 : process + +action "Process each\nMsgInboundTransfer" as val1_act_1 +action "Process each\nMsgInboundTransfer" as val2_act_1 +action "Process each\nMsgInboundTransfer" as val3_act_1 +action "Process each\nMsgInboundTransfer" as val4_act_1 +action "Process each\nMsgInboundTransfer" as val5_act_1 + +val1_2 --> val1_act_1 +val2_2 --> val2_act_1 +val3_2 --> val3_act_1 +val4_2 --> val4_act_1 +val5_2 --> val5_act_1 + +action "Accumulate votes x3" as val1_act_2 +action "Is not a part\nof the signers set" as val2_act_2 #red +action "Accumulate votes x3" as val3_act_2 +action "Is not a part\nof the signers set" as val4_act_2 #red +action "Accumulate votes x3" as val5_act_2 + +val1_act_1 --> val1_act_2 +val2_act_1 --> val2_act_2 +val3_act_1 --> val3_act_2 +val4_act_1 --> val4_act_2 +val5_act_1 --> val5_act_2 + +action "Call tokenfactory mint" as val1_act_3 +action "Call tokenfactory mint" as val3_act_3 +action "Call tokenfactory mint" as val5_act_3 + +val1_act_2 --> val1_act_3 +val3_act_2 --> val3_act_3 +val5_act_2 --> val5_act_3 + +node "Submit the block" as consensus + +val1_act_3 --> consensus +val3_act_3 --> consensus +val5_act_3 --> consensus + +note left of consensus + The transfer is done! +end note + +@enduml diff --git a/x/bridge/keeper/keeper.go b/x/bridge/keeper/keeper.go index 4f108fb9dc9..3a7f09c23aa 100644 --- a/x/bridge/keeper/keeper.go +++ b/x/bridge/keeper/keeper.go @@ -5,6 +5,8 @@ import ( "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" @@ -12,6 +14,9 @@ import ( ) type Keeper struct { + storeKey storetypes.StoreKey + cdc codec.BinaryCodec + // paramSpace stores module's params paramSpace paramtypes.Subspace // router is used to access tokenfactory methods @@ -25,6 +30,8 @@ type Keeper struct { // NewKeeper returns a new instance of the x/bridge keeper. func NewKeeper( + storeKey storetypes.StoreKey, + cdc codec.BinaryCodec, paramSpace paramtypes.Subspace, router *baseapp.MsgServiceRouter, accountKeeper types.AccountKeeper, @@ -40,6 +47,8 @@ func NewKeeper( } return Keeper{ + storeKey: storeKey, + cdc: cdc, paramSpace: paramSpace, router: router, accountKeeper: accountKeeper, diff --git a/x/bridge/keeper/msg_server.go b/x/bridge/keeper/msg_server.go index c2810612b1d..245e62f7f94 100644 --- a/x/bridge/keeper/msg_server.go +++ b/x/bridge/keeper/msg_server.go @@ -27,13 +27,18 @@ func (m msgServer) InboundTransfer( goCtx context.Context, msg *types.MsgInboundTransfer, ) (*types.MsgInboundTransferResponse, error) { + err := msg.ValidateBasic() + if err != nil { + return nil, err + } + ctx := sdk.UnwrapSDKContext(goCtx) if !m.k.validateSenderIsSigner(ctx, msg.Sender) { return nil, errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "Sender is not part of the signer set") } - err := m.k.InboundTransfer(ctx, msg.DestAddr, msg.AssetId, msg.Amount) + err = m.k.InboundTransfer(ctx, msg.ExternalId, msg.Sender, msg.DestAddr, msg.AssetId, msg.Amount) if err != nil { return nil, err } @@ -54,11 +59,16 @@ func (m msgServer) OutboundTransfer( goCtx context.Context, msg *types.MsgOutboundTransfer, ) (*types.MsgOutboundTransferResponse, error) { + err := msg.ValidateBasic() + if err != nil { + return nil, err + } + ctx := sdk.UnwrapSDKContext(goCtx) // Don't need to check the signature here since every user could be the sender - err := m.k.OutboundTransfer(ctx, msg.Sender, msg.AssetId, msg.Amount) + err = m.k.OutboundTransfer(ctx, msg.Sender, msg.AssetId, msg.Amount) if err != nil { return nil, err } @@ -81,6 +91,11 @@ func (m msgServer) UpdateParams( goCtx context.Context, msg *types.MsgUpdateParams, ) (*types.MsgUpdateParamsResponse, error) { + err := msg.ValidateBasic() + if err != nil { + return nil, err + } + ctx := sdk.UnwrapSDKContext(goCtx) if msg.Sender != m.k.govModuleAddr { @@ -113,6 +128,11 @@ func (m msgServer) ChangeAssetStatus( goCtx context.Context, msg *types.MsgChangeAssetStatus, ) (*types.MsgChangeAssetStatusResponse, error) { + err := msg.ValidateBasic() + if err != nil { + return nil, err + } + ctx := sdk.UnwrapSDKContext(goCtx) result, err := m.k.ChangeAssetStatus(ctx, msg.AssetId, msg.NewStatus) diff --git a/x/bridge/keeper/transfers.go b/x/bridge/keeper/transfers.go index d3ded2299f9..f5ce65ba7fd 100644 --- a/x/bridge/keeper/transfers.go +++ b/x/bridge/keeper/transfers.go @@ -1,36 +1,128 @@ package keeper import ( + "errors" + "fmt" + "slices" + errorsmod "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/osmosis-labs/osmosis/v23/x/bridge/types" tokenfactorytypes "github.com/osmosis-labs/osmosis/v23/x/tokenfactory/types" ) +var ( + ErrAlreadyFinalized = errors.New("transfer already finalized") + ErrNeedMoreVotes = errors.New("transfer needs more votes") + ErrTransferNotFound = errors.New("transfer info not found") +) + func (k Keeper) InboundTransfer( ctx sdk.Context, + externalID string, + sender string, destAddr string, assetID types.AssetID, amount math.Int, ) error { params := k.GetParams(ctx) + // Check if the asset accepts inbound transfers asset, ok := params.GetAsset(assetID) if !ok { return errorsmod.Wrapf(types.ErrInvalidAssetID, "Asset not found %s", assetID.Name()) } - if !asset.Status.InboundActive() { return errorsmod.Wrapf(types.ErrInvalidAssetStatus, "Inbound transfers are disabled for this asset") } + // Try to finalize the transfer in store + err := k.finalizeInboundTransfer(ctx, externalID, sender, destAddr, assetID, amount, params.VotesNeeded) + switch { + case err == nil: + // Everything is fine, mint! + case errors.Is(err, ErrAlreadyFinalized), errors.Is(err, ErrNeedMoreVotes): + // Expected scenario, just return without minting + return nil + default: + // Unexpected error + return errorsmod.Wrapf(sdkerrors.ErrLogic, "Can't finalize inbound trander: %s", err.Error()) + } + + err = k.mint(ctx, destAddr, assetID, amount) + if err != nil { + return errorsmod.Wrap(types.ErrTokenfactory, err.Error()) + } + + return nil +} + +func (k Keeper) finalizeInboundTransfer( + ctx sdk.Context, + externalID string, + sender string, + destAddr string, + assetID types.AssetID, + amount math.Int, + votesNeeded uint64, +) error { + // Get the transfer info from the store to update it properly + transfer, err := k.GetInboundTransfer(ctx, externalID) + switch { + case err == nil: + case errors.Is(err, ErrTransferNotFound): + // If the transfer is new, then create it + transfer = types.NewInboundTransfer(externalID, destAddr, assetID, amount) + default: + return fmt.Errorf("can't get the transfer info from store: %s", err.Error()) + } + + // Check if the sender has already signed this transfer + if slices.Contains(transfer.Voters, sender) { + return fmt.Errorf("the transfer has already been signed by this sender") + } + + // This variable is used to detect the right moment to process the transfer. + // It indicates if the transfer was already finalized before adding a new voter to the voter list. + alreadyFinalized := transfer.Finalized + + // Add the new voter to the voter list and update the finalization flag + transfer.Voters = append(transfer.Voters, sender) + transfer.Finalized = uint64(len(transfer.Voters)) >= votesNeeded + + // Save the updated transfer info + err = k.UpsertInboundTransfer(ctx, transfer) + if err != nil { + return fmt.Errorf("can't save the transfer to store: %s", err.Error()) + } + + // If the transfer is already finalized, then we only need to add the sender + // to the voter list and return + if alreadyFinalized { + return ErrAlreadyFinalized + } + + // If the transfer is not finalized after adding the new voter, + // then it still needs more votes + if !transfer.Finalized { + return ErrNeedMoreVotes + } + + // If the transfer is not finalized before adding the new voter to the voter list, + // but is finalized after the addition, then it is time to process it + + return nil +} + +func (k Keeper) mint(ctx sdk.Context, destAddr string, assetID types.AssetID, amount math.Int) error { moduleAddr := k.accountKeeper.GetModuleAddress(types.ModuleName) denom, err := tokenfactorytypes.GetTokenDenom(moduleAddr.String(), assetID.Name()) if err != nil { - return errorsmod.Wrapf(types.ErrTokenfactory, "Can't create a tokenfacroty denom for %s", assetID.Name()) + return fmt.Errorf("can't create a tokenfacroty denom for %s: %w", assetID.Name(), err) } msgMint := &tokenfactorytypes.MsgMint{ @@ -41,14 +133,46 @@ func (k Keeper) InboundTransfer( handler := k.router.Handler(msgMint) if handler == nil { - return errorsmod.Wrapf(types.ErrTokenfactory, "Can't route a mint message") + return fmt.Errorf("can't route a mint message") } - // ignore resp since it is empty in this method + // Ignore resp since it is empty in this method _, err = handler(ctx, msgMint) if err != nil { - return errorsmod.Wrapf(types.ErrTokenfactory, "Can't execute a mint message: %s", err) + return fmt.Errorf("can't execute a mint message for %s: %w", assetID.Name(), err) + } + + return nil +} + +// GetInboundTransfer returns the transfer by the external id. +func (k Keeper) GetInboundTransfer(ctx sdk.Context, externalID string) (types.InboundTransfer, error) { + store := ctx.KVStore(k.storeKey) + b := store.Get(types.InboundTransferKey(externalID)) + if b == nil { + return types.InboundTransfer{}, ErrTransferNotFound + } + + var inboundTransfer types.InboundTransfer + err := k.cdc.Unmarshal(b, &inboundTransfer) + if err != nil { + return types.InboundTransfer{}, errors.New("can't unmarshal the inbound transfer") + } + + return inboundTransfer, nil +} + +// UpsertInboundTransfer updates or inserts the inbound transfer depending on +// whether it is already presented in the store or not. +func (k Keeper) UpsertInboundTransfer(ctx sdk.Context, t types.InboundTransfer) error { + store := ctx.KVStore(k.storeKey) + key := types.InboundTransferKey(t.ExternalId) + + value, err := k.cdc.Marshal(&t) + if err != nil { + return errors.New("can't marshal the inbound transfer") } + store.Set(key, value) return nil } diff --git a/x/bridge/types/bridge.pb.go b/x/bridge/types/bridge.pb.go index f9ea4b76b24..697c2d3c1a1 100644 --- a/x/bridge/types/bridge.pb.go +++ b/x/bridge/types/bridge.pb.go @@ -248,11 +248,101 @@ func (m *Asset) GetExponent() uint64 { return 0 } +// InboundTransfer is a representation of the inbound transfer. +type InboundTransfer struct { + // ExternalId is a unique ID of the transfer coming from outside. + // Serves the purpose of uniquely identifying the transfer in another chain + // (e.g., this might be the BTC tx hash). + ExternalId string `protobuf:"bytes,1,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" yaml:"external_id"` + // DestAddr is a destination Osmosis address + DestAddr string `protobuf:"bytes,2,opt,name=dest_addr,json=destAddr,proto3" json:"dest_addr,omitempty" yaml:"dest_addr"` + // AssetID is the ID of the asset being transferred + AssetId AssetID `protobuf:"bytes,3,opt,name=asset_id,json=assetId,proto3" json:"asset_id" yaml:"asset_id"` + // Amount of coins to transfer + Amount cosmossdk_io_math.Int `protobuf:"bytes,4,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount" yaml:"amount"` + // Voters is a list of validators signed this transfer + Voters []string `protobuf:"bytes,5,rep,name=voters,proto3" json:"voters,omitempty" yaml:"voters"` + // Finalized indicates whether the transfer needs more votes or has + // already accumulated a sufficient number. The finalised flag is set + // to true as soon as length(voters) is greater than or equal to + // the module's param votes_needed. + Finalized bool `protobuf:"varint,6,opt,name=finalized,proto3" json:"finalized,omitempty" yaml:"finalized"` +} + +func (m *InboundTransfer) Reset() { *m = InboundTransfer{} } +func (m *InboundTransfer) String() string { return proto.CompactTextString(m) } +func (*InboundTransfer) ProtoMessage() {} +func (*InboundTransfer) Descriptor() ([]byte, []int) { + return fileDescriptor_f999ddf08452f1f3, []int{3} +} +func (m *InboundTransfer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InboundTransfer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InboundTransfer.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 *InboundTransfer) XXX_Merge(src proto.Message) { + xxx_messageInfo_InboundTransfer.Merge(m, src) +} +func (m *InboundTransfer) XXX_Size() int { + return m.Size() +} +func (m *InboundTransfer) XXX_DiscardUnknown() { + xxx_messageInfo_InboundTransfer.DiscardUnknown(m) +} + +var xxx_messageInfo_InboundTransfer proto.InternalMessageInfo + +func (m *InboundTransfer) GetExternalId() string { + if m != nil { + return m.ExternalId + } + return "" +} + +func (m *InboundTransfer) GetDestAddr() string { + if m != nil { + return m.DestAddr + } + return "" +} + +func (m *InboundTransfer) GetAssetId() AssetID { + if m != nil { + return m.AssetId + } + return AssetID{} +} + +func (m *InboundTransfer) GetVoters() []string { + if m != nil { + return m.Voters + } + return nil +} + +func (m *InboundTransfer) GetFinalized() bool { + if m != nil { + return m.Finalized + } + return false +} + func init() { proto.RegisterEnum("osmosis.bridge.v1beta1.AssetStatus", AssetStatus_name, AssetStatus_value) proto.RegisterType((*Params)(nil), "osmosis.bridge.v1beta1.Params") proto.RegisterType((*AssetID)(nil), "osmosis.bridge.v1beta1.AssetID") proto.RegisterType((*Asset)(nil), "osmosis.bridge.v1beta1.Asset") + proto.RegisterType((*InboundTransfer)(nil), "osmosis.bridge.v1beta1.InboundTransfer") } func init() { @@ -260,42 +350,53 @@ func init() { } var fileDescriptor_f999ddf08452f1f3 = []byte{ - // 559 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x93, 0x4f, 0x6e, 0xda, 0x40, - 0x18, 0xc5, 0xb1, 0x49, 0x48, 0x33, 0xa4, 0x09, 0x99, 0xf4, 0x8f, 0xdb, 0x06, 0x9b, 0x4e, 0xa4, - 0x0a, 0x55, 0xad, 0x2d, 0xc8, 0x2e, 0x3b, 0x8c, 0xa9, 0x8a, 0x82, 0x20, 0xb2, 0x61, 0xd3, 0x0d, - 0x32, 0xf6, 0xc4, 0x58, 0x8d, 0x3d, 0x88, 0x31, 0x28, 0x1c, 0xa0, 0xfb, 0x1e, 0xa2, 0x87, 0xc9, - 0xa2, 0x8b, 0x2c, 0xab, 0x2e, 0xac, 0x0a, 0x6e, 0xe0, 0x13, 0x54, 0x9e, 0x31, 0x2d, 0x91, 0x50, - 0x76, 0x7e, 0xf3, 0x7e, 0xef, 0x7d, 0x9f, 0xc6, 0x1a, 0x70, 0x46, 0x68, 0x40, 0xa8, 0x4f, 0xb5, - 0xd1, 0xd4, 0x77, 0x3d, 0xac, 0xcd, 0x6b, 0x23, 0x1c, 0xd9, 0xb5, 0x4c, 0xaa, 0x93, 0x29, 0x89, - 0x08, 0x7c, 0x91, 0x41, 0x6a, 0x76, 0x9a, 0x41, 0xaf, 0x9f, 0x79, 0xc4, 0x23, 0x0c, 0xd1, 0xd2, - 0x2f, 0x4e, 0xa3, 0x6f, 0x22, 0x28, 0x5c, 0xd9, 0x53, 0x3b, 0xa0, 0xf0, 0x03, 0xd8, 0xa3, 0xbe, - 0x17, 0xe2, 0x29, 0x95, 0x84, 0x4a, 0xbe, 0xba, 0xaf, 0xc3, 0x24, 0x56, 0x0e, 0x17, 0x76, 0x70, - 0x73, 0x81, 0x32, 0x03, 0x99, 0x6b, 0x04, 0x76, 0x40, 0xc1, 0xa6, 0x14, 0x47, 0x54, 0x12, 0x2b, - 0xf9, 0x6a, 0xb1, 0x5e, 0x56, 0xb7, 0xcf, 0x55, 0x1b, 0x29, 0xa5, 0x3f, 0xbf, 0x8b, 0x95, 0x5c, - 0x12, 0x2b, 0x4f, 0x79, 0x1f, 0x8f, 0x22, 0x33, 0xeb, 0x80, 0x17, 0xe0, 0x60, 0x4e, 0x22, 0x4c, - 0x87, 0x21, 0xc6, 0x2e, 0x76, 0xa5, 0x7c, 0x45, 0xa8, 0xee, 0xe8, 0x2f, 0x93, 0x58, 0x39, 0xe1, - 0x81, 0x4d, 0x17, 0x99, 0x45, 0x26, 0xbb, 0x4c, 0xc1, 0x26, 0xc8, 0x5f, 0x63, 0x2c, 0xed, 0x54, - 0x84, 0xea, 0xbe, 0x5e, 0x4b, 0xe7, 0xfc, 0x8e, 0x95, 0x37, 0x0e, 0x5b, 0x87, 0xba, 0x5f, 0x55, - 0x9f, 0x68, 0x81, 0x1d, 0x8d, 0xd5, 0x0e, 0xf6, 0x6c, 0x67, 0x61, 0x60, 0x27, 0x89, 0x15, 0xc0, - 0x5b, 0xaf, 0x31, 0x46, 0x66, 0x9a, 0x46, 0x01, 0xd8, 0x63, 0x8b, 0xb6, 0x8d, 0x74, 0x17, 0x4a, - 0x66, 0x53, 0x07, 0x0f, 0x9d, 0xb1, 0xed, 0x87, 0x92, 0xc0, 0x8a, 0x37, 0x76, 0xd9, 0x74, 0x91, - 0x59, 0xe4, 0xb2, 0x99, 0x2a, 0xf8, 0x0e, 0xec, 0xba, 0x38, 0x24, 0x81, 0x24, 0xb2, 0x50, 0x29, - 0x89, 0x95, 0x03, 0x1e, 0x62, 0xc7, 0xc8, 0xe4, 0x36, 0xfa, 0x29, 0x80, 0x5d, 0x36, 0x0f, 0xea, - 0x40, 0xf4, 0x5d, 0x36, 0xa3, 0x58, 0x57, 0x1e, 0xbd, 0xc3, 0xb6, 0xa1, 0x1f, 0x67, 0xb7, 0xb8, - 0xcf, 0x3b, 0x7d, 0x17, 0x99, 0xa2, 0xef, 0xc2, 0x2e, 0x28, 0xd0, 0xc8, 0x8e, 0x66, 0x94, 0x8d, - 0x3d, 0xac, 0x9f, 0x3d, 0xda, 0x63, 0x31, 0x54, 0x3f, 0xfe, 0xff, 0x37, 0x78, 0x18, 0x99, 0x59, - 0x0b, 0xd4, 0xc0, 0x13, 0x7c, 0x3b, 0x21, 0x21, 0x0e, 0xa3, 0xec, 0x4f, 0x9c, 0x24, 0xb1, 0x72, - 0xc4, 0xe1, 0xb5, 0x83, 0xcc, 0x7f, 0xd0, 0xfb, 0x1f, 0x02, 0x28, 0x6e, 0x74, 0xc3, 0x53, 0x20, - 0x35, 0x2c, 0xab, 0xd5, 0x1f, 0x5a, 0xfd, 0x46, 0x7f, 0x60, 0x0d, 0x07, 0x5d, 0xeb, 0xaa, 0xd5, - 0x6c, 0x7f, 0x6a, 0xb7, 0x8c, 0x52, 0x0e, 0x9e, 0x80, 0xa3, 0x07, 0x6e, 0xef, 0xb2, 0x24, 0xc0, - 0x0a, 0x38, 0x7d, 0x70, 0xa8, 0x77, 0x7a, 0xcd, 0xcb, 0x96, 0x31, 0x6c, 0x77, 0xf5, 0xde, 0xa0, - 0x6b, 0x94, 0x44, 0xf8, 0x16, 0x94, 0xb7, 0x12, 0xbd, 0x41, 0x9f, 0x23, 0x79, 0x58, 0x06, 0xaf, - 0xb6, 0x22, 0x7a, 0xaf, 0xff, 0xb9, 0xb4, 0xa3, 0x77, 0xee, 0x96, 0xb2, 0x70, 0xbf, 0x94, 0x85, - 0x3f, 0x4b, 0x59, 0xf8, 0xbe, 0x92, 0x73, 0xf7, 0x2b, 0x39, 0xf7, 0x6b, 0x25, 0xe7, 0xbe, 0xd4, - 0x3d, 0x3f, 0x1a, 0xcf, 0x46, 0xaa, 0x43, 0x02, 0x2d, 0xbb, 0xbb, 0x8f, 0x37, 0xf6, 0x88, 0xae, - 0x85, 0x36, 0xaf, 0x9f, 0x6b, 0xb7, 0xeb, 0x77, 0x17, 0x2d, 0x26, 0x98, 0x8e, 0x0a, 0xec, 0x05, - 0x9d, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x14, 0x36, 0xb7, 0x96, 0x03, 0x00, 0x00, + // 726 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0xcf, 0x6f, 0xda, 0x48, + 0x14, 0xc6, 0x90, 0x10, 0x18, 0xb2, 0x81, 0x4c, 0x7e, 0x79, 0x77, 0x13, 0xcc, 0x4e, 0xa4, 0x15, + 0xbb, 0x6a, 0x6d, 0x41, 0x0e, 0x95, 0x72, 0xc3, 0x40, 0x54, 0x2b, 0x08, 0x22, 0x03, 0x97, 0x5e, + 0xd0, 0xc0, 0x0c, 0xc4, 0x2a, 0xb6, 0x23, 0x8f, 0x89, 0x92, 0xde, 0x7b, 0xef, 0x1f, 0xd1, 0x3f, + 0x26, 0x87, 0x1e, 0x72, 0xac, 0x7a, 0xb0, 0xaa, 0xe4, 0x3f, 0xf0, 0xad, 0xb7, 0xca, 0xe3, 0x21, + 0x71, 0xd4, 0x28, 0xea, 0x6d, 0xde, 0x7c, 0xdf, 0xfb, 0xde, 0xf3, 0x7b, 0x9f, 0x07, 0x1c, 0xba, + 0xcc, 0x76, 0x99, 0xc5, 0xb4, 0xb1, 0x67, 0x91, 0x19, 0xd5, 0x2e, 0x6b, 0x63, 0xea, 0xe3, 0x9a, + 0x08, 0xd5, 0x0b, 0xcf, 0xf5, 0x5d, 0xb8, 0x2b, 0x48, 0xaa, 0xb8, 0x15, 0xa4, 0xbf, 0xb6, 0x67, + 0xee, 0xcc, 0xe5, 0x14, 0x2d, 0x3a, 0xc5, 0x6c, 0xf4, 0x31, 0x0d, 0xb2, 0x67, 0xd8, 0xc3, 0x36, + 0x83, 0xaf, 0xc0, 0x1a, 0xb3, 0x66, 0x0e, 0xf5, 0x98, 0x2c, 0x55, 0x32, 0xd5, 0xbc, 0x0e, 0xc3, + 0x40, 0xd9, 0xb8, 0xc6, 0xf6, 0xfc, 0x18, 0x09, 0x00, 0x99, 0x4b, 0x0a, 0xec, 0x80, 0x2c, 0x66, + 0x8c, 0xfa, 0x4c, 0x4e, 0x57, 0x32, 0xd5, 0x42, 0xfd, 0x40, 0x7d, 0xbe, 0xae, 0xda, 0x88, 0x58, + 0xfa, 0xce, 0x4d, 0xa0, 0xa4, 0xc2, 0x40, 0xf9, 0x23, 0xd6, 0x8b, 0x53, 0x91, 0x29, 0x34, 0xe0, + 0x31, 0x58, 0xbf, 0x74, 0x7d, 0xca, 0x46, 0x0e, 0xa5, 0x84, 0x12, 0x39, 0x53, 0x91, 0xaa, 0x2b, + 0xfa, 0x5e, 0x18, 0x28, 0x5b, 0x71, 0x42, 0x12, 0x45, 0x66, 0x81, 0x87, 0x5d, 0x1e, 0xc1, 0x26, + 0xc8, 0x4c, 0x29, 0x95, 0x57, 0x2a, 0x52, 0x35, 0xaf, 0xd7, 0xa2, 0x3a, 0xdf, 0x02, 0xe5, 0xef, + 0x09, 0x6f, 0x87, 0x91, 0xf7, 0xaa, 0xe5, 0x6a, 0x36, 0xf6, 0xcf, 0xd5, 0x0e, 0x9d, 0xe1, 0xc9, + 0x75, 0x8b, 0x4e, 0xc2, 0x40, 0x01, 0xb1, 0xea, 0x94, 0x52, 0x64, 0x46, 0xd9, 0xc8, 0x06, 0x6b, + 0xbc, 0x51, 0xa3, 0x15, 0xf5, 0xc2, 0xdc, 0x85, 0x37, 0xa1, 0xa3, 0xc9, 0x39, 0xb6, 0x1c, 0x59, + 0xe2, 0xc2, 0x89, 0x5e, 0x92, 0x28, 0x32, 0x0b, 0x71, 0xd8, 0x8c, 0x22, 0xf8, 0x2f, 0x58, 0x25, + 0xd4, 0x71, 0x6d, 0x39, 0xcd, 0x93, 0x4a, 0x61, 0xa0, 0xac, 0xc7, 0x49, 0xfc, 0x1a, 0x99, 0x31, + 0x8c, 0xbe, 0x48, 0x60, 0x95, 0xd7, 0x83, 0x3a, 0x48, 0x5b, 0x84, 0xd7, 0x28, 0xd4, 0x95, 0x17, + 0x67, 0x68, 0xb4, 0xf4, 0x4d, 0x31, 0xc5, 0x7c, 0xac, 0x69, 0x11, 0x64, 0xa6, 0x2d, 0x02, 0xbb, + 0x20, 0xcb, 0x7c, 0xec, 0x2f, 0x18, 0x2f, 0xbb, 0x51, 0x3f, 0x7c, 0x51, 0xa7, 0xcf, 0xa9, 0xfa, + 0xe6, 0xe3, 0x36, 0xe2, 0x64, 0x64, 0x0a, 0x15, 0xa8, 0x81, 0x1c, 0xbd, 0xba, 0x70, 0x1d, 0xea, + 0xf8, 0x62, 0x13, 0x5b, 0x61, 0xa0, 0x14, 0x63, 0xf2, 0x12, 0x41, 0xe6, 0x03, 0x09, 0xfd, 0x48, + 0x83, 0xa2, 0xe1, 0x8c, 0xdd, 0x85, 0x43, 0x06, 0x1e, 0x76, 0xd8, 0x94, 0x7a, 0xf0, 0x0d, 0x28, + 0xd0, 0x2b, 0x9f, 0x7a, 0x0e, 0x9e, 0x8f, 0xc4, 0x17, 0xe6, 0xf5, 0xdd, 0x30, 0x50, 0xe0, 0x52, + 0xe7, 0x01, 0x44, 0x26, 0x58, 0x46, 0x06, 0x81, 0x35, 0x90, 0x27, 0x94, 0xf9, 0x23, 0x4c, 0x88, + 0x27, 0xe6, 0xb8, 0x1d, 0x06, 0x4a, 0x69, 0x39, 0x47, 0x01, 0x21, 0x33, 0x17, 0x9d, 0x1b, 0x84, + 0x78, 0xb0, 0x0f, 0x72, 0xdc, 0x48, 0x51, 0xa1, 0xcc, 0xef, 0x8d, 0x72, 0x4f, 0x8c, 0xb2, 0x98, + 0x30, 0x24, 0x6f, 0x65, 0x8d, 0x1f, 0x0d, 0x02, 0x4f, 0x40, 0x16, 0xdb, 0xee, 0xc2, 0xf1, 0x85, + 0xb5, 0x54, 0x61, 0xad, 0x9d, 0x5f, 0xad, 0x65, 0x38, 0x7e, 0xc2, 0xdb, 0x3c, 0x29, 0xf2, 0x36, + 0x3f, 0xc0, 0xff, 0x40, 0x36, 0xb2, 0xab, 0xc7, 0xe4, 0x55, 0xfe, 0x5b, 0x25, 0x06, 0x1f, 0xdf, + 0x23, 0x53, 0x10, 0x60, 0x1d, 0xe4, 0xa7, 0x96, 0x83, 0xe7, 0xd6, 0x07, 0x4a, 0xe4, 0x6c, 0x45, + 0xaa, 0xe6, 0x92, 0x9f, 0xfe, 0x00, 0x21, 0xf3, 0x91, 0xf6, 0xff, 0x67, 0x09, 0x14, 0x12, 0x7b, + 0x85, 0xfb, 0x40, 0x6e, 0xf4, 0xfb, 0xed, 0xc1, 0xa8, 0x3f, 0x68, 0x0c, 0x86, 0xfd, 0xd1, 0xb0, + 0xdb, 0x3f, 0x6b, 0x37, 0x8d, 0x13, 0xa3, 0xdd, 0x2a, 0xa5, 0xe0, 0x16, 0x28, 0x3e, 0x41, 0x7b, + 0xa7, 0x25, 0x09, 0x56, 0xc0, 0xfe, 0x93, 0x4b, 0xbd, 0xd3, 0x6b, 0x9e, 0xb6, 0x5b, 0x23, 0xa3, + 0xab, 0xf7, 0x86, 0xdd, 0x56, 0x29, 0x0d, 0xff, 0x01, 0x07, 0xcf, 0x32, 0x7a, 0xc3, 0x41, 0x4c, + 0xc9, 0xc0, 0x03, 0xf0, 0xe7, 0xb3, 0x14, 0xbd, 0x37, 0x78, 0x5b, 0x5a, 0xd1, 0x3b, 0x37, 0x77, + 0x65, 0xe9, 0xf6, 0xae, 0x2c, 0x7d, 0xbf, 0x2b, 0x4b, 0x9f, 0xee, 0xcb, 0xa9, 0xdb, 0xfb, 0x72, + 0xea, 0xeb, 0x7d, 0x39, 0xf5, 0xae, 0x3e, 0xb3, 0xfc, 0xf3, 0xc5, 0x58, 0x9d, 0xb8, 0xb6, 0x26, + 0x96, 0xf6, 0x7a, 0x8e, 0xc7, 0x6c, 0x19, 0x68, 0x97, 0xf5, 0x23, 0xed, 0x6a, 0xf9, 0xe6, 0xf9, + 0xd7, 0x17, 0x94, 0x8d, 0xb3, 0xfc, 0xf5, 0x3a, 0xfa, 0x19, 0x00, 0x00, 0xff, 0xff, 0xf1, 0x2a, + 0x6d, 0xe8, 0x12, 0x05, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -439,6 +540,82 @@ func (m *Asset) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *InboundTransfer) 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 *InboundTransfer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InboundTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Finalized { + i-- + if m.Finalized { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if len(m.Voters) > 0 { + for iNdEx := len(m.Voters) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Voters[iNdEx]) + copy(dAtA[i:], m.Voters[iNdEx]) + i = encodeVarintBridge(dAtA, i, uint64(len(m.Voters[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintBridge(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.AssetId.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBridge(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.DestAddr) > 0 { + i -= len(m.DestAddr) + copy(dAtA[i:], m.DestAddr) + i = encodeVarintBridge(dAtA, i, uint64(len(m.DestAddr))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExternalId) > 0 { + i -= len(m.ExternalId) + copy(dAtA[i:], m.ExternalId) + i = encodeVarintBridge(dAtA, i, uint64(len(m.ExternalId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintBridge(dAtA []byte, offset int, v uint64) int { offset -= sovBridge(v) base := offset @@ -510,6 +687,36 @@ func (m *Asset) Size() (n int) { return n } +func (m *InboundTransfer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExternalId) + if l > 0 { + n += 1 + l + sovBridge(uint64(l)) + } + l = len(m.DestAddr) + if l > 0 { + n += 1 + l + sovBridge(uint64(l)) + } + l = m.AssetId.Size() + n += 1 + l + sovBridge(uint64(l)) + l = m.Amount.Size() + n += 1 + l + sovBridge(uint64(l)) + if len(m.Voters) > 0 { + for _, s := range m.Voters { + l = len(s) + n += 1 + l + sovBridge(uint64(l)) + } + } + if m.Finalized { + n += 2 + } + return n +} + func sovBridge(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -920,6 +1127,239 @@ func (m *Asset) Unmarshal(dAtA []byte) error { } return nil } +func (m *InboundTransfer) 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 ErrIntOverflowBridge + } + 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: InboundTransfer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InboundTransfer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExternalId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + 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 ErrInvalidLengthBridge + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBridge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExternalId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + 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 ErrInvalidLengthBridge + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBridge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetId", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBridge + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBridge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AssetId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + 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 ErrInvalidLengthBridge + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBridge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Voters", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + 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 ErrInvalidLengthBridge + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBridge + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Voters = append(m.Voters, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Finalized", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBridge + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Finalized = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipBridge(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBridge + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBridge(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/bridge/types/errors.go b/x/bridge/types/errors.go index b524eeb5fb0..7982c3f52b0 100644 --- a/x/bridge/types/errors.go +++ b/x/bridge/types/errors.go @@ -20,4 +20,5 @@ var ( ErrTokenfactory = errorsmod.Register(ModuleName, 11, "tokenfactory error") ErrInvalidAssetID = errorsmod.Register(ModuleName, 12, "invalid asset id") ErrInvalidFee = errorsmod.Register(ModuleName, 13, "invalid fee") + ErrInvalidExternalID = errorsmod.Register(ModuleName, 14, "invalid external id") ) diff --git a/x/bridge/types/helpers_test.go b/x/bridge/types/helpers_test.go index f1d1d63c025..c6c794d53ab 100644 --- a/x/bridge/types/helpers_test.go +++ b/x/bridge/types/helpers_test.go @@ -10,6 +10,8 @@ import ( ) var ( + externalID = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + pk1 = ed25519.GenPrivKey().PubKey() addr1Bytes = sdk.AccAddress(pk1.Address()) addr1 = addr1Bytes.String() diff --git a/x/bridge/types/keys.go b/x/bridge/types/keys.go index 8f641a9222d..d5dfb484f1e 100644 --- a/x/bridge/types/keys.go +++ b/x/bridge/types/keys.go @@ -13,3 +13,11 @@ const ( // QuerierRoute defines the module's query routing key QuerierRoute = ModuleName ) + +var InboundTransfersKey = []byte{0x01} + +// InboundTransferKey returns the store prefix key where all the data +// associated with a specific InboundTransfer is stored +func InboundTransferKey(externalID string) []byte { + return append(InboundTransfersKey, []byte(externalID)...) +} diff --git a/x/bridge/types/msgs.go b/x/bridge/types/msgs.go index ec822a191a5..ecfa8104aa8 100644 --- a/x/bridge/types/msgs.go +++ b/x/bridge/types/msgs.go @@ -10,20 +10,26 @@ import ( var _ sdk.Msg = &MsgInboundTransfer{} func NewMsgInboundTransfer( + externalID string, sender string, destAddr string, assetID AssetID, amount math.Int, ) *MsgInboundTransfer { return &MsgInboundTransfer{ - Sender: sender, - DestAddr: destAddr, - AssetId: assetID, - Amount: amount, + ExternalId: externalID, + Sender: sender, + DestAddr: destAddr, + AssetId: assetID, + Amount: amount, } } func (m MsgInboundTransfer) ValidateBasic() error { + if len(m.ExternalId) == 0 { + return errorsmod.Wrapf(ErrInvalidExternalID, "Empty external id") + } + _, err := sdk.AccAddressFromBech32(m.Sender) if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) diff --git a/x/bridge/types/msgs_test.go b/x/bridge/types/msgs_test.go index daae7d99d54..3d4ad2392c7 100644 --- a/x/bridge/types/msgs_test.go +++ b/x/bridge/types/msgs_test.go @@ -21,10 +21,11 @@ func TestAuthzMsg(t *testing.T) { { name: "MsgInboundTransfer", msg: &types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: addr1, + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(100), }, }, { @@ -75,10 +76,11 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "valid", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: addr1, + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(100), }, expectedSigners: []sdk.AccAddress{addr1Bytes}, expectedErr: nil, @@ -86,10 +88,11 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "empty sender", msg: types.MsgInboundTransfer{ - Sender: "", - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: "", + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(100), }, expectedSigners: []sdk.AccAddress{sdk.AccAddress("")}, expectedErr: sdkerrors.ErrInvalidAddress, @@ -97,21 +100,35 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "invalid sender", msg: types.MsgInboundTransfer{ - Sender: "qwerty", - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: "qwerty", + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(100), }, expectedSigners: []sdk.AccAddress{nil}, expectedErr: sdkerrors.ErrInvalidAddress, }, + { + name: "empty external id", + msg: types.MsgInboundTransfer{ + ExternalId: "", + Sender: addr1, + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(100), + }, + expectedSigners: []sdk.AccAddress{addr1Bytes}, + expectedErr: types.ErrInvalidExternalID, + }, { name: "empty destination addr", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: "", - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: addr1, + DestAddr: "", + AssetId: assetID1, + Amount: math.NewInt(100), }, expectedSigners: []sdk.AccAddress{addr1Bytes}, expectedErr: sdkerrors.ErrInvalidAddress, @@ -119,10 +136,11 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "invalid destination addr", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: "qwerty", - AssetId: assetID1, - Amount: math.NewInt(100), + ExternalId: externalID, + Sender: addr1, + DestAddr: "qwerty", + AssetId: assetID1, + Amount: math.NewInt(100), }, expectedSigners: []sdk.AccAddress{addr1Bytes}, expectedErr: sdkerrors.ErrInvalidAddress, @@ -130,8 +148,9 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "invalid asset id", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: addr2, + ExternalId: externalID, + Sender: addr1, + DestAddr: addr2, AssetId: types.AssetID{ SourceChain: "", Denom: "btc", @@ -144,10 +163,11 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "zero amount", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(0), + ExternalId: externalID, + Sender: addr1, + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(0), }, expectedSigners: []sdk.AccAddress{addr1Bytes}, expectedErr: sdkerrors.ErrInvalidCoins, @@ -155,10 +175,11 @@ func TestMsgInboundTransfer(t *testing.T) { { name: "negative amount", msg: types.MsgInboundTransfer{ - Sender: addr1, - DestAddr: addr2, - AssetId: assetID1, - Amount: math.NewInt(-100), + ExternalId: externalID, + Sender: addr1, + DestAddr: addr2, + AssetId: assetID1, + Amount: math.NewInt(-100), }, expectedSigners: []sdk.AccAddress{addr1Bytes}, expectedErr: sdkerrors.ErrInvalidCoins, diff --git a/x/bridge/types/transfers.go b/x/bridge/types/transfers.go new file mode 100644 index 00000000000..23900e810f1 --- /dev/null +++ b/x/bridge/types/transfers.go @@ -0,0 +1,21 @@ +package types + +import ( + "cosmossdk.io/math" +) + +func NewInboundTransfer( + externalID string, + destAddr string, + assetID AssetID, + amount math.Int, +) InboundTransfer { + return InboundTransfer{ + ExternalId: externalID, + DestAddr: destAddr, + AssetId: assetID, + Amount: amount, + Voters: make([]string, 0), + Finalized: false, + } +} diff --git a/x/bridge/types/tx.pb.go b/x/bridge/types/tx.pb.go index 8091fdccc6f..d7f45b9ca58 100644 --- a/x/bridge/types/tx.pb.go +++ b/x/bridge/types/tx.pb.go @@ -38,14 +38,18 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // on Osmosis in return. The tokens are minted through the x/tokenfactory module // to the destination address. type MsgInboundTransfer struct { + // ExternalId is a unique ID of the transfer coming from outside. + // Serves the purpose of uniquely identifying the transfer in another chain + // (e.g., this might be the BTC tx hash) + ExternalId string `protobuf:"bytes,1,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" yaml:"external_id"` // Sender is a sender's address - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` // DestAddr is a destination Osmosis address - DestAddr string `protobuf:"bytes,2,opt,name=dest_addr,json=destAddr,proto3" json:"dest_addr,omitempty" yaml:"dest_addr"` + DestAddr string `protobuf:"bytes,3,opt,name=dest_addr,json=destAddr,proto3" json:"dest_addr,omitempty" yaml:"dest_addr"` // AssetID is the ID of the asset being transferred - AssetId AssetID `protobuf:"bytes,3,opt,name=asset_id,json=assetId,proto3" json:"asset_id" yaml:"asset_id"` + AssetId AssetID `protobuf:"bytes,4,opt,name=asset_id,json=assetId,proto3" json:"asset_id" yaml:"asset_id"` // Amount of coins to transfer - Amount cosmossdk_io_math.Int `protobuf:"bytes,4,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount" yaml:"amount"` + Amount cosmossdk_io_math.Int `protobuf:"bytes,5,opt,name=amount,proto3,customtype=cosmossdk.io/math.Int" json:"amount" yaml:"amount"` } func (m *MsgInboundTransfer) Reset() { *m = MsgInboundTransfer{} } @@ -81,6 +85,13 @@ func (m *MsgInboundTransfer) XXX_DiscardUnknown() { var xxx_messageInfo_MsgInboundTransfer proto.InternalMessageInfo +func (m *MsgInboundTransfer) GetExternalId() string { + if m != nil { + return m.ExternalId + } + return "" +} + func (m *MsgInboundTransfer) GetSender() string { if m != nil { return m.Sender @@ -453,49 +464,51 @@ func init() { func init() { proto.RegisterFile("osmosis/bridge/v1beta1/tx.proto", fileDescriptor_8e478e3238c885a8) } var fileDescriptor_8e478e3238c885a8 = []byte{ - // 660 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x95, 0x4f, 0x6f, 0xd3, 0x3e, - 0x18, 0xc7, 0x9b, 0xee, 0xa7, 0xfd, 0x36, 0x0f, 0xd8, 0x1a, 0x36, 0xd6, 0x85, 0x91, 0x8c, 0x0c, - 0xb4, 0x31, 0x68, 0xa2, 0x65, 0x9c, 0x7a, 0x5b, 0x41, 0x48, 0x95, 0xa8, 0x40, 0x1d, 0x48, 0xc0, - 0x65, 0x72, 0x6a, 0x93, 0x46, 0x2c, 0x76, 0x89, 0xdd, 0xfd, 0x79, 0x0b, 0x9c, 0x38, 0xf3, 0x2a, - 0xb8, 0x20, 0xc4, 0x3b, 0xd8, 0x71, 0xdc, 0x10, 0x87, 0x0a, 0x6d, 0x07, 0xee, 0x7b, 0x05, 0x28, - 0xb6, 0xd3, 0x6e, 0xe9, 0x5a, 0xad, 0x12, 0x47, 0x2e, 0x55, 0x9a, 0x7c, 0x9e, 0xef, 0xf3, 0xf8, - 0xe3, 0xb8, 0x05, 0x16, 0x65, 0x11, 0x65, 0x21, 0x73, 0xfd, 0x38, 0x44, 0x01, 0x76, 0x77, 0xd7, - 0x7d, 0xcc, 0xe1, 0xba, 0xcb, 0xf7, 0x9d, 0x56, 0x4c, 0x39, 0xd5, 0x6f, 0x28, 0xc0, 0x91, 0x80, - 0xa3, 0x00, 0x63, 0x36, 0xa0, 0x01, 0x15, 0x88, 0x9b, 0x5c, 0x49, 0xda, 0x28, 0xc0, 0x28, 0x24, - 0xd4, 0x15, 0x9f, 0xea, 0x96, 0xd9, 0x10, 0x09, 0xae, 0x0f, 0x59, 0x2f, 0xbe, 0x41, 0x43, 0xa2, - 0x9e, 0x2f, 0x0f, 0x98, 0x40, 0xf5, 0x13, 0x90, 0xfd, 0x25, 0x0f, 0xf4, 0x1a, 0x0b, 0xaa, 0xc4, - 0xa7, 0x6d, 0x82, 0x5e, 0xc4, 0x90, 0xb0, 0xb7, 0x38, 0xd6, 0xef, 0x81, 0x71, 0x86, 0x09, 0xc2, - 0x71, 0x51, 0x5b, 0xd2, 0x56, 0x27, 0x2b, 0x85, 0xd3, 0x8e, 0x75, 0xf5, 0x00, 0x46, 0x3b, 0x65, - 0x5b, 0xde, 0xb7, 0xeb, 0x0a, 0xd0, 0xd7, 0xc1, 0x24, 0xc2, 0x8c, 0x6f, 0x43, 0x84, 0xe2, 0x62, - 0x5e, 0xd0, 0xb3, 0xa7, 0x1d, 0x6b, 0x46, 0xd2, 0xdd, 0x47, 0x76, 0x7d, 0x22, 0xb9, 0xde, 0x44, - 0x28, 0xd6, 0xb7, 0xc0, 0x04, 0x64, 0x0c, 0xf3, 0xed, 0x10, 0x15, 0xc7, 0x96, 0xb4, 0xd5, 0x29, - 0xcf, 0x72, 0x2e, 0xb6, 0xe1, 0x6c, 0x26, 0x5c, 0xf5, 0x71, 0x65, 0xfe, 0xb0, 0x63, 0xe5, 0x4e, - 0x3b, 0xd6, 0xb4, 0x8c, 0x4d, 0xcb, 0xed, 0xfa, 0xff, 0xe2, 0xb2, 0x8a, 0xf4, 0x27, 0x60, 0x1c, - 0x46, 0xb4, 0x4d, 0x78, 0xf1, 0x3f, 0x31, 0x84, 0x93, 0x54, 0xfc, 0xec, 0x58, 0x73, 0x52, 0x13, - 0x43, 0xef, 0x9c, 0x90, 0xba, 0x11, 0xe4, 0x4d, 0xa7, 0x4a, 0x78, 0x6f, 0x3d, 0xb2, 0xc8, 0xae, - 0xab, 0xea, 0xf2, 0x9d, 0x0f, 0xbf, 0x3f, 0xaf, 0x65, 0x77, 0x2f, 0x94, 0x7e, 0x4a, 0x5c, 0x09, - 0xb2, 0x17, 0x81, 0xd1, 0xaf, 0xad, 0x8e, 0x59, 0x8b, 0x12, 0x86, 0xed, 0xaf, 0x79, 0x70, 0xbd, - 0xc6, 0x82, 0x67, 0x6d, 0xfe, 0x4f, 0xab, 0xd2, 0x7a, 0x37, 0xd1, 0xba, 0x94, 0xd1, 0x4a, 0x95, - 0xa0, 0x9e, 0xd7, 0x5b, 0xe0, 0xe6, 0x05, 0xe2, 0xba, 0x62, 0xbf, 0x69, 0x60, 0xba, 0xc6, 0x82, - 0x97, 0x2d, 0x04, 0x39, 0x7e, 0x0e, 0x63, 0x18, 0xb1, 0x51, 0xa4, 0xbe, 0x02, 0x80, 0xe0, 0xbd, - 0xed, 0x96, 0x28, 0x14, 0x56, 0xa7, 0x3c, 0x73, 0x90, 0x23, 0x19, 0x5f, 0x59, 0x50, 0x8a, 0x0a, - 0x32, 0xb2, 0x57, 0x6f, 0xd7, 0x27, 0x09, 0xde, 0x93, 0x54, 0xf9, 0x76, 0xb2, 0xbc, 0xc5, 0xcc, - 0xf2, 0xda, 0x62, 0xcc, 0x92, 0xc2, 0x17, 0xc0, 0x7c, 0x66, 0xf4, 0xee, 0xb2, 0x3e, 0xe5, 0xc1, - 0x6c, 0x8d, 0x05, 0x8f, 0x9a, 0x90, 0x04, 0x58, 0xec, 0xcd, 0x16, 0x87, 0xbc, 0x3d, 0xd2, 0xda, - 0xce, 0xee, 0x7e, 0xfe, 0x6f, 0xed, 0xfe, 0x6b, 0x29, 0x8c, 0x89, 0x69, 0xc4, 0x4b, 0x75, 0xcd, - 0x5b, 0x1e, 0x1a, 0x2b, 0x07, 0xaf, 0xcc, 0x9d, 0x37, 0x26, 0x03, 0xa4, 0x31, 0x49, 0x94, 0x57, - 0x12, 0x63, 0x76, 0xc6, 0x58, 0x43, 0x18, 0x28, 0x89, 0xf6, 0x25, 0x55, 0x64, 0x82, 0xc5, 0x8b, - 0xdc, 0xa4, 0xf2, 0xbc, 0xef, 0x63, 0x60, 0xac, 0xc6, 0x02, 0xfd, 0x3d, 0x98, 0xce, 0xfe, 0x8c, - 0xad, 0x0d, 0x1a, 0xb5, 0xff, 0xec, 0x1a, 0xde, 0xe5, 0xd9, 0xb4, 0xb5, 0xce, 0xc1, 0x4c, 0xdf, - 0x19, 0xbf, 0x3f, 0x24, 0x27, 0x0b, 0x1b, 0x1b, 0x23, 0xc0, 0xdd, 0xae, 0x4d, 0x70, 0xe5, 0xdc, - 0x01, 0x58, 0x19, 0x12, 0x72, 0x16, 0x34, 0xdc, 0x4b, 0x82, 0xdd, 0x4e, 0x7b, 0xa0, 0xd0, 0xff, - 0x4e, 0x3e, 0x18, 0x92, 0xd2, 0x47, 0x1b, 0x0f, 0x47, 0xa1, 0xd3, 0xc6, 0x95, 0xa7, 0x87, 0xc7, - 0xa6, 0x76, 0x74, 0x6c, 0x6a, 0xbf, 0x8e, 0x4d, 0xed, 0xe3, 0x89, 0x99, 0x3b, 0x3a, 0x31, 0x73, - 0x3f, 0x4e, 0xcc, 0xdc, 0x1b, 0x2f, 0x08, 0x79, 0xb3, 0xed, 0x3b, 0x0d, 0x1a, 0xb9, 0x2a, 0xb9, - 0xb4, 0x03, 0x7d, 0x96, 0x7e, 0x71, 0x77, 0xbd, 0x0d, 0x77, 0x3f, 0x7d, 0x9f, 0xf8, 0x41, 0x0b, - 0x33, 0x7f, 0x5c, 0xfc, 0xd7, 0x6d, 0xfc, 0x09, 0x00, 0x00, 0xff, 0xff, 0x63, 0x99, 0xf4, 0x4f, - 0x94, 0x07, 0x00, 0x00, + // 702 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x95, 0x4d, 0x6f, 0xd3, 0x30, + 0x18, 0xc7, 0x9b, 0x76, 0x8c, 0xcd, 0x03, 0xb6, 0x86, 0xbd, 0x74, 0x65, 0x24, 0x23, 0x03, 0x6d, + 0x0c, 0x9a, 0x68, 0x19, 0x12, 0x52, 0x6f, 0x2b, 0x08, 0xa9, 0x12, 0x15, 0x28, 0x03, 0x09, 0xb8, + 0x4c, 0x4e, 0x63, 0xd2, 0x88, 0xc5, 0x2e, 0xb1, 0xbb, 0x97, 0xaf, 0xc0, 0x89, 0x33, 0x9f, 0x82, + 0x1b, 0xe2, 0x1b, 0xec, 0x38, 0x6e, 0x88, 0x43, 0x85, 0xb6, 0x03, 0x37, 0x0e, 0xfd, 0x04, 0x28, + 0xb6, 0xd3, 0x6e, 0xe9, 0x56, 0xad, 0x62, 0x97, 0xca, 0x89, 0xff, 0xcf, 0xff, 0x79, 0x9e, 0xdf, + 0x63, 0x37, 0x40, 0x27, 0x34, 0x24, 0x34, 0xa0, 0x96, 0x1b, 0x05, 0x9e, 0x8f, 0xac, 0x9d, 0x35, + 0x17, 0x31, 0xb8, 0x66, 0xb1, 0x3d, 0xb3, 0x19, 0x11, 0x46, 0xd4, 0x59, 0x29, 0x30, 0x85, 0xc0, + 0x94, 0x82, 0xe2, 0xb4, 0x4f, 0x7c, 0xc2, 0x25, 0x56, 0xbc, 0x12, 0xea, 0x62, 0x1e, 0x86, 0x01, + 0x26, 0x16, 0xff, 0x95, 0xaf, 0xb4, 0x3a, 0x77, 0xb0, 0x5c, 0x48, 0x7b, 0xf6, 0x75, 0x12, 0x60, + 0xb9, 0xbf, 0x74, 0x4e, 0x05, 0x32, 0x1f, 0x17, 0x19, 0x7f, 0xb3, 0x40, 0xad, 0x51, 0xbf, 0x8a, + 0x5d, 0xd2, 0xc2, 0xde, 0xab, 0x08, 0x62, 0xfa, 0x1e, 0x45, 0xea, 0x63, 0x30, 0x81, 0xf6, 0x18, + 0x8a, 0x30, 0xdc, 0xde, 0x0a, 0xbc, 0x82, 0xb2, 0xa8, 0xac, 0x8c, 0x57, 0x66, 0x3b, 0x6d, 0x5d, + 0xdd, 0x87, 0xe1, 0x76, 0xd9, 0x38, 0xb1, 0x69, 0x38, 0x20, 0x79, 0xaa, 0x7a, 0xea, 0x7d, 0x30, + 0x4a, 0x11, 0xf6, 0x50, 0x54, 0xc8, 0xf2, 0x98, 0x7c, 0xa7, 0xad, 0x5f, 0x17, 0x31, 0xe2, 0xbd, + 0xe1, 0x48, 0x81, 0xba, 0x06, 0xc6, 0x3d, 0x44, 0xd9, 0x16, 0xf4, 0xbc, 0xa8, 0x90, 0xe3, 0xea, + 0xe9, 0x4e, 0x5b, 0x9f, 0x12, 0xea, 0xee, 0x96, 0xe1, 0x8c, 0xc5, 0xeb, 0x0d, 0xcf, 0x8b, 0xd4, + 0x4d, 0x30, 0x06, 0x29, 0x45, 0x2c, 0xae, 0x69, 0x64, 0x51, 0x59, 0x99, 0xb0, 0x75, 0xf3, 0x6c, + 0x8c, 0xe6, 0x46, 0xac, 0xab, 0x3e, 0xad, 0xcc, 0x1d, 0xb4, 0xf5, 0x4c, 0xa7, 0xad, 0x4f, 0x0a, + 0xdb, 0x24, 0xdc, 0x70, 0xae, 0xf2, 0x65, 0xd5, 0x53, 0x9f, 0x81, 0x51, 0x18, 0x92, 0x16, 0x66, + 0x85, 0x2b, 0xbc, 0x08, 0x33, 0x8e, 0xf8, 0xd5, 0xd6, 0x67, 0x04, 0x5f, 0xea, 0x7d, 0x30, 0x03, + 0x62, 0x85, 0x90, 0x35, 0xcc, 0x2a, 0x66, 0xbd, 0x7e, 0x44, 0x90, 0xe1, 0xc8, 0xe8, 0xf2, 0xdd, + 0x4f, 0x7f, 0xbe, 0xae, 0xa6, 0xc7, 0x1e, 0x08, 0xb0, 0x25, 0x26, 0xc9, 0x1a, 0x0b, 0xa0, 0xd8, + 0xcf, 0xdb, 0x41, 0xb4, 0x49, 0x30, 0x45, 0xc6, 0xb7, 0x2c, 0xb8, 0x59, 0xa3, 0xfe, 0x8b, 0x16, + 0x3b, 0x3d, 0x8f, 0x1e, 0x56, 0x65, 0x28, 0xac, 0xd9, 0xa1, 0xb1, 0xe6, 0x2e, 0x1f, 0xeb, 0xc8, + 0x7f, 0x61, 0xbd, 0x17, 0x63, 0x5d, 0x4c, 0x61, 0x25, 0x12, 0x50, 0x8f, 0xeb, 0x6d, 0x70, 0xeb, + 0x0c, 0x70, 0x5d, 0xb0, 0xdf, 0x15, 0x30, 0x59, 0xa3, 0xfe, 0xeb, 0xa6, 0x07, 0x19, 0x7a, 0x09, + 0x23, 0x18, 0xd2, 0x61, 0xa0, 0xbe, 0x01, 0x00, 0xa3, 0xdd, 0xad, 0x26, 0x0f, 0xe4, 0x54, 0x27, + 0x6c, 0xed, 0x3c, 0x46, 0xc2, 0xbe, 0x32, 0x2f, 0x11, 0xe5, 0x85, 0x65, 0x2f, 0xde, 0x70, 0xc6, + 0x31, 0xda, 0x15, 0xaa, 0xf2, 0x9d, 0xb8, 0xbd, 0x85, 0x54, 0x7b, 0x2d, 0x5e, 0x66, 0x49, 0xca, + 0xe7, 0xc1, 0x5c, 0xaa, 0xf4, 0x6e, 0x5b, 0x5f, 0xb2, 0x60, 0xba, 0x46, 0xfd, 0x27, 0x0d, 0x88, + 0x7d, 0xc4, 0x67, 0xb3, 0xc9, 0x20, 0x6b, 0x0d, 0xd5, 0xdb, 0xc9, 0xe9, 0x67, 0x2f, 0x6b, 0xfa, + 0x6f, 0x05, 0x30, 0xca, 0xab, 0xe1, 0x87, 0xea, 0x86, 0xbd, 0x34, 0xd0, 0x56, 0x14, 0x5e, 0x99, + 0x39, 0x4d, 0x4c, 0x18, 0x08, 0x62, 0x42, 0x51, 0x5e, 0x8e, 0x89, 0x19, 0x29, 0x62, 0x75, 0x4e, + 0xa0, 0xc4, 0xd3, 0x97, 0x64, 0x90, 0x06, 0x16, 0xce, 0x62, 0x93, 0xc0, 0xb3, 0x7f, 0xe4, 0x40, + 0xae, 0x46, 0x7d, 0xf5, 0x23, 0x98, 0x4c, 0xff, 0xff, 0xad, 0x9e, 0x57, 0x6a, 0xff, 0xdd, 0x2d, + 0xda, 0x17, 0xd7, 0x26, 0xa9, 0x55, 0x06, 0xa6, 0xfa, 0xee, 0xf8, 0x83, 0x01, 0x3e, 0x69, 0x71, + 0x71, 0x7d, 0x08, 0x71, 0x37, 0x6b, 0x03, 0x5c, 0x3b, 0x75, 0x01, 0x96, 0x07, 0x98, 0x9c, 0x14, + 0x16, 0xad, 0x0b, 0x0a, 0xbb, 0x99, 0x76, 0x41, 0xbe, 0xff, 0x4c, 0x3e, 0x1c, 0xe0, 0xd2, 0xa7, + 0x2e, 0x3e, 0x1a, 0x46, 0x9d, 0x24, 0xae, 0x3c, 0x3f, 0x38, 0xd2, 0x94, 0xc3, 0x23, 0x4d, 0xf9, + 0x7d, 0xa4, 0x29, 0x9f, 0x8f, 0xb5, 0xcc, 0xe1, 0xb1, 0x96, 0xf9, 0x79, 0xac, 0x65, 0xde, 0xd9, + 0x7e, 0xc0, 0x1a, 0x2d, 0xd7, 0xac, 0x93, 0xd0, 0x92, 0xce, 0xa5, 0x6d, 0xe8, 0xd2, 0xe4, 0xc1, + 0xda, 0xb1, 0xd7, 0xad, 0xbd, 0xe4, 0x3c, 0xb1, 0xfd, 0x26, 0xa2, 0xee, 0x28, 0xff, 0x48, 0xae, + 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xdd, 0x20, 0xbe, 0x38, 0xcd, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -723,7 +736,7 @@ func (m *MsgInboundTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a { size, err := m.AssetId.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -733,19 +746,26 @@ func (m *MsgInboundTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintTx(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x1a + dAtA[i] = 0x22 if len(m.DestAddr) > 0 { i -= len(m.DestAddr) copy(dAtA[i:], m.DestAddr) i = encodeVarintTx(dAtA, i, uint64(len(m.DestAddr))) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x1a } if len(m.Sender) > 0 { i -= len(m.Sender) copy(dAtA[i:], m.Sender) i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) i-- + dAtA[i] = 0x12 + } + if len(m.ExternalId) > 0 { + i -= len(m.ExternalId) + copy(dAtA[i:], m.ExternalId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ExternalId))) + i-- dAtA[i] = 0xa } return len(dAtA) - i, nil @@ -1002,6 +1022,10 @@ func (m *MsgInboundTransfer) Size() (n int) { } var l int _ = l + l = len(m.ExternalId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } l = len(m.Sender) if l > 0 { n += 1 + l + sovTx(uint64(l)) @@ -1143,6 +1167,38 @@ func (m *MsgInboundTransfer) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExternalId", 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.ExternalId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) } @@ -1174,7 +1230,7 @@ func (m *MsgInboundTransfer) Unmarshal(dAtA []byte) error { } m.Sender = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DestAddr", wireType) } @@ -1206,7 +1262,7 @@ func (m *MsgInboundTransfer) Unmarshal(dAtA []byte) error { } m.DestAddr = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AssetId", wireType) } @@ -1239,7 +1295,7 @@ func (m *MsgInboundTransfer) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) } diff --git a/x/concentrated-liquidity/export_test.go b/x/concentrated-liquidity/export_test.go index b05f018040e..f2c1cf8120b 100644 --- a/x/concentrated-liquidity/export_test.go +++ b/x/concentrated-liquidity/export_test.go @@ -258,7 +258,7 @@ func (k Keeper) GetAllIncentiveRecordsForUptime(ctx sdk.Context, poolId uint64, return k.getAllIncentiveRecordsForUptime(ctx, poolId, minUptime) } -func (k Keeper) CollectIncentives(ctx sdk.Context, owner sdk.AccAddress, positionId uint64) (sdk.Coins, sdk.Coins, error) { +func (k Keeper) CollectIncentives(ctx sdk.Context, owner sdk.AccAddress, positionId uint64) (sdk.Coins, sdk.Coins, []sdk.Coins, error) { return k.collectIncentives(ctx, owner, positionId) } @@ -270,7 +270,7 @@ func UpdateAccumAndClaimRewards(accum *accum.AccumulatorObject, positionKey stri return updateAccumAndClaimRewards(accum, positionKey, growthOutside) } -func (k Keeper) PrepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, error) { +func (k Keeper) PrepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, []sdk.Coins, error) { return k.prepareClaimAllIncentivesForPosition(ctx, positionId) } @@ -355,3 +355,11 @@ func ComputeTotalIncentivesToEmit(timeElapsedSeconds osmomath.Dec, emissionRate func (k Keeper) GetIncentiveScalingFactorForPool(ctx sdk.Context, poolID uint64) (osmomath.Dec, error) { return k.getIncentiveScalingFactorForPool(ctx, poolID) } + +func ScaleDownIncentiveAmount(incentiveAmount osmomath.Int, scalingFactor osmomath.Dec) (scaledTotalEmittedAmount osmomath.Int) { + return scaleDownIncentiveAmount(incentiveAmount, scalingFactor) +} + +func (k Keeper) RedepositForfeitedIncentives(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, scaledForfeitedIncentivesByUptime []sdk.Coins, totalForefeitedIncentives sdk.Coins) error { + return k.redepositForfeitedIncentives(ctx, poolId, owner, scaledForfeitedIncentivesByUptime, totalForefeitedIncentives) +} diff --git a/x/concentrated-liquidity/incentives.go b/x/concentrated-liquidity/incentives.go index 2b57752db08..8b6fca1a84e 100644 --- a/x/concentrated-liquidity/incentives.go +++ b/x/concentrated-liquidity/incentives.go @@ -753,16 +753,16 @@ func moveRewardsToNewPositionAndDeleteOldAcc(accum *accum.AccumulatorObject, old // The parent function (collectIncentives) does the actual bank sends for both the collected and forfeited incentives. // // Returns error if the position/uptime accumulators don't exist, or if there is an issue that arises while claiming. -func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, error) { +func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, []sdk.Coins, error) { // Retrieve the position with the given ID. position, err := k.GetPosition(ctx, positionId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } err = k.UpdatePoolUptimeAccumulatorsToNow(ctx, position.PoolId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // Compute the age of the position. @@ -770,19 +770,19 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId // Should never happen, defense in depth. if positionAge < 0 { - return sdk.Coins{}, sdk.Coins{}, types.NegativeDurationError{Duration: positionAge} + return sdk.Coins{}, sdk.Coins{}, nil, types.NegativeDurationError{Duration: positionAge} } // Retrieve the uptime accumulators for the position's pool. uptimeAccumulators, err := k.GetUptimeAccumulators(ctx, position.PoolId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // Compute uptime growth outside of the range between lower tick and upper tick uptimeGrowthOutside, err := k.GetUptimeGrowthOutsideRange(ctx, position.PoolId, position.LowerTick, position.UpperTick) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // Create a variable to hold the name of the position. @@ -796,10 +796,11 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId incentiveScalingFactor, err := k.getIncentiveScalingFactorForPool(ctx, position.PoolId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // Loop through each uptime accumulator for the pool. + scaledForfeitedIncentivesByUptime := make([]sdk.Coins, len(types.SupportedUptimes)) for uptimeIndex, uptimeAccum := range uptimeAccumulators { // Check if the accumulator contains the position. // There should never be a case where you can have a position for 1 accumulator, and not the rest. @@ -809,7 +810,7 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId if hasPosition { collectedIncentivesForUptimeScaled, _, err := updateAccumAndClaimRewards(uptimeAccum, positionName, uptimeGrowthOutside[uptimeIndex]) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // We scale the uptime per-unit of liquidity accumulator up to avoid truncation to zero. @@ -824,6 +825,12 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId } if positionAge < supportedUptimes[uptimeIndex] { + // We track forfeited incentives by uptime accumulator to allow for efficient redepositing. + // To avoid descaling and rescaling, we keep the forfeited incentives in scaled form. + // This is slightly unwieldy as it means we return a slice of scaled coins, but doing it this way + // allows us to efficiently handle all cases related to forfeited incentives. + scaledForfeitedIncentivesByUptime[uptimeIndex] = collectedIncentivesForUptimeScaled + // If the age of the position is less than the current uptime we are iterating through, then the position's // incentives are forfeited to the community pool. The parent function does the actual bank send. forfeitedIncentivesForPosition = forfeitedIncentivesForPosition.Add(collectedIncentivesForUptime...) @@ -835,13 +842,83 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId } } - return collectedIncentivesForPosition, forfeitedIncentivesForPosition, nil + return collectedIncentivesForPosition, forfeitedIncentivesForPosition, scaledForfeitedIncentivesByUptime, nil +} + +// redepositForfeitedIncentives handles logic for redepositing forfeited incentives for a given pool. +// Specifically, it implements the following flows: +// - If there is no remaining active liquidity, the forfeited incentives are sent back to the sender. +// - If there is active liquidity, the forfeited incentives are redeposited into the uptime accumulators. +// Since forfeits are already being tracked in "scaled form", we do not need to do any additional scaling +// and simply deposit amount / activeLiquidity into the uptime accumulators. +// +// Returns error if: +// * Pool with the given ID does not exist +// * Uptime accumulators for the pool cannot be retrieved +// * Forfeited incentives length does not match the supported uptimes (defense in depth, should never happen) +// * Bank send fails +func (k Keeper) redepositForfeitedIncentives(ctx sdk.Context, poolId uint64, sender sdk.AccAddress, scaledForfeitedIncentivesByUptime []sdk.Coins, totalForefeitedIncentives sdk.Coins) error { + if len(scaledForfeitedIncentivesByUptime) != len(types.SupportedUptimes) { + return types.InvalidForfeitedIncentivesLengthError{ForfeitedIncentivesLength: len(scaledForfeitedIncentivesByUptime), ExpectedLength: len(types.SupportedUptimes)} + } + + // Fetch pool from state to check active liquidity. + pool, err := k.getPoolById(ctx, poolId) + if err != nil { + return err + } + activeLiquidity := pool.GetLiquidity() + + // If no active liquidity, give the forfeited incentives to the sender. + if activeLiquidity.LT(sdk.OneDec()) { + err := k.bankKeeper.SendCoins(ctx, pool.GetIncentivesAddress(), sender, totalForefeitedIncentives) + if err != nil { + return err + } + return nil + } + + // If pool has active liquidity on current tick, redeposit forfeited incentives into uptime accumulators. + uptimeAccums, err := k.GetUptimeAccumulators(ctx, poolId) + if err != nil { + return err + } + + // Loop through each uptime accumulator for the pool and redeposit forfeited incentives. + for uptimeIndex := range uptimeAccums { + curUptimeForfeited := scaledForfeitedIncentivesByUptime[uptimeIndex] + if curUptimeForfeited.IsZero() { + continue + } + + // Note that this logic is a simplified version of the regular incentive distribution logic. + // It leans on the fact that the tracked forfeited incentives are already scaled appropriately + // so we do not need to run any additional computations beyond dividing by the active liquidity. + incentivesToAddToCurAccum := sdk.NewDecCoins() + for _, forfeitedCoin := range curUptimeForfeited { + // Calculate the amount to add to the accumulator by dividing the forfeited coin amount by the current uptime duration + forfeitedAmountPerLiquidity := forfeitedCoin.Amount.ToLegacyDec().QuoTruncate(activeLiquidity) + + // Create a DecCoin from the calculated amount + decCoinToAdd := sdk.NewDecCoinFromDec(forfeitedCoin.Denom, forfeitedAmountPerLiquidity) + + // Add the calculated DecCoin to the incentives to add to current accumulator + incentivesToAddToCurAccum = incentivesToAddToCurAccum.Add(decCoinToAdd) + } + + // Emit incentives to current uptime accumulator + uptimeAccums[uptimeIndex].AddToAccumulator(incentivesToAddToCurAccum) + } + + return nil } func (k Keeper) GetClaimableIncentives(ctx sdk.Context, positionId uint64) (sdk.Coins, sdk.Coins, error) { // Since this is a query, we don't want to modify the state and therefore use a cache context. cacheCtx, _ := ctx.CacheContext() - return k.prepareClaimAllIncentivesForPosition(cacheCtx, positionId) + // We omit the by-uptime forfeited incentives slice as it is not needed for this query. + collectedIncentives, forfeitedIncentives, _, err := k.prepareClaimAllIncentivesForPosition(cacheCtx, positionId) + return collectedIncentives, forfeitedIncentives, err } // collectIncentives collects incentives for all uptime accumulators for the specified position id. @@ -850,49 +927,41 @@ func (k Keeper) GetClaimableIncentives(ctx sdk.Context, positionId uint64) (sdk. // Returns error if: // - position with the given id does not exist // - other internal database or math errors. -func (k Keeper) collectIncentives(ctx sdk.Context, sender sdk.AccAddress, positionId uint64) (sdk.Coins, sdk.Coins, error) { +func (k Keeper) collectIncentives(ctx sdk.Context, sender sdk.AccAddress, positionId uint64) (sdk.Coins, sdk.Coins, []sdk.Coins, error) { // Retrieve the position with the given ID. position, err := k.GetPosition(ctx, positionId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } if sender.String() != position.Address { - return sdk.Coins{}, sdk.Coins{}, types.NotPositionOwnerError{ + return sdk.Coins{}, sdk.Coins{}, nil, types.NotPositionOwnerError{ PositionId: positionId, Address: sender.String(), } } // Claim all incentives for the position. - collectedIncentivesForPosition, forfeitedIncentivesForPosition, err := k.prepareClaimAllIncentivesForPosition(ctx, position.PositionId) + collectedIncentivesForPosition, totalForfeitedIncentivesForPosition, scaledAmountForfeitedByUptime, err := k.prepareClaimAllIncentivesForPosition(ctx, position.PositionId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // If no incentives were collected, return an empty coin set. - if collectedIncentivesForPosition.IsZero() && forfeitedIncentivesForPosition.IsZero() { - return collectedIncentivesForPosition, forfeitedIncentivesForPosition, nil + if collectedIncentivesForPosition.IsZero() && totalForfeitedIncentivesForPosition.IsZero() { + return collectedIncentivesForPosition, totalForfeitedIncentivesForPosition, scaledAmountForfeitedByUptime, nil } // Send the collected incentives to the position's owner. pool, err := k.getPoolById(ctx, position.PoolId) if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } // Send the collected incentives to the position's owner from the pool's address. if !collectedIncentivesForPosition.IsZero() { if err := k.bankKeeper.SendCoins(ctx, pool.GetIncentivesAddress(), sender, collectedIncentivesForPosition); err != nil { - return sdk.Coins{}, sdk.Coins{}, err - } - } - - // Send the forfeited incentives to the community pool from the pool's address. - if !forfeitedIncentivesForPosition.IsZero() { - err = k.communityPoolKeeper.FundCommunityPool(ctx, forfeitedIncentivesForPosition, pool.GetIncentivesAddress()) - if err != nil { - return sdk.Coins{}, sdk.Coins{}, err + return sdk.Coins{}, sdk.Coins{}, nil, err } } @@ -904,11 +973,11 @@ func (k Keeper) collectIncentives(ctx sdk.Context, sender sdk.AccAddress, positi sdk.NewAttribute(types.AttributeKeyPoolId, strconv.FormatUint(pool.GetId(), 10)), sdk.NewAttribute(types.AttributeKeyPositionId, strconv.FormatUint(positionId, 10)), sdk.NewAttribute(types.AttributeKeyTokensOut, collectedIncentivesForPosition.String()), - sdk.NewAttribute(types.AttributeKeyForfeitedTokens, forfeitedIncentivesForPosition.String()), + sdk.NewAttribute(types.AttributeKeyForfeitedTokens, totalForfeitedIncentivesForPosition.String()), ), }) - return collectedIncentivesForPosition, forfeitedIncentivesForPosition, nil + return collectedIncentivesForPosition, totalForfeitedIncentivesForPosition, scaledAmountForfeitedByUptime, nil } // createIncentive creates an incentive record in state for the given pool. diff --git a/x/concentrated-liquidity/incentives_test.go b/x/concentrated-liquidity/incentives_test.go index 9d4ee0e9917..c3a4fcd5521 100644 --- a/x/concentrated-liquidity/incentives_test.go +++ b/x/concentrated-liquidity/incentives_test.go @@ -2311,7 +2311,7 @@ func (s *KeeperTestSuite) TestQueryAndCollectIncentives() { s.Require().Equal(tc.expectedIncentivesClaimed, incentivesClaimedQuery) s.Require().Equal(tc.expectedForfeitedIncentives, incentivesForfeitedQuery) } - actualIncentivesClaimed, actualIncetivesForfeited, err := clKeeper.CollectIncentives(s.Ctx, ownerWithValidPosition, DefaultPositionId) + actualIncentivesClaimed, actualIncetivesForfeited, _, err := clKeeper.CollectIncentives(s.Ctx, ownerWithValidPosition, DefaultPositionId) // Assertions s.Require().Equal(incentivesClaimedQuery, actualIncentivesClaimed) @@ -2341,7 +2341,9 @@ func (s *KeeperTestSuite) TestQueryAndCollectIncentives() { s.Require().Equal(tc.expectedForfeitedIncentives.String(), actualIncetivesForfeited.String()) // Ensure balances are updated by the correct amounts - s.Require().Equal(tc.expectedIncentivesClaimed.Add(tc.expectedForfeitedIncentives...).String(), (incentivesBalanceBeforeCollect.Sub(incentivesBalanceAfterCollect...)).String()) + // Note that we expect the forfeited incentives to remain in the pool incentives balance since they are + // redeposited, so we only expect the diff in incentives balance to be the amount successfully claimed. + s.Require().Equal(tc.expectedIncentivesClaimed.String(), (incentivesBalanceBeforeCollect.Sub(incentivesBalanceAfterCollect...)).String()) s.Require().Equal(tc.expectedIncentivesClaimed.String(), (ownerBalancerAfterCollect.Sub(ownerBalancerBeforeCollect...)).String()) }) } @@ -2775,6 +2777,30 @@ func (s *KeeperTestSuite) TestUpdateAccumAndClaimRewards() { }) } +// checkForfeitedCoinsByUptime checks that the sum of forfeited coins by uptime matches the expected total forfeited coins. +// It adds up the Coins corresponding to each uptime in the map and asserts that the result is equal to the input totalForfeitedCoins. +func (s *KeeperTestSuite) checkForfeitedCoinsByUptime(totalForfeitedCoins sdk.Coins, scaledForfeitedCoinsByUptime []sdk.Coins) { + // Exit early if scaledForfeitedCoinsByUptime is empty + if len(scaledForfeitedCoinsByUptime) == 0 { + s.Require().Equal(totalForfeitedCoins, sdk.NewCoins()) + return + } + + forfeitedCoins := sdk.NewCoins() + // Iterate through uptime indexes and add up the forfeited coins from each + // We unfortunately need to iterate through each coin individually to properly scale down the amount + // (doing it in bulk leads to inconsistent rounding error) + for uptimeIndex := range types.SupportedUptimes { + for _, coin := range scaledForfeitedCoinsByUptime[uptimeIndex] { + // Scale down the actual forfeited coin amount + scaledDownAmount := cl.ScaleDownIncentiveAmount(coin.Amount, cl.PerUnitLiqScalingFactor) + forfeitedCoins = forfeitedCoins.Add(sdk.NewCoin(coin.Denom, scaledDownAmount)) + } + } + + s.Require().Equal(totalForfeitedCoins, forfeitedCoins, "Total forfeited coins do not match the sum of forfeited coins by uptime after scaling down") +} + // Note that the non-forfeit cases are thoroughly tested in `TestCollectIncentives` func (s *KeeperTestSuite) TestQueryAndClaimAllIncentives() { uptimeHelper := getExpectedUptimes() @@ -2905,7 +2931,7 @@ func (s *KeeperTestSuite) TestQueryAndClaimAllIncentives() { s.Require().Equal(initSenderBalances, newSenderBalances) s.Require().Equal(initPoolBalances, newPoolBalances) - amountClaimed, amountForfeited, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, tc.positionIdClaim) + amountClaimed, totalAmountForfeited, scaledAmountForfeitedByUptime, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, tc.positionIdClaim) // --- Assertions --- @@ -2914,7 +2940,8 @@ func (s *KeeperTestSuite) TestQueryAndClaimAllIncentives() { newPoolBalances = s.App.BankKeeper.GetAllBalances(s.Ctx, clPool.GetAddress()) s.Require().Equal(amountClaimedQuery, amountClaimed) - s.Require().Equal(amountForfeitedQuery, amountForfeited) + s.Require().Equal(amountForfeitedQuery, totalAmountForfeited) + s.checkForfeitedCoinsByUptime(totalAmountForfeited, scaledAmountForfeitedByUptime) if tc.expectedError != nil { s.Require().ErrorIs(err, tc.expectedError) @@ -2939,7 +2966,7 @@ func (s *KeeperTestSuite) TestQueryAndClaimAllIncentives() { expectedCoins = expectedCoins.Add(sdk.NormalizeCoins(growthInside)...) } s.Require().Equal(expectedCoins.String(), amountClaimed.String()) - s.Require().Equal(sdk.Coins{}, amountForfeited) + s.Require().Equal(sdk.Coins{}, totalAmountForfeited) } // Ensure balances have not been mutated @@ -3078,10 +3105,11 @@ func (s *KeeperTestSuite) TestPrepareClaimAllIncentivesForPosition() { } // System under test - collectedInc, forfeitedIncentives, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionOneData.ID) + collectedInc, totalForfeitedIncentives, scaledForfeitedIncentivesByUptime, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionOneData.ID) s.Require().NoError(err) s.Require().Equal(tc.expectedCoins.String(), collectedInc.String()) - s.Require().Equal(expectedForfeitedIncentives.String(), forfeitedIncentives.String()) + s.Require().Equal(expectedForfeitedIncentives.String(), totalForfeitedIncentives.String()) + s.checkForfeitedCoinsByUptime(totalForfeitedIncentives, scaledForfeitedIncentivesByUptime) // The difference accumulator value should have increased if we forfeited incentives by claiming. uptimeAccumsDiffPostClaim := sdk.NewDecCoins() @@ -3169,7 +3197,7 @@ func (s *KeeperTestSuite) TestFunctional_ClaimIncentives_LiquidityChange_Varying s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(testFullChargeDuration)) // Claim incentives. - collected, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionOneData.ID) + collected, _, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionOneData.ID) s.Require().NoError(err) s.Require().Equal(expectedCoinsPerFullCharge.String(), collected.String()) @@ -3184,13 +3212,13 @@ func (s *KeeperTestSuite) TestFunctional_ClaimIncentives_LiquidityChange_Varying s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(testFullChargeDuration)) // Claim for second position. Must only claim half of the original expected amount since now there are 2 positions. - collected, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionTwoData.ID) + collected, _, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionTwoData.ID) s.Require().NoError(err) s.Require().Equal(expectedHalfOfExpectedCoinsPerFullCharge.String(), collected.String()) // Claim for first position and observe that claims full expected charge for the period between 1st claim and 2nd position creation // and half of the full charge amount since the 2nd position was created. - collected, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionOneData.ID) + collected, _, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, defaultAddress, positionOneData.ID) s.Require().NoError(err) // Note, adding one since both expected amounts already subtract one (-2 in total) s.Require().Equal(expectedCoinsPerFullCharge.Add(expectedHalfOfExpectedCoinsPerFullCharge.Add(oneUUSDCCoin)...).String(), collected.String()) @@ -3667,13 +3695,13 @@ func (s *KeeperTestSuite) TestIncentiveTruncation() { // The check below shows that the incentive is not claimed due to truncation s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Minute * 50)) - incentives, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID) + incentives, _, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID) s.Require().NoError(err) s.Require().True(incentives.IsZero()) // Incentives should now be claimed due to lack of truncation s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Hour * 6)) - incentives, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID) + incentives, _, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID) s.Require().NoError(err) s.Require().False(incentives.IsZero()) } @@ -3766,3 +3794,136 @@ func (s *KeeperTestSuite) scaleUptimeAccumulators(uptimeAccumulatorsToScale []sd return growthCopy } + +// assertUptimeAccumsEmpty asserts that the uptime accumulators for the given pool are empty. +func (s *KeeperTestSuite) assertUptimeAccumsEmpty(poolId uint64) { + uptimeAccums, err := s.App.ConcentratedLiquidityKeeper.GetUptimeAccumulators(s.Ctx, poolId) + s.Require().NoError(err) + + // Ensure uptime accums remain empty + for _, accum := range uptimeAccums { + s.Require().Equal(sdk.NewDecCoins(), sdk.NewDecCoins(accum.GetValue()...)) + } +} + +// TestRedepositForfeitedIncentives tests the redeposit of forfeited incentives into uptime accumulators. +// In the cases where the pool has active liquidity and the incentives need to be redeposited, +// it asserts that forfeitedIncentives / activeLiquidity was deposited into the accumulators. +// In the cases where the pool has no active liquidity, it asserts that the forfeited incentives were sent to the owner. +func (s *KeeperTestSuite) TestRedepositForfeitedIncentives() { + tests := map[string]struct { + setupPoolWithActiveLiquidity bool + forfeitedIncentives []sdk.Coins + expectedError error + }{ + "No forfeited incentives": { + setupPoolWithActiveLiquidity: true, + forfeitedIncentives: []sdk.Coins{sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins()}, + }, + "With active liquidity - forfeited incentives redeposited": { + setupPoolWithActiveLiquidity: true, + forfeitedIncentives: []sdk.Coins{{sdk.NewCoin("foo", sdk.NewInt(12345))}, sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins()}, + }, + "Multiple forfeited incentives redeposited": { + setupPoolWithActiveLiquidity: true, + forfeitedIncentives: []sdk.Coins{sdk.NewCoins(), {sdk.NewCoin("bar", sdk.NewInt(54321))}, sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), {sdk.NewCoin("foo", sdk.NewInt(12345))}}, + }, + "All slots filled with forfeited incentives": { + setupPoolWithActiveLiquidity: true, + forfeitedIncentives: []sdk.Coins{{sdk.NewCoin("foo", sdk.NewInt(10000))}, {sdk.NewCoin("bar", sdk.NewInt(20000))}, {sdk.NewCoin("baz", sdk.NewInt(30000))}, {sdk.NewCoin("qux", sdk.NewInt(40000))}, {sdk.NewCoin("quux", sdk.NewInt(50000))}, {sdk.NewCoin("corge", sdk.NewInt(60000))}}, + }, + "No active liquidity with no forfeited incentives": { + setupPoolWithActiveLiquidity: false, + forfeitedIncentives: []sdk.Coins{sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins()}, + }, + "No active liquidity with forfeited incentives sent to owner": { + setupPoolWithActiveLiquidity: false, + forfeitedIncentives: []sdk.Coins{{sdk.NewCoin("foo", sdk.NewInt(10000))}, sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins(), sdk.NewCoins()}, + }, + "Incorrect forfeited incentives length": { + setupPoolWithActiveLiquidity: true, + forfeitedIncentives: []sdk.Coins{sdk.NewCoins()}, // Incorrect length, should be len(types.SupportedUptimes) + expectedError: types.InvalidForfeitedIncentivesLengthError{ForfeitedIncentivesLength: 1, ExpectedLength: len(types.SupportedUptimes)}, + }, + } + + for name, tc := range tests { + s.Run(name, func() { + s.SetupTest() + + // Setup pool + pool := s.PrepareConcentratedPool() + poolId := pool.GetId() + owner := s.TestAccs[0] + + if tc.setupPoolWithActiveLiquidity { + // Add position to ensure pool has active liquidity + s.SetupDefaultPosition(poolId) + } + + // Fund pool with forfeited incentives and track total + totalForfeitedIncentives := sdk.NewCoins() + for _, coins := range tc.forfeitedIncentives { + s.FundAcc(pool.GetIncentivesAddress(), coins) + totalForfeitedIncentives = totalForfeitedIncentives.Add(coins...) + } + + // Get balances before the operation to compare after + balancesBefore := s.App.BankKeeper.GetAllBalances(s.Ctx, owner) + + // --- System under test --- + + err := s.App.ConcentratedLiquidityKeeper.RedepositForfeitedIncentives(s.Ctx, poolId, owner, tc.forfeitedIncentives, totalForfeitedIncentives) + + // --- Assertions --- + + balancesAfter := s.App.BankKeeper.GetAllBalances(s.Ctx, owner) + balanceChange := balancesAfter.Sub(balancesBefore...) + + // If an error is expected, check if it matches the expected error + if tc.expectedError != nil { + s.Require().ErrorContains(err, tc.expectedError.Error()) + + // Check if the owner's balance did not change + s.Require().Equal(sdk.NewCoins(), sdk.NewCoins(balanceChange...)) + + // Assert that uptime accumulators remain empty + s.assertUptimeAccumsEmpty(poolId) + return + } + + s.Require().NoError(err) + + // If there is no active liquidity, the forfeited incentives should be sent to the owner + if !tc.setupPoolWithActiveLiquidity { + // Check if the owner received the forfeited incentives + s.Require().Equal(totalForfeitedIncentives, balanceChange) + + // Assert that uptime accumulators remain empty + s.assertUptimeAccumsEmpty(poolId) + return + } + + // If there is active liquidity, the forfeited incentives should not + // be sent to the owner, but instead redeposited into the uptime accumulators. + s.Require().Equal(sdk.NewCoins(), sdk.NewCoins(balanceChange...)) + + // Refetch updated pool and accumulators + pool, err = s.App.ConcentratedLiquidityKeeper.GetPoolById(s.Ctx, poolId) + s.Require().NoError(err) + uptimeAccums, err := s.App.ConcentratedLiquidityKeeper.GetUptimeAccumulators(s.Ctx, poolId) + s.Require().NoError(err) + + // Check if the forfeited incentives were redeposited into the uptime accumulators + for i, accum := range uptimeAccums { + // Check that each accumulator has the correct value of scaledForfeitedIncentives / activeLiquidity + // Note that the function assumed the input slice is already scaled to avoid unnecessary recomputation. + for _, forfeitedCoin := range tc.forfeitedIncentives[i] { + expectedAmount := forfeitedCoin.Amount.ToLegacyDec().QuoTruncate(pool.GetLiquidity()) + accumAmount := accum.GetValue().AmountOf(forfeitedCoin.Denom) + s.Require().Equal(expectedAmount, accumAmount, "Forfeited incentive amount mismatch in uptime accumulator") + } + } + }) + } +} diff --git a/x/concentrated-liquidity/invariant_test.go b/x/concentrated-liquidity/invariant_test.go index bea522948ce..a0202ffa5f0 100644 --- a/x/concentrated-liquidity/invariant_test.go +++ b/x/concentrated-liquidity/invariant_test.go @@ -92,7 +92,7 @@ func (s *KeeperTestSuite) assertTotalRewardsInvariant(expectedGlobalRewardValues // // Balancer full range incentives are also not factored in because they are claimed and sent to // gauge immediately upon distribution. - collectedIncentives, _, err := s.Clk.CollectIncentives(cachedCtx, owner, position.PositionId) + collectedIncentives, _, _, err := s.Clk.CollectIncentives(cachedCtx, owner, position.PositionId) s.Require().NoError(err) // Ensure position owner's balance was updated correctly diff --git a/x/concentrated-liquidity/lp.go b/x/concentrated-liquidity/lp.go index 30669918ce8..fc7cb3b8756 100644 --- a/x/concentrated-liquidity/lp.go +++ b/x/concentrated-liquidity/lp.go @@ -268,7 +268,7 @@ func (k Keeper) WithdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position return osmomath.Int{}, osmomath.Int{}, err } - _, _, err = k.collectIncentives(ctx, owner, positionId) + _, totalForefeitedIncentives, scaledForfeitedIncentivesByUptime, err := k.collectIncentives(ctx, owner, positionId) if err != nil { return osmomath.Int{}, osmomath.Int{}, err } @@ -289,6 +289,12 @@ func (k Keeper) WithdrawPosition(ctx sdk.Context, owner sdk.AccAddress, position return osmomath.Int{}, osmomath.Int{}, err } + // If the position has any forfeited incentives, re-deposit them into the pool. + err = k.redepositForfeitedIncentives(ctx, position.PoolId, owner, scaledForfeitedIncentivesByUptime, totalForefeitedIncentives) + if err != nil { + return osmomath.Int{}, osmomath.Int{}, err + } + // If the requested liquidity amount to withdraw is equal to the available liquidity, delete the position from state. // Ensure we collect any outstanding spread factors prior to deleting the position from state. Outstanding incentives // should already be fully claimed by this point. This claiming process also clears position records from spread factor diff --git a/x/concentrated-liquidity/lp_test.go b/x/concentrated-liquidity/lp_test.go index 6bec56bab86..d779ac8fd2b 100644 --- a/x/concentrated-liquidity/lp_test.go +++ b/x/concentrated-liquidity/lp_test.go @@ -5,7 +5,6 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/osmosis-labs/osmosis/osmomath" "github.com/osmosis-labs/osmosis/osmoutils" @@ -596,8 +595,6 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { s.FundAcc(pool.GetSpreadRewardsAddress(), expectedSpreadRewardsClaimed) } - communityPoolBalanceBefore := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress(distributiontypes.ModuleName)) - // Set expected incentives and fund pool with appropriate amount expectedIncentivesClaimed = expectedIncentivesFromUptimeGrowth(defaultUptimeGrowth, liquidityCreated, tc.timeElapsed, defaultMultiplier) @@ -641,14 +638,19 @@ func (s *KeeperTestSuite) TestWithdrawPosition() { poolSpreadRewardBalanceAfterWithdraw := s.App.BankKeeper.GetAllBalances(s.Ctx, pool.GetSpreadRewardsAddress()) incentivesBalanceAfterWithdraw := s.App.BankKeeper.GetAllBalances(s.Ctx, pool.GetIncentivesAddress()) ownerBalancerAfterWithdraw := s.App.BankKeeper.GetAllBalances(s.Ctx, owner) - communityPoolBalanceAfter := s.App.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress(distributiontypes.ModuleName)) + + // If the position was the last in the pool, we expect it to receive full incentives since there + // is nobody to forfeit to + updatedPool, err := concentratedLiquidityKeeper.GetPoolById(s.Ctx, pool.GetId()) + s.Require().NoError(err) + if updatedPool.GetLiquidity().LTE(sdk.OneDec()) { + expectedIncentivesClaimed = expectedFullIncentivesFromAllUptimes + } // owner should only have tokens equivalent to the delta balance of the pool expectedOwnerBalanceDelta := expectedPoolBalanceDelta.Add(expectedIncentivesClaimed...).Add(expectedSpreadRewardsClaimed...) actualOwnerBalancerDelta := ownerBalancerAfterWithdraw.Sub(ownerBalancerBeforeWithdraw...) - - communityPoolBalanceDelta := communityPoolBalanceAfter.Sub(communityPoolBalanceBefore...) - actualIncentivesClaimed := incentivesBalanceBeforeWithdraw.Sub(incentivesBalanceAfterWithdraw...).Sub(communityPoolBalanceDelta...) + actualIncentivesClaimed := incentivesBalanceBeforeWithdraw.Sub(incentivesBalanceAfterWithdraw...) s.Require().Equal(expectedPoolBalanceDelta.String(), poolBalanceBeforeWithdraw.Sub(poolBalanceAfterWithdraw...).String()) s.Require().NotEmpty(expectedOwnerBalanceDelta) diff --git a/x/concentrated-liquidity/msg_server.go b/x/concentrated-liquidity/msg_server.go index 32cbf74a612..9a4a95520da 100644 --- a/x/concentrated-liquidity/msg_server.go +++ b/x/concentrated-liquidity/msg_server.go @@ -148,7 +148,7 @@ func (server msgServer) CollectIncentives(goCtx context.Context, msg *types.MsgC totalCollectedIncentives := sdk.NewCoins() totalForefeitedIncentives := sdk.NewCoins() for _, positionId := range msg.PositionIds { - collectedIncentives, forfeitedIncentives, err := server.keeper.collectIncentives(ctx, sender, positionId) + collectedIncentives, forfeitedIncentives, _, err := server.keeper.collectIncentives(ctx, sender, positionId) if err != nil { return nil, err } diff --git a/x/concentrated-liquidity/msg_server_test.go b/x/concentrated-liquidity/msg_server_test.go index 45104ea6357..c073669aa8c 100644 --- a/x/concentrated-liquidity/msg_server_test.go +++ b/x/concentrated-liquidity/msg_server_test.go @@ -357,7 +357,7 @@ func (s *KeeperTestSuite) TestCollectIncentives_Events() { numPositionsToCreate: 1, expectedTotalCollectIncentivesEvent: 1, expectedCollectIncentivesEvent: 1, - expectedMessageEvents: 2, // 1 for collect send, 1 for forfeit send + expectedMessageEvents: 1, // 1 for collect send }, "two position IDs": { upperTick: DefaultUpperTick, @@ -366,7 +366,7 @@ func (s *KeeperTestSuite) TestCollectIncentives_Events() { numPositionsToCreate: 2, expectedTotalCollectIncentivesEvent: 1, expectedCollectIncentivesEvent: 2, - expectedMessageEvents: 4, // 2 for collect send, 2 for forfeit send + expectedMessageEvents: 2, // 2 for collect send }, "three position IDs": { upperTick: DefaultUpperTick, @@ -375,7 +375,7 @@ func (s *KeeperTestSuite) TestCollectIncentives_Events() { numPositionsToCreate: 3, expectedTotalCollectIncentivesEvent: 1, expectedCollectIncentivesEvent: 3, - expectedMessageEvents: 6, // 3 for collect send, 3 for forfeit send + expectedMessageEvents: 3, // 3 for collect send }, "error: three position IDs - not an owner": { upperTick: DefaultUpperTick, diff --git a/x/concentrated-liquidity/position.go b/x/concentrated-liquidity/position.go index 39a28529721..ef980e39240 100644 --- a/x/concentrated-liquidity/position.go +++ b/x/concentrated-liquidity/position.go @@ -702,7 +702,7 @@ func (k Keeper) transferPositions(ctx sdk.Context, positionIds []uint64, sender if positionHasActiveUnderlyingLock { return types.LockNotMatureError{PositionId: position.PositionId, LockId: lockId} } - + // Delete the KVStore entries for the position. err = k.deletePosition(ctx, positionId, sender, position.PoolId) if err != nil { diff --git a/x/concentrated-liquidity/position_test.go b/x/concentrated-liquidity/position_test.go index 1a8840e72e9..b70af04f4b4 100644 --- a/x/concentrated-liquidity/position_test.go +++ b/x/concentrated-liquidity/position_test.go @@ -82,16 +82,17 @@ func (s *KeeperTestSuite) GetTotalAccruedRewardsByAccumulator(positionId uint64, // ExecuteAndValidateSuccessfulIncentiveClaim claims incentives for position Id and asserts its output is as expected. // It also asserts that no more incentives can be claimed for the position. -func (s *KeeperTestSuite) ExecuteAndValidateSuccessfulIncentiveClaim(positionId uint64, expectedRewards sdk.Coins, expectedForfeited sdk.Coins) { +func (s *KeeperTestSuite) ExecuteAndValidateSuccessfulIncentiveClaim(positionId uint64, expectedRewards sdk.Coins, expectedForfeited sdk.Coins, poolId uint64) { // Initial claim and assertion - claimedRewards, forfeitedRewards, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionId) + claimedRewards, totalForfeitedRewards, scaledForfeitedRewardsByUptime, err := s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionId) s.Require().NoError(err) s.Require().Equal(expectedRewards, claimedRewards) - s.Require().Equal(expectedForfeited, forfeitedRewards) + s.Require().Equal(expectedForfeited, totalForfeitedRewards) + s.checkForfeitedCoinsByUptime(totalForfeitedRewards, scaledForfeitedRewardsByUptime) // Sanity check that cannot claim again. - claimedRewards, _, err = s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionId) + claimedRewards, _, _, err = s.Clk.PrepareClaimAllIncentivesForPosition(s.Ctx, positionId) s.Require().NoError(err) s.Require().Equal(sdk.Coins(nil), claimedRewards) @@ -2471,7 +2472,7 @@ func (s *KeeperTestSuite) TestTransferPositions() { s.addUptimeGrowthInsideRange(s.Ctx, pool.GetId(), apptesting.DefaultLowerTick+1, DefaultLowerTick, DefaultUpperTick, expectedUptimes.hundredTokensMultiDenom) s.AddToSpreadRewardAccumulator(pool.GetId(), sdk.NewDecCoin(ETH, osmomath.NewInt(10))) for _, positionId := range tc.positionsToTransfer { - _, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, newOwner, positionId) + _, _, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, newOwner, positionId) s.Require().NoError(err) _, err = s.App.ConcentratedLiquidityKeeper.CollectSpreadRewards(s.Ctx, newOwner, positionId) s.Require().NoError(err) diff --git a/x/concentrated-liquidity/types/errors.go b/x/concentrated-liquidity/types/errors.go index c61a268551d..c464429f3f8 100644 --- a/x/concentrated-liquidity/types/errors.go +++ b/x/concentrated-liquidity/types/errors.go @@ -947,3 +947,12 @@ type IncentiveEmissionOvrflowError struct { func (e IncentiveEmissionOvrflowError) Error() string { return fmt.Sprintf("either too much time has passed since last pool update or the emission rate is too high, causing overflow: %s", e.PanicMessage) } + +type InvalidForfeitedIncentivesLengthError struct { + ForfeitedIncentivesLength int + ExpectedLength int +} + +func (e InvalidForfeitedIncentivesLengthError) Error() string { + return fmt.Sprintf("attempted to redeposit incorrectly constructed forfeited incentives slice. forfeited incentives must have an entry for each supported uptime. forfeit entries: %d, expected: %d", e.ForfeitedIncentivesLength, e.ExpectedLength) +}