Skip to content

Commit

Permalink
Merge pull request #7 from iqlusioninc/liquid_staking_non_epoch_staking
Browse files Browse the repository at this point in the history
Liquid staking - non epoch version
  • Loading branch information
zmanian authored Jan 10, 2022
2 parents ee1df02 + e3c5af4 commit 63fa237
Show file tree
Hide file tree
Showing 38 changed files with 5,342 additions and 1,320 deletions.
61 changes: 61 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,67 @@ all: tools lint
# The below include contains the tools.
include contrib/devtools/Makefile



PACKAGES=$(shell go list ./... | grep -v '/simulation')
VERSION := $(shell git describe --abbrev=6 --dirty --always --tags)
COMMIT := $(shell git log -1 --format='%H')
DOCKER := $(shell which docker)
DOCKER_BUF := $(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace bufbuild/buf
HTTPS_GIT := https://github.com/iqlusioninc/liquidity-staking-module.git

build_tags = netgo
ifeq ($(LEDGER_ENABLED),true)
ifeq ($(OS),Windows_NT)
GCCEXE = $(shell where gcc.exe 2> NUL)
ifeq ($(GCCEXE),)
$(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
else
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S),OpenBSD)
$(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988))
else
GCC = $(shell command -v gcc 2> /dev/null)
ifeq ($(GCC),)
$(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
endif
endif
endif

ifeq (cleveldb,$(findstring cleveldb,$(GAIA_BUILD_OPTIONS)))
build_tags += gcc
endif
build_tags += $(BUILD_TAGS)
build_tags := $(strip $(build_tags))

whitespace :=
whitespace += $(whitespace)
comma := ,
build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags))

ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=gravity \
-X github.com/cosmos/cosmos-sdk/version.AppName=gravity \
-X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \
-X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \
-X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" \

BUILD_FLAGS := -ldflags '$(ldflags)' -gcflags="all=-N -l"

##############
### Build and install

install: go.sum
go install $(BUILD_FLAGS) ./cmd/liquidstakingd

build:
go build -o build/liquidstakingd $(BUILD_FLAGS) ./cmd/liquidstakingd/main.go

########################################
### Tools & dependencies

Expand Down
14 changes: 14 additions & 0 deletions proto/distribution/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ service Msg {
// full commission to the validator address.
rpc WithdrawValidatorCommission(MsgWithdrawValidatorCommission) returns (MsgWithdrawValidatorCommissionResponse);

// WithdrawTokenizeShareRecordReward defines a method to withdraw reward for
// owning TokenizeShareRecord
rpc WithdrawTokenizeShareRecordReward(MsgWithdrawTokenizeShareRecordReward)
returns (MsgWithdrawTokenizeShareRecordRewardResponse);

// FundCommunityPool defines a method to allow an account to directly
// fund the community pool.
rpc FundCommunityPool(MsgFundCommunityPool) returns (MsgFundCommunityPoolResponse);
Expand Down Expand Up @@ -64,6 +69,15 @@ message MsgWithdrawValidatorCommission {
// MsgWithdrawValidatorCommissionResponse defines the Msg/WithdrawValidatorCommission response type.
message MsgWithdrawValidatorCommissionResponse {}

message MsgWithdrawTokenizeShareRecordReward {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string owner_address = 1 [ (gogoproto.moretags) = "yaml:\"owner_address\"" ];
}

message MsgWithdrawTokenizeShareRecordRewardResponse {}

// MsgFundCommunityPool allows an account to directly
// fund the community pool.
message MsgFundCommunityPool {
Expand Down
7 changes: 7 additions & 0 deletions proto/staking/v1beta1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ message GenesisState {
repeated Redelegation redelegations = 7 [(gogoproto.nullable) = false];

bool exported = 8;

// store tokenize share records to provide reward to record owners
repeated TokenizeShareRecord tokenize_share_records = 9
[ (gogoproto.nullable) = false ];

// last tokenize share record id, used for next share record id calculation
uint64 last_tokenize_share_record_id = 10;
}

// LastValidatorPower required for validator set update logic.
Expand Down
10 changes: 10 additions & 0 deletions proto/staking/v1beta1/staking.proto
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,13 @@ message Pool {
(gogoproto.moretags) = "yaml:\"bonded_tokens\""
];
}

message TokenizeShareRecord {
option (gogoproto.equal) = true;

uint64 id = 1;
string owner = 2;
string share_token_denom = 3;
string module_account = 4; // module account take the role of delegator
string validator = 5; // validator delegated to for tokenize share record creation
}
52 changes: 52 additions & 0 deletions proto/staking/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ service Msg {
// Undelegate defines a method for performing an undelegation from a
// delegate and a validator.
rpc Undelegate(MsgUndelegate) returns (MsgUndelegateResponse);

// TokenizeShares defines a method for tokenizing shares from a validator.
rpc TokenizeShares(MsgTokenizeShares) returns (MsgTokenizeSharesResponse);

// RedeemTokens defines a method for redeeming tokens from a validator for
// shares.
rpc RedeemTokens(MsgRedeemTokensforShares)
returns (MsgRedeemTokensforSharesResponse);

// TransferTokenizeShareRecord defines a method to transfer ownership of
// TokenizeShareRecord
rpc TransferTokenizeShareRecord(MsgTransferTokenizeShareRecord)
returns (MsgTransferTokenizeShareRecordResponse);
}

// MsgCreateValidator defines a SDK message for creating a new validator.
Expand Down Expand Up @@ -124,3 +137,42 @@ message MsgUndelegate {
message MsgUndelegateResponse {
google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}

message MsgTokenizeShares {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string delegator_address = 1
[ (gogoproto.moretags) = "yaml:\"delegator_address\"" ];
string validator_address = 2
[ (gogoproto.moretags) = "yaml:\"validator_address\"" ];
cosmos.base.v1beta1.Coin amount = 3 [ (gogoproto.nullable) = false ];
string tokenized_share_owner = 4
[ (gogoproto.moretags) = "yaml:\"tokenized_share_owner\"" ];
}

message MsgTokenizeSharesResponse {
cosmos.base.v1beta1.Coin amount = 1 [ (gogoproto.nullable) = false ];
}

message MsgRedeemTokensforShares {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

string delegator_address = 1
[ (gogoproto.moretags) = "yaml:\"delegator_address\"" ];
cosmos.base.v1beta1.Coin amount = 2 [ (gogoproto.nullable) = false ];
}

message MsgRedeemTokensforSharesResponse {}

message MsgTransferTokenizeShareRecord {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

uint64 tokenize_share_record_id = 1;
string sender = 2;
string new_owner = 3;
}

message MsgTransferTokenizeShareRecordResponse {}
33 changes: 33 additions & 0 deletions x/distribution/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func NewTxCmd() *cobra.Command {
NewWithdrawAllRewardsCmd(),
NewSetWithdrawAddrCmd(),
NewFundCommunityPoolCmd(),
NewWithdrawTokenizeShareRecordRewardCmd(),
)

return distTxCmd
Expand Down Expand Up @@ -332,3 +333,35 @@ Where proposal.json contains:

return cmd
}

// WithdrawTokenizeShareRecordReward defines a method to withdraw reward for owning TokenizeShareRecord
func NewWithdrawTokenizeShareRecordRewardCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "withdraw-tokenize-share-rewards",
Args: cobra.ExactArgs(0),
Short: "Withdraw reward for owning TokenizeShareRecord",
Long: strings.TrimSpace(
fmt.Sprintf(`Withdraw reward for owned TokenizeShareRecord
Example:
$ %s tx distribution withdraw-tokenize-share-rewards --from mykey
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

msg := types.NewMsgWithdrawTokenizeShareRecordReward(clientCtx.GetFromAddress())

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
43 changes: 43 additions & 0 deletions x/distribution/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -747,3 +747,46 @@ func (s *IntegrationTestSuite) TestGetCmdSubmitProposal() {
})
}
}

func (s *IntegrationTestSuite) TestNewWithdrawTokenizeShareRecordRewardCmd() {
val := s.network.Validators[0]

testCases := []struct {
name string
args []string
expectErr bool
expectedCode uint32
respType proto.Message
}{
{
"valid transaction of withdraw tokenize share record reward",
[]string{
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
false, 0, &sdk.TxResponse{},
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.NewWithdrawTokenizeShareRecordRewardCmd()
clientCtx := val.ClientCtx

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err, out.String())
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.respType), out.String())

txResp := tc.respType.(*sdk.TxResponse)
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
}
})
}
}
94 changes: 94 additions & 0 deletions x/distribution/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

sdk "github.com/cosmos/cosmos-sdk/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
simapp "github.com/iqlusioninc/liquidity-staking-module/app"
"github.com/iqlusioninc/liquidity-staking-module/x/distribution/types"
"github.com/iqlusioninc/liquidity-staking-module/x/staking"
stakingkeeper "github.com/iqlusioninc/liquidity-staking-module/x/staking/keeper"
"github.com/iqlusioninc/liquidity-staking-module/x/staking/teststaking"
stakingtypes "github.com/iqlusioninc/liquidity-staking-module/x/staking/types"
)
Expand Down Expand Up @@ -67,6 +70,97 @@ func TestCalculateRewardsBasic(t *testing.T) {
require.Equal(t, sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDec(initial / 2)}}, app.DistrKeeper.GetValidatorAccumulatedCommission(ctx, valAddrs[0]).Commission)
}

func TestWithdrawTokenizeShareRecordReward(t *testing.T) {
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})

addr := simapp.AddTestAddrs(app, ctx, 2, sdk.NewInt(100000000))
valAddrs := simapp.ConvertAddrsToValAddrs(addr)
tstaking := teststaking.NewHelper(t, ctx, app.StakingKeeper)

// create validator with 50% commission
tstaking.Commission = stakingtypes.NewCommissionRates(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0))
valPower := int64(100)
tstaking.CreateValidatorWithValPower(valAddrs[0], valConsPk1, valPower, true)

// end block to bond validator
staking.EndBlocker(ctx, app.StakingKeeper)

// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)

// fetch validator and delegation
val := app.StakingKeeper.Validator(ctx, valAddrs[0])
del := app.StakingKeeper.Delegation(ctx, sdk.AccAddress(valAddrs[0]), valAddrs[0])

// end period
endingPeriod := app.DistrKeeper.IncrementValidatorPeriod(ctx, val)

// calculate delegation rewards
rewards := app.DistrKeeper.CalculateDelegationRewards(ctx, val, del, endingPeriod)

// rewards should be zero
require.True(t, rewards.IsZero())

// start out block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)

// retrieve validator
val = app.StakingKeeper.Validator(ctx, valAddrs[0])

// increase block height
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 3)

// allocate some rewards
initial := app.StakingKeeper.TokensFromConsensusPower(ctx, 10)
tokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: initial.ToDec()}}
app.DistrKeeper.AllocateTokensToValidator(ctx, val, tokens)

// end period
app.DistrKeeper.IncrementValidatorPeriod(ctx, val)

coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, initial)}
err := app.MintKeeper.MintCoins(ctx, coins)
require.NoError(t, err)
err = app.BankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, types.ModuleName, coins)
require.NoError(t, err)

// tokenize share amount
delTokens := sdk.NewInt(1000000)
msgServer := stakingkeeper.NewMsgServerImpl(app.StakingKeeper)
resp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &stakingtypes.MsgTokenizeShares{
DelegatorAddress: sdk.AccAddress(valAddrs[0]).String(),
ValidatorAddress: valAddrs[0].String(),
TokenizedShareOwner: sdk.AccAddress(valAddrs[0]).String(),
Amount: sdk.NewCoin(sdk.DefaultBondDenom, delTokens),
})

// assert tokenize share response
require.NoError(t, err)
require.Equal(t, resp.Amount.Amount, delTokens)

// end block to bond validator
staking.EndBlocker(ctx, app.StakingKeeper)
// next block
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
// allocate some rewards
app.DistrKeeper.AllocateTokensToValidator(ctx, val, tokens)
// end period
app.DistrKeeper.IncrementValidatorPeriod(ctx, val)

beforeBalance := app.BankKeeper.GetBalance(ctx, sdk.AccAddress(valAddrs[0]), sdk.DefaultBondDenom)

// withdraw rewards
coins, err = app.DistrKeeper.WithdrawTokenizeShareRecordReward(ctx, sdk.AccAddress(valAddrs[0]))
require.Nil(t, err)

// check return value
require.Equal(t, coins.String(), "50000stake")
// check balance changes
afterBalance := app.BankKeeper.GetBalance(ctx, sdk.AccAddress(valAddrs[0]), sdk.DefaultBondDenom)
require.Equal(t, beforeBalance.Amount.Add(coins.AmountOf(sdk.DefaultBondDenom)), afterBalance.Amount)
}

func TestCalculateRewardsAfterSlash(t *testing.T) {
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
Expand Down
Loading

0 comments on commit 63fa237

Please sign in to comment.