Skip to content

Commit

Permalink
generate files
Browse files Browse the repository at this point in the history
  • Loading branch information
kingpinXD committed Feb 28, 2024
1 parent da7f1a6 commit dca649e
Show file tree
Hide file tree
Showing 17 changed files with 308 additions and 79 deletions.
2 changes: 2 additions & 0 deletions docs/openapi/openapi.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53964,6 +53964,8 @@ definitions:
type: string
proved:
type: boolean
emissionsMsgWithdrawEmissionResponse:
type: object
emissionsQueryGetEmissionsFactorsResponse:
type: object
properties:
Expand Down
17 changes: 17 additions & 0 deletions docs/spec/emissions/messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Messages

## MsgWithdrawEmission

WithdrawEmission create a withdraw emission object , which is then process at endblock
The withdraw emission object is created and stored
using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed.
Creating a withdraw does not guarantee that the emission will be processed
All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not.

```proto
message MsgWithdrawEmission {
string creator = 1;
string amount = 2;
}
```

1 change: 0 additions & 1 deletion proto/emissions/withdrawable_emissions.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,4 @@ message WithdrawEmission {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
string withdraw_failed_reason = 4;
}
2 changes: 1 addition & 1 deletion testutil/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ var moduleAccountPerms = map[string][]string{
crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner},
fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner},
emissionstypes.ModuleName: {authtypes.Minter},
emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter},
emissionstypes.UndistributedObserverRewardsPool: {authtypes.Minter, authtypes.Burner},
emissionstypes.UndistributedTssRewardsPool: nil,
}

Expand Down
24 changes: 24 additions & 0 deletions testutil/keeper/mocks/emissions/emission.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions typescript/emissions/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from "./events_pb";
export * from "./genesis_pb";
export * from "./params_pb";
export * from "./query_pb";
export * from "./tx_pb";
export * from "./withdrawable_emissions_pb";
56 changes: 56 additions & 0 deletions typescript/emissions/tx_pb.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// @generated by protoc-gen-es v1.3.0 with parameter "target=dts"
// @generated from file emissions/tx.proto (package zetachain.zetacore.emissions, syntax proto3)
/* eslint-disable */
// @ts-nocheck

import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
import { Message, proto3 } from "@bufbuild/protobuf";

/**
* @generated from message zetachain.zetacore.emissions.MsgWithdrawEmission
*/
export declare class MsgWithdrawEmission extends Message<MsgWithdrawEmission> {
/**
* @generated from field: string creator = 1;
*/
creator: string;

/**
* @generated from field: string amount = 2;
*/
amount: string;

constructor(data?: PartialMessage<MsgWithdrawEmission>);

static readonly runtime: typeof proto3;
static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmission";
static readonly fields: FieldList;

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): MsgWithdrawEmission;

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): MsgWithdrawEmission;

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): MsgWithdrawEmission;

static equals(a: MsgWithdrawEmission | PlainMessage<MsgWithdrawEmission> | undefined, b: MsgWithdrawEmission | PlainMessage<MsgWithdrawEmission> | undefined): boolean;
}

/**
* @generated from message zetachain.zetacore.emissions.MsgWithdrawEmissionResponse
*/
export declare class MsgWithdrawEmissionResponse extends Message<MsgWithdrawEmissionResponse> {
constructor(data?: PartialMessage<MsgWithdrawEmissionResponse>);

static readonly runtime: typeof proto3;
static readonly typeName = "zetachain.zetacore.emissions.MsgWithdrawEmissionResponse";
static readonly fields: FieldList;

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): MsgWithdrawEmissionResponse;

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): MsgWithdrawEmissionResponse;

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): MsgWithdrawEmissionResponse;

static equals(a: MsgWithdrawEmissionResponse | PlainMessage<MsgWithdrawEmissionResponse> | undefined, b: MsgWithdrawEmissionResponse | PlainMessage<MsgWithdrawEmissionResponse> | undefined): boolean;
}

29 changes: 29 additions & 0 deletions typescript/emissions/withdrawable_emissions_pb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,32 @@ export declare class WithdrawableEmissions extends Message<WithdrawableEmissions
static equals(a: WithdrawableEmissions | PlainMessage<WithdrawableEmissions> | undefined, b: WithdrawableEmissions | PlainMessage<WithdrawableEmissions> | undefined): boolean;
}

/**
* @generated from message zetachain.zetacore.emissions.WithdrawEmission
*/
export declare class WithdrawEmission extends Message<WithdrawEmission> {
/**
* @generated from field: string address = 1;
*/
address: string;

/**
* @generated from field: string amount = 2;
*/
amount: string;

constructor(data?: PartialMessage<WithdrawEmission>);

static readonly runtime: typeof proto3;
static readonly typeName = "zetachain.zetacore.emissions.WithdrawEmission";
static readonly fields: FieldList;

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): WithdrawEmission;

static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): WithdrawEmission;

static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): WithdrawEmission;

static equals(a: WithdrawEmission | PlainMessage<WithdrawEmission> | undefined, b: WithdrawEmission | PlainMessage<WithdrawEmission> | undefined): boolean;
}

12 changes: 10 additions & 2 deletions x/emissions/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,27 +149,35 @@ func DistributeTssRewards(ctx sdk.Context, amount sdk.Int, bankKeeper types.Bank

func EndBlocker(ctx sdk.Context, keeper keeper.Keeper) {
allWithdrawEmissions := keeper.GetAllWithdrawEmissions(ctx)
// delete all withdraw emissions irrespective of weather they are processed or not; this is to avoid processing the same withdraw emission again if in case it fails
// if a witdraw emission fails, it will need to be created again by the observer
defer func() {
for _, withdrawEmission := range allWithdrawEmissions {
keeper.DeleteWithdrawEmissions(ctx, withdrawEmission.Address)
}
}()
for _, withdrawEmission := range allWithdrawEmissions {
// use a tmpCtx, which is a cache-wrapped context to avoid writing to the store if the transfer of funds fail
tmpCtx, commit := ctx.CacheContext()
coin := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawEmission.Amount))

// parse the address if it fails to log the error and continue to the next withdraw
address, err := sdk.AccAddressFromBech32(withdrawEmission.Address)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error while parsing withdraw emission address %s : %s", withdrawEmission.Address, err))
continue
}

// remove the withdraw emission amount from the observer and send it to the observer
// RemoveObserverEmission checks weather the observer has enough withdrawable emissions available
err = keeper.RemoveObserverEmission(tmpCtx, withdrawEmission.Address, withdrawEmission.Amount)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission from observer %s : %s", withdrawEmission.Address, err))
ctx.Logger().Error(fmt.Sprintf("Error while removing withdraw emission amount from observer %s : %s", withdrawEmission.Address, err))
continue
}
err = keeper.GetBankKeeper().SendCoinsFromModuleToAccount(tmpCtx, types.UndistributedObserverRewardsPool, address, coin)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("Error while sending withdraw emission to %s : %s", withdrawEmission.Address, err))
ctx.Logger().Error(fmt.Sprintf("Error while processing withdraw of emission to %s : %s", withdrawEmission.Address, err))
continue
}
commit()
Expand Down
24 changes: 23 additions & 1 deletion x/emissions/keeper/msg_server_withdraw_emissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,34 @@ import (

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
"github.com/zeta-chain/zetacore/x/emissions/types"
)

// WithdrawEmission create a withdraw emission object , which is then process at endblock
// The withdraw emission object is created and stored
// using the address of the creator as the index key ,therefore, if more that one withdraw requests are created in a block on thr last one would be processed.
// Creating a withdraw does not guarantee that the emission will be processed
// All withdraws for a block are deleted at the end of the block irrespective of whether they were processed or not.
func (k msgServer) WithdrawEmission(goCtx context.Context, msg *types.MsgWithdrawEmission) (*types.MsgWithdrawEmissionResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
err := k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount)

// check if the creator address is valid
_, err := sdk.AccAddressFromBech32(msg.Creator)
if err != nil {
return nil, errorsmod.Wrap(types.ErrInvalidAddress, err.Error())
}

// check if undistributed rewards pool has enough balance to process this request.
// This is just a basic check , the actual processing at endblock might still fail if the pool balance gets affected .
undistributedRewardsBalanced := k.GetBankKeeper().GetBalance(ctx, types.UndistributedObserverRewardsPoolAddress, config.BaseDenom)
if undistributedRewardsBalanced.Amount.LT(msg.Amount) {
return nil, errorsmod.Wrap(types.ErrRewardsPoolDoesNotHaveEnoughBalance, " rewards pool does not have enough balance to process this request")
}

// create a withdraw emission object
// CreateWithdrawEmissions makes sure that enough withdrawable emissions are available before creating the withdraw object
err = k.CreateWithdrawEmissions(ctx, msg.Creator, msg.Amount)
if err != nil {
return nil, errorsmod.Wrap(types.ErrUnableToCreateWithdrawEmissions, err.Error())
}
Expand Down
121 changes: 121 additions & 0 deletions x/emissions/keeper/msg_server_withdraw_emissions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package keeper_test

import (
"testing"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
keepertest "github.com/zeta-chain/zetacore/testutil/keeper"
"github.com/zeta-chain/zetacore/testutil/sample"
"github.com/zeta-chain/zetacore/x/emissions"
"github.com/zeta-chain/zetacore/x/emissions/keeper"
"github.com/zeta-chain/zetacore/x/emissions/types"
)

func TestMsgServer_WithdrawEmission(t *testing.T) {
t.Run("successfully withdraw emissions at endblock", func(t *testing.T) {
k, ctx, sk, _ := keepertest.EmissionsKeeper(t)

msgServer := keeper.NewMsgServerImpl(*k)
withdrawableEmission := sample.WithdrawableEmissions(t)
k.SetWithdrawableEmission(ctx, withdrawableEmission)
err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount)))
require.NoError(t, err)

_, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{
Creator: withdrawableEmission.Address,
Amount: withdrawableEmission.Amount,
})
require.NoError(t, err)

we, found := k.GetWithdrawEmissions(ctx, withdrawableEmission.Address)
require.True(t, found)
require.Equal(t, withdrawableEmission.Amount, we.Amount)

balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String()
require.Equal(t, sdk.ZeroInt().String(), balance)

emissions.EndBlocker(ctx, *k)
balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawableEmission.Address), config.BaseDenom).Amount.String()
require.Equal(t, withdrawableEmission.Amount.String(), balance)
})

t.Run("unable to create withdraw emissions with invalid address", func(t *testing.T) {
k, ctx, sk, _ := keepertest.EmissionsKeeper(t)

msgServer := keeper.NewMsgServerImpl(*k)
withdrawableEmission := sample.WithdrawableEmissions(t)
k.SetWithdrawableEmission(ctx, withdrawableEmission)
err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawableEmission.Amount)))
require.NoError(t, err)

_, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{
Creator: "invalid_address",
Amount: withdrawableEmission.Amount,
})
require.ErrorIs(t, err, types.ErrInvalidAddress)
})

t.Run("unable to create withdraw emissions if undistributed bbserver rewards pool does not have enough balance", func(t *testing.T) {
k, ctx, _, _ := keepertest.EmissionsKeeper(t)

msgServer := keeper.NewMsgServerImpl(*k)
withdrawableEmission := sample.WithdrawableEmissions(t)
k.SetWithdrawableEmission(ctx, withdrawableEmission)

_, err := msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{
Creator: withdrawableEmission.Address,
Amount: withdrawableEmission.Amount,
})
require.ErrorIs(t, err, types.ErrRewardsPoolDoesNotHaveEnoughBalance)
})

t.Run("unable to create withdraw emissions with invalid amount", func(t *testing.T) {
k, ctx, _, _ := keepertest.EmissionsKeeper(t)
msgServer := keeper.NewMsgServerImpl(*k)
withdrawableEmission := sample.WithdrawableEmissions(t)
k.SetWithdrawableEmission(ctx, withdrawableEmission)
_, err := msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{
Creator: withdrawableEmission.Address,
Amount: sdkmath.NewInt(-1),
})
require.ErrorIs(t, err, types.ErrUnableToCreateWithdrawEmissions)
})

t.Run("successfully create withdraw emissions but unable to process it", func(t *testing.T) {
k, ctx, sk, _ := keepertest.EmissionsKeeper(t)
msgServer := keeper.NewMsgServerImpl(*k)
withdrawablEmission := sample.WithdrawableEmissions(t)
k.SetWithdrawableEmission(ctx, withdrawablEmission)
err := sk.BankKeeper.MintCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount)))
require.NoError(t, err)

_, err = msgServer.WithdrawEmission(ctx, &types.MsgWithdrawEmission{
Creator: withdrawablEmission.Address,
Amount: withdrawablEmission.Amount,
})
require.NoError(t, err)

we, found := k.GetWithdrawEmissions(ctx, withdrawablEmission.Address)
require.True(t, found)
require.Equal(t, withdrawablEmission.Amount, we.Amount)

balance := k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String()
require.Equal(t, sdk.ZeroInt().String(), balance)

// Undistributed pool balance gets affected after the withdraw has been created
err = sk.BankKeeper.BurnCoins(ctx, types.UndistributedObserverRewardsPool, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, withdrawablEmission.Amount)))
require.NoError(t, err)

emissions.EndBlocker(ctx, *k)
// Undistributed pool does not have a balance so no rewards are distributed
balance = k.GetBankKeeper().GetBalance(ctx, sdk.MustAccAddressFromBech32(withdrawablEmission.Address), config.BaseDenom).Amount.String()
require.Equal(t, sdk.ZeroInt().String(), balance)

// Withdraw gets deleted after end-blocker
_, found = k.GetWithdrawEmissions(ctx, withdrawablEmission.Address)
require.False(t, found)
})
}
2 changes: 1 addition & 1 deletion x/emissions/keeper/withdraw_emissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (k Keeper) CreateWithdrawEmissions(ctx sdk.Context, address string, amount
return types.ErrEmissionsNotFound
}
if amount.IsNegative() || amount.IsZero() {
return types.ErrNotEnoughEmissionsAvailable
return types.ErrInvalidAmount
}
if amount.GT(emissions.Amount) {
amount = emissions.Amount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestKeeper_CreateWithdrawEmissions(t *testing.T) {
withdrawableEmission.Amount = sdkmath.ZeroInt()
k.SetWithdrawableEmission(ctx, withdrawableEmission)
err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.ZeroInt())
require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable)
require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount)
})

t.Run("unable to create withdraw for negative amount", func(t *testing.T) {
Expand All @@ -96,7 +96,7 @@ func TestKeeper_CreateWithdrawEmissions(t *testing.T) {
withdrawableEmission.Amount = sdkmath.NewInt(-1)
k.SetWithdrawableEmission(ctx, withdrawableEmission)
err := k.CreateWithdrawEmissions(ctx, withdrawableEmission.Address, sdkmath.NewInt(-1))
require.ErrorIs(t, err, emissionstypes.ErrNotEnoughEmissionsAvailable)
require.ErrorIs(t, err, emissionstypes.ErrInvalidAmount)
})

t.Run("unable to create withdraw for non existing withdrawable emission", func(t *testing.T) {
Expand Down
Loading

0 comments on commit dca649e

Please sign in to comment.