Skip to content

Commit

Permalink
feat(staking): implement new staking design changes (#189)
Browse files Browse the repository at this point in the history
* remove state and add new interfaces

* unjail fee, fix comments

* fix comments

* add todos

* add instructions to README

* add extra devnet

* refactor staking script

* bindings

* fixes and changes

* comments and iface fixes

* address PR comments

* fix tests

* pre commit fixes

* feat(evmstaking): update event log processing (#201)

* feat(evmstaking): unit tests

feat(evmstaking): new staking events

feat(evmstaking): unit tests for new staking events

feat(evmstaking): unit tests for new staking events

feat(evmstaking): rebase latest changes

* feat(evmstaking): rebase latest changes

---------

Co-authored-by: Zerui Ge <gezerui1997@gmail.com>
  • Loading branch information
Ramarti and ezreal1997 authored Oct 15, 2024
1 parent f51e781 commit f021bb9
Show file tree
Hide file tree
Showing 39 changed files with 2,936 additions and 4,119 deletions.
4 changes: 3 additions & 1 deletion client/genutil/genutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ func addValidator(txConfig client.TxConfig, pubkey crypto.PubKey, cdc codec.Code
amount,
sttypes.Description{Moniker: addr.Hex()},
sttypes.NewCommissionRates(zero, zero, zero),
sdk.DefaultPowerReduction)
sdk.DefaultPowerReduction,
sttypes.TokenType_LOCKED,
)
if err != nil {
return nil, errors.Wrap(err, "create validator message")
}
Expand Down
10 changes: 8 additions & 2 deletions client/x/evmengine/keeper/abci_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ func TestKeeper_PrepareProposal(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, resp)

msgDelegate := stypes.NewMsgDelegate("delAddr", "valAddr", sdk.NewInt64Coin("stake", 100))
msgDelegate := stypes.NewMsgDelegate(
"delAddr", "valAddr", sdk.NewInt64Coin("stake", 100),
stypes.FlexibleDelegationID, stypes.PeriodType_FLEXIBLE,
)
resp.Txs[0] = appendMsgToTx(t, txConfig, resp.Txs[0], msgDelegate)

// decode the txn and get the messages
Expand Down Expand Up @@ -606,7 +609,10 @@ type mockVEProvider struct{}

func (m mockVEProvider) PrepareVotes(_ context.Context, _ abci.ExtendedCommitInfo) ([]sdk.Msg, error) {
coin := sdk.NewInt64Coin("stake", 100)
msg := stypes.NewMsgDelegate("addr", "addr", coin)
msg := stypes.NewMsgDelegate(
"addr", "addr", coin,
stypes.FlexibleDelegationID, stypes.PeriodType_FLEXIBLE,
)

return []sdk.Msg{msg}, nil
}
Expand Down
27 changes: 4 additions & 23 deletions client/x/evmstaking/keeper/abci_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
package keeper_test

import (
"context"
"testing"
"time"

sdkmath "cosmossdk.io/math"

abcitypes "github.com/cometbft/cometbft/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
dtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
stypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"

"github.com/piplabs/story/client/x/evmstaking/types"
"github.com/piplabs/story/lib/errors"
"github.com/piplabs/story/lib/k1util"

"go.uber.org/mock/gomock"
)

/*
func (s *TestSuite) TestEndBlock() {
require := s.Require()
ctx, keeper, bankKeeper, stakingKeeper, distrKeeper := s.Ctx, s.EVMStakingKeeper, s.BankKeeper, s.StakingKeeper, s.DistrKeeper
Expand Down Expand Up @@ -106,6 +87,7 @@ func (s *TestSuite) TestEndBlock() {
postStateCheck func(t *testing.T, c context.Context, expectedWithdrawals []types.Withdrawal)
expectedError string
}{
{
name: "pass: no mature unbonded delegations & eligible partial withdrawals",
setup: func(c context.Context) ([]types.Withdrawal, []abcitypes.ValidatorUpdate) {
Expand Down Expand Up @@ -308,7 +290,7 @@ func (s *TestSuite) TestEndBlock() {
// Mock successful ExpectedPartialWithdrawals
distrKeeper.EXPECT().GetValidatorAccumulatedCommission(gomock.Any(), gomock.Any()).Return(dtypes.ValidatorAccumulatedCommission{}, nil).Times(valCnt)
distrKeeper.EXPECT().IncrementValidatorPeriod(gomock.Any(), gomock.Any()).Return(uint64(0), nil).Times(valCnt)
distrKeeper.EXPECT().CalculateDelegationRewards(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(rewards, nil).Times(valCnt)
distrKeeper.EXPECT().CalculateDelegationRewards(gomock.Any(), gomock.Any(), gomock.Any()).Return(rewards, nil).Times(valCnt)
// Mock failed EnqueueEligiblePartialWithdrawal
distrKeeper.EXPECT().WithdrawDelegationRewards(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed to withdraw delegation rewards"))
Expand Down Expand Up @@ -338,10 +320,8 @@ func (s *TestSuite) TestEndBlock() {
if expectedValUpdates != nil {
compareValUpdates(s.T(), expectedValUpdates, valUpdates)
}
}
})
}
}
// compareValUpdates compares two slices of ValidatorUpdates, ignoring the order.
func compareValUpdates(t *testing.T, expected, actual abcitypes.ValidatorUpdates) {
Expand Down Expand Up @@ -383,3 +363,4 @@ func (s *TestSuite) setupMatureUnbondingDelegation(ctx sdk.Context, delAddr sdk.
s.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), delAddr, types.ModuleName, gomock.Any()).Return(nil)
s.BankKeeper.EXPECT().BurnCoins(gomock.Any(), types.ModuleName, gomock.Any()).Return(nil)
}
*/
33 changes: 29 additions & 4 deletions client/x/evmstaking/keeper/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ import (
)

func (k Keeper) ProcessDeposit(ctx context.Context, ev *bindings.IPTokenStakingDeposit) error {
depositorPubkey, err := k1util.PubKeyBytesToCosmos(ev.DelegatorCmpPubkey)
delCmpPubkey, err := UncmpPubKeyToCmpPubKey(ev.DelegatorUncmpPubkey)
if err != nil {
return errors.Wrap(err, "compress delegator pubkey")
}
depositorPubkey, err := k1util.PubKeyBytesToCosmos(delCmpPubkey)
if err != nil {
return errors.Wrap(err, "depositor pubkey to cosmos")
}

validatorPubkey, err := k1util.PubKeyBytesToCosmos(ev.ValidatorCmpPubkey)
valCmpPubkey, err := UncmpPubKeyToCmpPubKey(ev.ValidatorUnCmpPubkey)
if err != nil {
return errors.Wrap(err, "compress validator pubkey")
}
validatorPubkey, err := k1util.PubKeyBytesToCosmos(valCmpPubkey)
if err != nil {
return errors.Wrap(err, "validator pubkey to cosmos")
}
Expand All @@ -38,7 +46,7 @@ func (k Keeper) ProcessDeposit(ctx context.Context, ev *bindings.IPTokenStakingD
return errors.Wrap(err, "delegator pubkey to evm address")
}

amountCoin, amountCoins := IPTokenToBondCoin(ev.Amount)
amountCoin, amountCoins := IPTokenToBondCoin(ev.StakeAmount)

// Create account if not exists
if !k.authKeeper.HasAccount(ctx, depositorAddr) {
Expand Down Expand Up @@ -80,8 +88,25 @@ func (k Keeper) ProcessDeposit(ctx context.Context, ev *bindings.IPTokenStakingD
}
skeeperMsgServer := skeeper.NewMsgServerImpl(evmstakingSKeeper)

var periodType stypes.PeriodType
switch ev.StakingPeriod.Int64() {
case int64(stypes.PeriodType_FLEXIBLE):
periodType = stypes.PeriodType_FLEXIBLE
case int64(stypes.PeriodType_THREE_MONTHS):
periodType = stypes.PeriodType_THREE_MONTHS
case int64(stypes.PeriodType_ONE_YEAR):
periodType = stypes.PeriodType_ONE_YEAR
case int64(stypes.PeriodType_EIGHTEEN_MONTHS):
periodType = stypes.PeriodType_EIGHTEEN_MONTHS
default:
return errors.New("invalid staking period")
}

// Delegation by the depositor on the validator (validator existence is checked in msgServer.Delegate)
msg := stypes.NewMsgDelegate(depositorAddr.String(), validatorAddr.String(), amountCoin)
msg := stypes.NewMsgDelegate(
depositorAddr.String(), validatorAddr.String(), amountCoin,
ev.DelegationId.String(), periodType,
)
_, err = skeeperMsgServer.Delegate(ctx, msg)
if err != nil {
return errors.Wrap(err, "delegate")
Expand Down
77 changes: 56 additions & 21 deletions client/x/evmstaking/keeper/deposit_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package keeper_test

/*
import (
"context"
"math/big"
"time"
"cosmossdk.io/math"
sdkmath "cosmossdk.io/math"
"github.com/cometbft/cometbft/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -35,7 +38,7 @@ func (s *TestSuite) createValidator(ctx context.Context, valPubKey crypto.PubKey
// Create and update validator
val := testutil.NewValidator(s.T(), valAddr, valCosmosPubKey)
valTokens := stakingKeeper.TokensFromConsensusPower(ctx, 10)
validator, _ := val.AddTokensFromDel(valTokens)
validator, _, _ := val.AddTokensFromDel(valTokens, sdkmath.LegacyOneDec())
bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stypes.NotBondedPoolName, stypes.BondedPoolName, gomock.Any())
_ = skeeper.TestingUpdateValidator(stakingKeeper, sdkCtx, validator, true)
}
Expand All @@ -55,9 +58,12 @@ func (s *TestSuite) TestProcessDeposit() {
createDeposit := func(delPubKey, valPubKey []byte, amount *big.Int) *bindings.IPTokenStakingDeposit {
return &bindings.IPTokenStakingDeposit{
DelegatorCmpPubkey: delPubKey,
ValidatorCmpPubkey: valPubKey,
Amount: amount,
DelegatorUncmpPubkey: cmpToUncmp(delPubKey),
ValidatorUnCmpPubkey: cmpToUncmp(valPubKey),
StakeAmount: amount,
StakingPeriod: big.NewInt(0),
DelegationId: big.NewInt(0),
OperatorAddress: cmpToEVM(delPubKey),
}
}
expectAccountMock := func(isNewAccount bool) {
Expand All @@ -80,35 +86,44 @@ func (s *TestSuite) TestProcessDeposit() {
{
name: "fail: invalid delegator pubkey",
deposit: &bindings.IPTokenStakingDeposit{
DelegatorCmpPubkey: delPubKey.Bytes()[:16],
ValidatorCmpPubkey: valPubKey.Bytes(),
Amount: new(big.Int).SetUint64(1),
DelegatorUncmpPubkey: cmpToUncmp(delPubKey.Bytes())[:16],
ValidatorUnCmpPubkey: cmpToUncmp(valPubKey.Bytes()),
StakeAmount: new(big.Int).SetUint64(1),
StakingPeriod: big.NewInt(0),
DelegationId: big.NewInt(0),
OperatorAddress: cmpToEVM(delPubKey.Bytes()),
},
expectedErr: "invalid pubkey length",
expectedErr: "invalid uncompressed public key length or format",
},
{
name: "fail: invalid validator pubkey",
deposit: &bindings.IPTokenStakingDeposit{
DelegatorCmpPubkey: delPubKey.Bytes(),
ValidatorCmpPubkey: valPubKey.Bytes()[:16],
Amount: new(big.Int).SetUint64(1),
DelegatorUncmpPubkey: cmpToUncmp(delPubKey.Bytes()),
ValidatorUnCmpPubkey: cmpToUncmp(valPubKey.Bytes())[:16],
StakeAmount: new(big.Int).SetUint64(1),
StakingPeriod: big.NewInt(0),
DelegationId: big.NewInt(0),
OperatorAddress: cmpToEVM(delPubKey.Bytes()),
},
expectedErr: "invalid pubkey length",
expectedErr: "invalid uncompressed public key length or format",
},
{
name: "fail: corrupted delegator pubkey",
deposit: &bindings.IPTokenStakingDeposit{
DelegatorCmpPubkey: createCorruptedPubKey(delPubKey.Bytes()),
ValidatorCmpPubkey: valPubKey.Bytes(),
Amount: new(big.Int).SetUint64(1),
DelegatorUncmpPubkey: createCorruptedPubKey(cmpToUncmp(delPubKey.Bytes())),
ValidatorUnCmpPubkey: cmpToUncmp(valPubKey.Bytes()),
StakeAmount: new(big.Int).SetUint64(1),
StakingPeriod: big.NewInt(0),
DelegationId: big.NewInt(0),
OperatorAddress: cmpToEVM(delPubKey.Bytes()),
},
expectedErr: "delegator pubkey to evm address",
},
{
name: "fail: corrupted validator pubkey",
deposit: createDeposit(delPubKey.Bytes(), createCorruptedPubKey(valPubKey.Bytes()), new(big.Int).SetUint64(1)),
expectedErr: "validator pubkey to evm address",
expectedErr: "invalid uncompressed public key length or format",
},
// {
// name: "fail: corrupted validator pubkey",
// deposit: createDeposit(delPubKey.Bytes(), createCorruptedPubKey(valPubKey.Bytes()), new(big.Int).SetUint64(1)),
// expectedErr: "validator pubkey to evm address",
// },
{
name: "fail: mint coins to existing delegator",
settingMock: func() {
Expand Down Expand Up @@ -182,6 +197,15 @@ func (s *TestSuite) TestProcessDeposit() {
DelegatorAddress: delAddr.String(),
ValidatorAddress: valAddr.String(),
Shares: math.LegacyNewDecFromInt(math.NewInt(1)),
RewardsShares: math.LegacyNewDecFromInt(math.NewInt(1)),
PeriodDelegations: map[string]*stypes.PeriodDelegation{
stypes.FlexibleDelegationID: {
PeriodDelegationId: stypes.FlexibleDelegationID,
Shares: math.LegacyNewDecFromInt(math.NewInt(1)),
RewardsShares: math.LegacyNewDecFromInt(math.NewInt(1)),
EndTime: time.Time{},
},
},
},
},
{
Expand All @@ -197,6 +221,15 @@ func (s *TestSuite) TestProcessDeposit() {
DelegatorAddress: delAddr.String(),
ValidatorAddress: valAddr.String(),
Shares: math.LegacyNewDecFromInt(math.NewInt(1)),
RewardsShares: math.LegacyNewDecFromInt(math.NewInt(1)),
PeriodDelegations: map[string]*stypes.PeriodDelegation{
stypes.FlexibleDelegationID: {
PeriodDelegationId: stypes.FlexibleDelegationID,
Shares: math.LegacyNewDecFromInt(math.NewInt(1)),
RewardsShares: math.LegacyNewDecFromInt(math.NewInt(1)),
EndTime: time.Time{},
},
},
},
},
}
Expand All @@ -215,6 +248,7 @@ func (s *TestSuite) TestProcessDeposit() {
// check delegation
delegation, err := stakingKeeper.GetDelegation(cachedCtx, delAddr, valAddr)
require.NoError(err)
delegation.PeriodDelegations[stypes.FlexibleDelegationID].EndTime = time.Time{}
require.Equal(tc.expectedResult, delegation)
}
})
Expand Down Expand Up @@ -257,3 +291,4 @@ func (s *TestSuite) TestParseDepositLog() {
})
}
}
*/
2 changes: 2 additions & 0 deletions client/x/evmstaking/keeper/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package keeper_test

/*
import (
"context"
Expand Down Expand Up @@ -183,3 +184,4 @@ func (s *TestSuite) TestExportGenesis() {
})
}
}
*/
37 changes: 15 additions & 22 deletions client/x/evmstaking/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ type Keeper struct {
stakingKeeper types.StakingKeeper
distributionKeeper types.DistributionKeeper

ipTokenStakingContract *bindings.IPTokenStaking
ipTokenSlashingContract *bindings.IPTokenSlashing
ipTokenStakingContract *bindings.IPTokenStaking

WithdrawalQueue addcollections.Queue[types.Withdrawal]
DelegatorMap collections.Map[string, string] // bech32 to evm address (TODO: confirm that it's one-to-one or many-bech32-to-one-evm)
Expand Down Expand Up @@ -74,25 +73,19 @@ func NewKeeper(
panic(fmt.Sprintf("failed to bind to the IPTokenStaking contract: %s", err))
}

ipTokenSlashingContract, err := bindings.NewIPTokenSlashing(common.HexToAddress(predeploys.IPTokenSlashing), ethCl)
if err != nil {
panic(fmt.Sprintf("failed to bind to the IPTokenSlashing contract: %s", err))
}

return &Keeper{
cdc: cdc,
storeService: storeService,
authKeeper: ak,
bankKeeper: bk,
slashingKeeper: slk,
stakingKeeper: stk,
distributionKeeper: dk,
authority: authority,
validatorAddressCodec: validatorAddressCodec,
ipTokenStakingContract: ipTokenStakingContract,
ipTokenSlashingContract: ipTokenSlashingContract,
WithdrawalQueue: addcollections.NewQueue(sb, types.WithdrawalQueueKey, "withdrawal_queue", codec.CollValue[types.Withdrawal](cdc)),
DelegatorMap: collections.NewMap(sb, types.DelegatorMapKey, "delegator_map", collections.StringKey, collections.StringValue),
cdc: cdc,
storeService: storeService,
authKeeper: ak,
bankKeeper: bk,
slashingKeeper: slk,
stakingKeeper: stk,
distributionKeeper: dk,
authority: authority,
validatorAddressCodec: validatorAddressCodec,
ipTokenStakingContract: ipTokenStakingContract,
WithdrawalQueue: addcollections.NewQueue(sb, types.WithdrawalQueueKey, "withdrawal_queue", codec.CollValue[types.Withdrawal](cdc)),
DelegatorMap: collections.NewMap(sb, types.DelegatorMapKey, "delegator_map", collections.StringKey, collections.StringValue),
}
}

Expand Down Expand Up @@ -154,7 +147,7 @@ func (k Keeper) ProcessStakingEvents(ctx context.Context, height uint64, logs []
clog.Error(ctx, "Failed to parse Deposit log", err)
continue
}
ev.Amount.Div(ev.Amount, gwei)
ev.StakeAmount.Div(ev.StakeAmount, gwei)
if err = k.ProcessDeposit(ctx, ev); err != nil {
clog.Error(ctx, "Failed to process deposit", err)
continue
Expand All @@ -176,7 +169,7 @@ func (k Keeper) ProcessStakingEvents(ctx context.Context, height uint64, logs []
clog.Error(ctx, "Failed to parse Withdraw log", err)
continue
}
ev.Amount.Div(ev.Amount, gwei)
ev.StakeAmount.Div(ev.StakeAmount, gwei)
if err = k.ProcessWithdraw(ctx, ev); err != nil {
clog.Error(ctx, "Failed to process withdraw", err)
continue
Expand Down
Loading

0 comments on commit f021bb9

Please sign in to comment.