diff --git a/CHANGELOG.md b/CHANGELOG.md index d92fae0d8..d298a0fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +##v0.26.7 +* [sdk] [\#381] (https://github.com/bnb-chain/bnc-cosmos-sdk/pull/381) feat: implement BEP-333(BNB Chain Fusion) +* [fix] [\#356] (https://github.com/bnb-chain/bnc-cosmos-sdk/pull/356) fix: fix nil validator when publishing message + ##v0.26.6 * [sdk] [\#349] (https://github.com/bnb-chain/bnc-cosmos-sdk/pull/349) feat: add upgrade config for bep255 * [fix] [\#353] (https://github.com/bnb-chain/bnc-cosmos-sdk/pull/353) fix: add proof of possession for side vote addrs diff --git a/types/stake.go b/types/stake.go index 4a1a02170..7177a0a2c 100644 --- a/types/stake.go +++ b/types/stake.go @@ -76,6 +76,7 @@ type ValidatorSet interface { ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address ValidatorByVoteAddr(Context, []byte) Validator // get a particular validator by vote address TotalPower(Context) Dec // total power of the validator set + GetAllStatusVotingPower(ctx Context) Dec // total voting power of the validator set // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction Slash(Context, ConsAddress, int64, int64, Dec) diff --git a/types/upgrade.go b/types/upgrade.go index c01e71979..502bc4c9d 100644 --- a/types/upgrade.go +++ b/types/upgrade.go @@ -25,6 +25,14 @@ const ( FixDoubleSignChainId = "FixDoubleSignChainId" BEP126 = "BEP126" //https://github.com/binance-chain/BEPs/pull/126 BEP255 = "BEP255" // https://github.com/bnb-chain/BEPs/pull/255 + + FirstSunsetFork = "FirstSunsetFork" + SecondSunsetFork = "SecondSunsetFork" + FinalSunsetFork = "FinalSunsetFork" +) + +var ( + BCFusionStopGovThreshold int64 = 5_000_000_00000000 // 5M BNB ) var MainNetConfig = UpgradeConfig{ diff --git a/x/gov/handler.go b/x/gov/handler.go index db6ac09d6..b502012d6 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -22,6 +22,9 @@ func NewHandler(keeper Keeper) sdk.Handler { case MsgSideChainDeposit: return handleMsgSideChainDeposit(ctx, keeper, msg) case MsgSideChainSubmitProposal: + if sdk.IsUpgrade(sdk.SecondSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgSideChainSubmitProposal(ctx, keeper, msg) case MsgSideChainVote: return handleMsgSideChainVote(ctx, keeper, msg) diff --git a/x/gov/handler_sidechain.go b/x/gov/handler_sidechain.go index 29370e79e..c78f3d947 100644 --- a/x/gov/handler_sidechain.go +++ b/x/gov/handler_sidechain.go @@ -6,14 +6,20 @@ import ( ) func handleMsgSideChainSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSideChainSubmitProposal) sdk.Result { - if msg.ProposalType == ProposalTypeText && !sdk.IsUpgrade(sdk.BEP173) { - return ErrInvalidProposalType(keeper.codespace, msg.ProposalType).Result() - } - ctx, err := keeper.ScKeeper.PrepareCtxForSideChain(ctx, msg.SideChainId) if err != nil { return ErrInvalidSideChainId(keeper.codespace, msg.SideChainId).Result() } + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + vp := keeper.vs.GetAllStatusVotingPower(ctx) + if vp.LTE(sdk.NewDecFromInt(sdk.BCFusionStopGovThreshold)) { + return sdk.ErrMsgNotSupported("").Result() + } + } + + if msg.ProposalType == ProposalTypeText && !sdk.IsUpgrade(sdk.BEP173) { + return ErrInvalidProposalType(keeper.codespace, msg.ProposalType).Result() + } result := handleMsgSubmitProposal(ctx, keeper, NewMsgSubmitProposal(msg.Title, msg.Description, msg.ProposalType, msg.Proposer, msg.InitialDeposit, diff --git a/x/gov/tally.go b/x/gov/tally.go index 685c70770..a8073396e 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -45,8 +45,10 @@ func Tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, refu // if delegator tally voting power valAddrStr := sdk.ValAddress(vote.Voter).String() if val, ok := currValidators[valAddrStr]; ok { - val.Vote = vote.Option - currValidators[valAddrStr] = val + if val.DelegatorShares.GT(sdk.ZeroDec()) { + val.Vote = vote.Option + currValidators[valAddrStr] = val + } } else { keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) { diff --git a/x/ibc/endblock.go b/x/ibc/endblock.go index 3bae6ae65..670ee5f7f 100644 --- a/x/ibc/endblock.go +++ b/x/ibc/endblock.go @@ -1,20 +1,103 @@ package ibc import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" +) + +const ( + mirrorChannelID = 4 + mirrorSyncChannelID = 5 ) func EndBlocker(ctx sdk.Context, keeper Keeper) { if len(keeper.packageCollector.collectedPackages) == 0 { return } - var attributes []sdk.Attribute + var ( + attributes []sdk.Attribute + events sdk.Events + ) for _, ibcPackageRecord := range keeper.packageCollector.collectedPackages { attributes = append(attributes, sdk.NewAttribute(ibcPackageInfoAttributeKey, buildIBCPackageAttributeValue(ibcPackageRecord.destChainID, ibcPackageRecord.channelID, ibcPackageRecord.sequence))) } + keeper.packageCollector.collectedPackages = keeper.packageCollector.collectedPackages[:0] event := sdk.NewEvent(ibcEventType, attributes...) - ctx.EventManager().EmitEvent(event) + events.AppendEvent(event) + if sdk.IsUpgrade(sdk.FinalSunsetFork) && !keeper.sideKeeper.IsBSCAllChannelClosed(ctx) { + events = events.AppendEvents(closeSideChainChannels(ctx, keeper)) + } + ctx.EventManager().EmitEvents(events) +} + +func closeSideChainChannels(ctx sdk.Context, k Keeper) sdk.Events { + var events sdk.Events + sideChainId := k.sideKeeper.BscSideChainId(ctx) + // disable side chain channels + id := k.sideKeeper.Config().DestChainNameToID(sideChainId) + govChannelId := sdk.ChannelID(gov.ProposalTypeManageChanPermission) + permissions := k.sideKeeper.GetChannelSendPermissions(ctx, id) + channels := k.sideKeeper.Config().ChannelIDs() + + // mirror, mirrorSync channel was enabled by BEP84(https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP84.md) + // Those channels were bsc side channels, so they would not be in the bc store. + if _, exist := permissions[mirrorChannelID]; !exist { + channels = append(channels, mirrorChannelID) + permissions[mirrorChannelID] = sdk.ChannelAllow + } + if _, exist := permissions[mirrorSyncChannelID]; !exist { + channels = append(channels, mirrorSyncChannelID) + permissions[mirrorSyncChannelID] = sdk.ChannelAllow + } + + // close all side chain channels except gov channel + for _, channelId := range channels { + if channelId == govChannelId { + // skip gov channel + continue + } + if permissions[channelId] == sdk.ChannelForbidden { + // skip forbidden channel + continue + } + + events = events.AppendEvents(closeChannelOnSideChanAndKeeper(ctx, k, id, channelId)) + } + + // disable side chain gov channel + if permissions[govChannelId] == sdk.ChannelAllow { + events = events.AppendEvents(closeChannelOnSideChanAndKeeper(ctx, k, id, govChannelId)) + } + k.sideKeeper.SetBSCAllChannelClosed(ctx) + return events +} + +func closeChannelOnSideChanAndKeeper(ctx sdk.Context, k Keeper, + destChainID sdk.ChainID, channelID sdk.ChannelID) sdk.Events { + var events sdk.Events + _, err := k.sideKeeper.SaveChannelSettingChangeToIbc(ctx, destChainID, channelID, sdk.ChannelForbidden) + if err != nil { + ctx.Logger().Error("failed to save ibc channel change after FinalSunsetFork", + "sideChainId", destChainID, "channelId", channelID, "err", err.Error()) + events.AppendEvent(sdk.NewEvent(EventTypeSaveIBCChannelSettingFailed, + sdk.NewAttribute(AttributeKeySideChainId, fmt.Sprint(destChainID)), + sdk.NewAttribute(AttributeKeyChannelId, fmt.Sprint(channelID)), + sdk.NewAttribute(AttributeKeyError, err.Error()), + )) + return events + } + events.AppendEvent(sdk.NewEvent(EventTypeSaveIBCChannelSettingSucceed, + sdk.NewAttribute(AttributeKeySideChainId, fmt.Sprint(destChainID)), + sdk.NewAttribute(AttributeKeyChannelId, fmt.Sprint(channelID)), + )) + // close bc side chain channel + k.sideKeeper.SetChannelSendPermission(ctx, destChainID, channelID, sdk.ChannelForbidden) + + ctx.Logger().Info("close side chain channel after FinalSunsetFork", "sideChainId", destChainID, "channelId", channelID) + return events } diff --git a/x/ibc/events.go b/x/ibc/events.go index 30601f14f..cf6576829 100644 --- a/x/ibc/events.go +++ b/x/ibc/events.go @@ -6,6 +6,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +var ( + EventTypeSaveIBCChannelSettingFailed = "save_ibc_channel_setting_failed" + EventTypeSaveIBCChannelSettingSucceed = "save_ibc_channel_setting_succeed" + + AttributeKeySideChainId = "side_chain_id" + AttributeKeyChannelId = "channel_id" + AttributeKeyError = "error" +) + const ( separator = "::" ibcEventType = "IBCPackage" diff --git a/x/ibc/keeper.go b/x/ibc/keeper.go index 70491f099..f1994f380 100644 --- a/x/ibc/keeper.go +++ b/x/ibc/keeper.go @@ -37,6 +37,10 @@ func NewKeeper(storeKey sdk.StoreKey, paramSpace param.Subspace, codespace sdk.C } } +func (k *Keeper) SetSideChainKeeper(sidechainKeeper sidechain.Keeper) { + k.sideKeeper = sidechainKeeper +} + func (k *Keeper) CreateIBCSyncPackage(ctx sdk.Context, destChainName string, channelName string, packageLoad []byte) (uint64, sdk.Error) { relayerFee, err := k.GetRelayerFeeParam(ctx, destChainName) if err != nil { diff --git a/x/oracle/handler.go b/x/oracle/handler.go index 6deb8ca7d..a48e91784 100644 --- a/x/oracle/handler.go +++ b/x/oracle/handler.go @@ -17,6 +17,9 @@ func NewHandler(keeper Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { switch msg := msg.(type) { case types.ClaimMsg: + if sdk.IsUpgrade(sdk.FinalSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleClaimMsg(ctx, keeper, msg) default: errMsg := "Unrecognized oracle msg type" diff --git a/x/paramHub/genesis.go b/x/paramHub/genesis.go index c7c2e16e3..516a20aea 100644 --- a/x/paramHub/genesis.go +++ b/x/paramHub/genesis.go @@ -37,6 +37,7 @@ const ( SideChainDelegateFee = 1e5 SideChainRedelegateFee = 3e5 SideChainUndelegateFee = 2e5 + SideChainStakeMigrationFee = 3e5 // beacon chain stake fee EditChainValidatorFee = 1e8 @@ -73,7 +74,7 @@ const ( CrossBindRelayFee = 2e6 CrossUnbindRelayFee = 2e6 - //MiniToken fee + // MiniToken fee TinyIssueFee = 2e8 MiniIssueFee = 3e8 MiniSetUriFee = 37500 @@ -87,7 +88,7 @@ const ( var DefaultGenesisState = param.GenesisState{ FeeGenesis: FeeGenesisState, - //Add other param genesis here + // Add other param genesis here } // --------- Definition about fee prams ------------------- // @@ -131,4 +132,4 @@ var FeeGenesisState = []param.FeeParam{ }, } -//---------- End definition about fee param ---------------- // +// ---------- End definition about fee param ---------------- // diff --git a/x/paramHub/hub.go b/x/paramHub/hub.go index f9e4cdd76..3dd8a913f 100644 --- a/x/paramHub/hub.go +++ b/x/paramHub/hub.go @@ -140,6 +140,12 @@ func RegisterUpgradeBeginBlocker(paramHub *ParamHub) { } paramHub.UpdateFeeParams(ctx, updateFeeParams) }) + sdk.UpgradeMgr.RegisterBeginBlocker(sdk.FirstSunsetFork, func(ctx sdk.Context) { + updateFeeParams := []param.FeeParam{ + ¶m.FixedFeeParams{MsgType: "side_stake_migration", Fee: SideChainStakeMigrationFee, FeeFor: sdk.FeeForProposer}, + } + paramHub.UpdateFeeParams(ctx, updateFeeParams) + }) } func EndBreatheBlock(ctx sdk.Context, paramHub *ParamHub) { @@ -171,6 +177,7 @@ func init() { "side_delegate": fees.FixedFeeCalculatorGen, "side_redelegate": fees.FixedFeeCalculatorGen, "side_undelegate": fees.FixedFeeCalculatorGen, + "side_stake_migration": fees.FixedFeeCalculatorGen, "bsc_submit_evidence": fees.FixedFeeCalculatorGen, "side_chain_unjail": fees.FixedFeeCalculatorGen, "dexList": fees.FixedFeeCalculatorGen, diff --git a/x/paramHub/types/types.go b/x/paramHub/types/types.go index 464ff2ace..56f36e8b0 100644 --- a/x/paramHub/types/types.go +++ b/x/paramHub/types/types.go @@ -59,6 +59,7 @@ var ( "side_delegate": {}, "side_redelegate": {}, "side_undelegate": {}, + "side_stake_migration": {}, "bsc_submit_evidence": {}, "side_chain_unjail": {}, @@ -292,7 +293,7 @@ type SCParam interface { subspace.ParamSet UpdateCheck() error // native means weather the parameter stored in native store context or side chain store context - //GetParamAttribute() (string, bool) + // GetParamAttribute() (string, bool) GetParamAttribute() (string, bool) } diff --git a/x/sidechain/config.go b/x/sidechain/config.go index 9eab777b2..7ea1d33f9 100644 --- a/x/sidechain/config.go +++ b/x/sidechain/config.go @@ -5,6 +5,7 @@ import sdk "github.com/cosmos/cosmos-sdk/types" type crossChainConfig struct { srcChainID sdk.ChainID + channelIDs []sdk.ChannelID nameToChannelID map[string]sdk.ChannelID channelIDToName map[sdk.ChannelID]string channelIDToApp map[sdk.ChannelID]sdk.CrossChainApplication @@ -16,6 +17,7 @@ type crossChainConfig struct { func newCrossChainCfg() *crossChainConfig { config := &crossChainConfig{ srcChainID: 0, + channelIDs: make([]sdk.ChannelID, 0), nameToChannelID: make(map[string]sdk.ChannelID), channelIDToName: make(map[sdk.ChannelID]string), destChainNameToID: make(map[string]sdk.ChainID), @@ -24,3 +26,11 @@ func newCrossChainCfg() *crossChainConfig { } return config } + +func (c *crossChainConfig) DestChainNameToID(name string) sdk.ChainID { + return c.destChainNameToID[name] +} + +func (c *crossChainConfig) ChannelIDs() []sdk.ChannelID { + return c.channelIDs +} diff --git a/x/sidechain/keeper.go b/x/sidechain/keeper.go index ba5101676..db9aa788d 100644 --- a/x/sidechain/keeper.go +++ b/x/sidechain/keeper.go @@ -93,6 +93,7 @@ func (k *Keeper) RegisterChannel(name string, id sdk.ChannelID, app sdk.CrossCha if ok { return fmt.Errorf("duplicated channel id") } + k.cfg.channelIDs = append(k.cfg.channelIDs, id) k.cfg.nameToChannelID[name] = id k.cfg.channelIDToName[id] = name k.cfg.channelIDToApp[id] = app @@ -224,6 +225,16 @@ func (k *Keeper) incrSequence(ctx sdk.Context, destChainID sdk.ChainID, channelI kvStore.Set(buildChannelSequenceKey(destChainID, channelID, prefix), sequenceBytes) } +func (k *Keeper) IsBSCAllChannelClosed(ctx sdk.Context) bool { + kvStore := ctx.KVStore(k.storeKey) + return kvStore.Has(buildBSCAllChannelStatusPrefixKey(k.BscSideChainId(ctx))) +} + +func (k *Keeper) SetBSCAllChannelClosed(ctx sdk.Context) { + kvStore := ctx.KVStore(k.storeKey) + kvStore.Set(buildBSCAllChannelStatusPrefixKey(k.BscSideChainId(ctx)), []byte{1}) +} + func EndBlock(ctx sdk.Context, k Keeper) { if sdk.IsUpgrade(sdk.LaunchBscUpgrade) && k.govKeeper != nil { chanPermissions := k.getLastChanPermissionChanges(ctx) @@ -242,3 +253,7 @@ func EndBlock(ctx sdk.Context, k Keeper) { } return } + +func (k *Keeper) Config() *crossChainConfig { + return k.cfg +} diff --git a/x/sidechain/key.go b/x/sidechain/key.go index b58e8ed3d..4473e4841 100644 --- a/x/sidechain/key.go +++ b/x/sidechain/key.go @@ -20,6 +20,8 @@ var ( PrefixForReceiveSequenceKey = []byte{0xf1} PrefixForChannelPermissionKey = []byte{0xc0} + + PrefixForBSCAllChannelStatus = []byte{0xc1} ) func GetSideChainStorePrefixKey(sideChainId string) []byte { @@ -51,3 +53,7 @@ func buildChannelPermissionsPrefixKey(destChainID sdk.ChainID) []byte { binary.BigEndian.PutUint16(key[prefixLength:prefixLength+destChainIDLength], uint16(destChainID)) return key } + +func buildBSCAllChannelStatusPrefixKey(sideChainId string) []byte { + return append(PrefixForBSCAllChannelStatus, []byte(sideChainId)...) +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 48755ce4b..61dfe588f 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -370,7 +370,7 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideSlashPackage) s //} k.setValidatorSigningInfo(sideCtx, sideConsAddr, signInfo) - if k.PbsbServer != nil { + if k.PbsbServer != nil && validator != nil { event := SideSlashEvent{ Validator: validator.GetOperator(), InfractionType: Downtime, diff --git a/x/stake/client/cli/cmd.go b/x/stake/client/cli/cmd.go index de4d00069..070e4c90e 100644 --- a/x/stake/client/cli/cmd.go +++ b/x/stake/client/cli/cmd.go @@ -51,6 +51,7 @@ func AddCommands(root *cobra.Command, cdc *codec.Codec) { GetCmdSideChainDelegate(cdc), GetCmdSideChainRedelegate(cdc), GetCmdSideChainUnbond(cdc), + GetCmdSideChainStakeMigration(cdc), )..., ) stakingCmd.AddCommand(client.LineBreak) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index ed59f6794..ae6464b12 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -8,14 +8,16 @@ import ( // nolint const ( - FlagAddressDelegator = "address-delegator" - FlagAddressValidator = "validator" - FlagAddressValidatorSrc = "addr-validator-source" - FlagAddressValidatorDst = "addr-validator-dest" - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagSharesAmount = "shares-amount" - FlagSharesPercent = "shares-percent" + FlagAddressDelegator = "address-delegator" + FlagAddressValidator = "validator" + FlagAddressValidatorSrc = "addr-validator-source" + FlagAddressValidatorDst = "addr-validator-dest" + FlagAddressSmartChainValidator = "address-smart-chain-validator" + FlagAddressSmartChainBeneficiary = "address-smart-chain-beneficiary" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagSharesAmount = "shares-amount" + FlagSharesPercent = "shares-percent" FlagMoniker = "moniker" FlagIdentity = "identity" @@ -48,19 +50,21 @@ const ( // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) - fsShares = flag.NewFlagSet("", flag.ContinueOnError) - fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) - fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) - fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) - fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) - fsValidator = flag.NewFlagSet("", flag.ContinueOnError) - fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) - fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) - fsSideChainFull = flag.NewFlagSet("", flag.ContinueOnError) - fsSideChainEdit = flag.NewFlagSet("", flag.ContinueOnError) - fsSideChainId = flag.NewFlagSet("", flag.ContinueOnError) + fsPk = flag.NewFlagSet("", flag.ContinueOnError) + fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) + fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) + fsSideChainFull = flag.NewFlagSet("", flag.ContinueOnError) + fsSideChainEdit = flag.NewFlagSet("", flag.ContinueOnError) + fsSideChainId = flag.NewFlagSet("", flag.ContinueOnError) + fsSmartChainValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsSmartChainBeneficiary = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { @@ -97,4 +101,6 @@ func init() { fsSideChainEdit.String(FlagBLSWalletDir, "", "Absolute path of BLS wallet, should be provided if the side vote address is provided") fsSideChainEdit.String(FlagBLSPassword, "", "Password for BLS wallet") fsSideChainId.String(FlagSideChainId, "", "Chain-id of the side chain the validator belongs to") + fsSmartChainValidator.String(FlagAddressSmartChainValidator, "", "Smart chain operator address of the validator") + fsSmartChainBeneficiary.String(FlagAddressSmartChainBeneficiary, "", "Smart chain address of the delegation's beneficiary") } diff --git a/x/stake/client/cli/tx_sidechain.go b/x/stake/client/cli/tx_sidechain.go index 708cfbf0b..af48dcbb6 100644 --- a/x/stake/client/cli/tx_sidechain.go +++ b/x/stake/client/cli/tx_sidechain.go @@ -15,6 +15,8 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" + sTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/crypto/bls/common" validatorpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/validator-client" @@ -161,7 +163,6 @@ func GetCmdEditSideChainValidator(cdc *codec.Codec) *cobra.Command { msg = stake.NewMsgEditSideChainValidatorWithVoteAddr(sideChainId, sdk.ValAddress(valAddr), description, newRate, sideFeeAddr, sideConsAddr, sideVoteAddr) } else { msg = stake.NewMsgEditSideChainValidator(sideChainId, sdk.ValAddress(valAddr), description, newRate, sideFeeAddr, sideConsAddr) - } return utils.GenerateOrBroadcastMsgs(txBldr, cliCtx, []sdk.Msg{msg}) } @@ -263,7 +264,7 @@ func GetCmdSideChainRedelegate(cdc *codec.Codec) *cobra.Command { func GetCmdSideChainUnbond(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "bsc-unbond", - Short: "", + Short: "Undelegate illiquid tokens from the validator", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -300,6 +301,51 @@ func GetCmdSideChainUnbond(cdc *codec.Codec) *cobra.Command { return cmd } +func GetCmdSideChainStakeMigration(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "bsc-stake-migration", + Short: "Migrate delegation from Beacon Chain to Smart Chain", + RunE: func(cmd *cobra.Command, args []string) error { + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + valAddr, err := getValidatorAddr(FlagAddressValidator) + if err != nil { + return err + } + operatorAddr, err := getSmartChainAddr(FlagAddressSmartChainValidator) + if err != nil { + return err + } + delAddr, err := getSmartChainAddr(FlagAddressSmartChainBeneficiary) + if err != nil { + return err + } + refundAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + amount, err := getAmount() + if err != nil { + return err + } + + msg := sTypes.NewMsgSideChainStakeMigration(valAddr, operatorAddr, delAddr, refundAddr, amount) + return utils.GenerateOrBroadcastMsgs(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + + cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(fsValidator) + cmd.Flags().AddFlagSet(fsSmartChainValidator) + cmd.Flags().AddFlagSet(fsSmartChainBeneficiary) + + return cmd +} + func getSideChainId() (sideChainId string, err error) { sideChainId = viper.GetString(FlagSideChainId) if len(sideChainId) == 0 { @@ -412,6 +458,15 @@ func getValidatorAddr(flagName string) (valAddr sdk.ValAddress, err error) { return sdk.ValAddressFromBech32(valAddrStr) } +func getSmartChainAddr(flagName string) (addr sdk.SmartChainAddress, err error) { + addrStr := viper.GetString(flagName) + if len(addrStr) == 0 { + err = fmt.Errorf("%s is required", flagName) + return + } + return sdk.NewSmartChainAddress(addrStr) +} + func getBLSPassword() (string, error) { blsPassword := viper.GetString(FlagBLSPassword) if len(blsPassword) > 0 { diff --git a/x/stake/cross_stake/cross_stake.go b/x/stake/cross_stake/cross_stake.go index 275f7b8ff..0965be6e9 100644 --- a/x/stake/cross_stake/cross_stake.go +++ b/x/stake/cross_stake/cross_stake.go @@ -127,6 +127,14 @@ func (app *CrossStakeApp) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) Recipient: p.Recipient, } result, err = app.handleDistributeUndelegatedRefund(ctx, refundPackage) + case *types.CrossStakeDistributeUndelegatedSynPackageV2: + bcAmount := bsc.ConvertBSCAmountToBCAmount(p.Amount) + refundPackage := &types.CrossStakeRefundPackage{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: big.NewInt(bcAmount), + Recipient: p.Recipient, + } + result, err = app.handleDistributeUndelegatedRefund(ctx, refundPackage) default: app.stakeKeeper.Logger(ctx).Error("unknown cross stake fail ack event type", "err", err.Error(), "package", string(payload)) return sdk.ExecuteResult{} @@ -227,7 +235,7 @@ func (app *CrossStakeApp) handleUndelegate(ctx sdk.Context, pack *types.CrossSta }, errCode, nil } - _, err := app.stakeKeeper.BeginUnbonding(ctx.WithCrossStake(true), delAddr, pack.Validator, shares) + _, err := app.stakeKeeper.BeginUnbonding(ctx.WithCrossStake(true), delAddr, pack.Validator, shares, true) if err != nil { return sdk.ExecuteResult{}, errCode, err } @@ -347,7 +355,7 @@ func (app *CrossStakeApp) handleDistributeRewardRefund(ctx sdk.Context, pack *ty return sdk.ExecuteResult{}, err } - // publish event + // publish event if app.stakeKeeper.PbsbServer != nil && ctx.IsDeliverTx() { app.stakeKeeper.AddrPool.AddAddrs([]sdk.AccAddress{sdk.PegAccount, refundAddr}) PublishCrossStakeEvent(ctx, app.stakeKeeper, sdk.PegAccount.String(), []pubsub.CrossReceiver{{refundAddr.String(), pack.Amount.Int64()}}, @@ -368,7 +376,7 @@ func (app *CrossStakeApp) handleDistributeUndelegatedRefund(ctx sdk.Context, pac return sdk.ExecuteResult{}, err } - // publish event + // publish event if app.stakeKeeper.PbsbServer != nil && ctx.IsDeliverTx() { app.stakeKeeper.AddrPool.AddAddrs([]sdk.AccAddress{sdk.PegAccount, refundAddr}) PublishCrossStakeEvent(ctx, app.stakeKeeper, sdk.PegAccount.String(), []pubsub.CrossReceiver{{refundAddr.String(), pack.Amount.Int64()}}, diff --git a/x/stake/cross_stake/serialize.go b/x/stake/cross_stake/serialize.go index 8cb9a8be5..8c4bdb8e6 100644 --- a/x/stake/cross_stake/serialize.go +++ b/x/stake/cross_stake/serialize.go @@ -84,6 +84,17 @@ func DeserializeCrossStakeFailAckPackage(serializedPackage []byte) (interface{}, } return &pack, nil }, + func(serializedPackage []byte) (interface{}, error) { + var pack types.CrossStakeDistributeUndelegatedSynPackageV2 + err := rlp.DecodeBytes(serializedPackage, &pack) + if err != nil { + return nil, err + } + if pack.EventType != types.CrossStakeTypeDistributeUndelegated { + return nil, fmt.Errorf("wrong cross stake event type") + } + return &pack, nil + }, } var pack interface{} diff --git a/x/stake/endblock.go b/x/stake/endblock.go index 106af295a..4019d3dde 100644 --- a/x/stake/endblock.go +++ b/x/stake/endblock.go @@ -12,16 +12,24 @@ import ( func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.ValidatorUpdate, completedUbds []types.UnbondingDelegation) { // only change validator set in breath block after BEP159 - var events sdk.Events - var csEvents sdk.Events + var ( + events sdk.Events + csEvents sdk.Events + ) + if !sdk.IsUpgrade(sdk.BEP159) { _, validatorUpdates, completedUbds, _, events = handleValidatorAndDelegations(ctx, k) } else { k.DistributeInBlock(ctx, types.ChainIDForBeaconChain) validatorUpdates = k.PopPendingABCIValidatorUpdate(ctx) } + + var ( + sideChainIds []string + storePrefixes [][]byte + ) if sdk.IsUpgrade(sdk.BEP128) { - sideChainIds, storePrefixes := k.ScKeeper.GetAllSideChainPrefixes(ctx) + sideChainIds, storePrefixes = k.ScKeeper.GetAllSideChainPrefixes(ctx) if len(sideChainIds) == len(storePrefixes) { for i := range storePrefixes { sideChainCtx := ctx.WithSideChainKeyPrefix(storePrefixes[i]) @@ -31,9 +39,26 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (validatorUpdates []abci.Valid panic("sideChainIds does not equal to sideChainStores") } } + + if len(storePrefixes) > 0 && sdk.IsUpgrade(sdk.SecondSunsetFork) { + for i := range storePrefixes { + events.AppendEvents(handleRefundStake(ctx, storePrefixes[i], k)) + } + } + + if len(storePrefixes) > 0 && sdk.IsUpgrade(sdk.FirstSunsetFork) { + for i := range storePrefixes { + sideChainCtx := ctx.WithSideChainKeyPrefix(storePrefixes[i]) + _, unBoundedEvents := handleMatureUnbondingDelegations(k, sideChainCtx) + + events = append(events, unBoundedEvents...) + } + } + if sdk.IsUpgrade(sdk.BEP153) { events = events.AppendEvents(csEvents) } + ctx.EventManager().EmitEvents(events) return } @@ -252,3 +277,74 @@ func handleMatureUnbondingDelegations(k keeper.Keeper, ctx sdk.Context) ([]types return completed, events } + +const ( + maxProcessedRefundCount = 10 + maxProcessedRefundFailed = 200 +) + +func handleRefundStake(ctx sdk.Context, sideChainPrefix []byte, k keeper.Keeper) sdk.Events { + sideChainCtx := ctx.WithSideChainKeyPrefix(sideChainPrefix) + iterator := k.IteratorAllDelegations(sideChainCtx) + defer iterator.Close() + var refundEvents sdk.Events + succeedCount := 0 + failedCount := 0 + boundDenom := k.BondDenom(sideChainCtx) + bscSideChainId := k.ScKeeper.BscSideChainId(ctx) + + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.CDC(), iterator.Key(), iterator.Value()) + if delegation.CrossStake { + ctx = ctx.WithCrossStake(true) + } else { + ctx = ctx.WithCrossStake(false) + } + + result := handleMsgSideChainUndelegate(ctx, types.MsgSideChainUndelegate{ + DelegatorAddr: delegation.DelegatorAddr, + ValidatorAddr: delegation.ValidatorAddr, + Amount: sdk.NewCoin(boundDenom, delegation.GetShares().RawInt()), + SideChainId: bscSideChainId, + }, k) + refundEvents = refundEvents.AppendEvents(result.Events) + if !result.IsOK() { + ctx.Logger().Info("handleRefundStake failed", + "delegator", delegation.DelegatorAddr.String(), + "validator", delegation.ValidatorAddr.String(), + "amount", delegation.GetShares().String(), + "sideChainId", bscSideChainId, + "result", fmt.Sprintf("%+v", result), + ) + // this is to prevent too many delegation is in unbounded state + // if too many delegation is in unbounded state, it will cause too many iteration in the block + failedCount++ + if failedCount >= maxProcessedRefundFailed { + break + } + + continue + } + + if result.IsOK() && delegation.CrossStake { + k.SetAutoUnDelegate(sideChainCtx, delegation.DelegatorAddr, delegation.ValidatorAddr) + } + + ctx.Logger().Info("handleRefundStake after SecondSunsetFork", + "delegator", delegation.DelegatorAddr.String(), + "validator", delegation.ValidatorAddr.String(), + "amount", delegation.GetShares().String(), + "sideChainId", bscSideChainId, + ) + succeedCount++ + if succeedCount >= maxProcessedRefundCount { + break + } + } + ctx.Logger().Info("handleRefundStake processed count", + "succeedCount", succeedCount, + "failedCount", failedCount, + "sideChainId", bscSideChainId) + + return refundEvents +} diff --git a/x/stake/handler.go b/x/stake/handler.go index 4b2cada72..e55205ac1 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -28,6 +28,9 @@ func NewHandler(k keeper.Keeper, govKeeper gov.Keeper) sdk.Handler { return handleMsgRemoveValidatorAfterProposal(ctx, msg, k, govKeeper) // Beacon Chain New Staking in BEP-159 case types.MsgCreateValidatorOpen: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } if !sdk.IsUpgrade(sdk.BEP159Phase2) { return sdk.ErrMsgNotSupported("BEP-159 Phase 2 not activated yet").Result() } @@ -40,19 +43,45 @@ func NewHandler(k keeper.Keeper, govKeeper gov.Keeper) sdk.Handler { return handleMsgUndelegate(ctx, msg, k) // case MsgSideChain case types.MsgCreateSideChainValidator: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgCreateSideChainValidator(ctx, msg, k) case types.MsgEditSideChainValidator: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgEditSideChainValidator(ctx, msg, k) case types.MsgCreateSideChainValidatorWithVoteAddr: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgCreateSideChainValidatorWithVoteAddr(ctx, msg, k) case types.MsgEditSideChainValidatorWithVoteAddr: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgEditSideChainValidatorWithVoteAddr(ctx, msg, k) case types.MsgSideChainDelegate: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgSideChainDelegate(ctx, msg, k) case types.MsgSideChainRedelegate: + if sdk.IsUpgrade(sdk.FirstSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgSideChainRedelegate(ctx, msg, k) case types.MsgSideChainUndelegate: + if sdk.IsUpgrade(sdk.SecondSunsetFork) { + return sdk.ErrMsgNotSupported("").Result() + } return handleMsgSideChainUndelegate(ctx, msg, k) + case types.MsgSideChainStakeMigration: + if !sdk.IsUpgrade(sdk.FirstSunsetFork) || sdk.IsUpgrade(sdk.SecondSunsetFork) { + return sdk.ErrMsgNotSupported("MsgSideChainStakeMigration is only enabled between FirstSunsetFork and SecondSunsetFork").Result() + } + return handleMsgSideChainStakeMigration(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -79,7 +108,7 @@ func NewStakeHandler(k Keeper) sdk.Handler { } } -//_____________________________________________________________________ +// _____________________________________________________________________ // These functions assume everything has been authenticated, // now we just perform action and save @@ -446,7 +475,7 @@ func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keep } func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { - ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) + ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount, true) if err != nil { return err.Result() } diff --git a/x/stake/handler_sidechain.go b/x/stake/handler_sidechain.go index a44a91055..8cc518bad 100644 --- a/x/stake/handler_sidechain.go +++ b/x/stake/handler_sidechain.go @@ -3,8 +3,12 @@ package stake import ( "bytes" "fmt" + "strconv" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/bsc" + "github.com/cosmos/cosmos-sdk/bsc/rlp" + "github.com/cosmos/cosmos-sdk/pubsub" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/keeper" "github.com/cosmos/cosmos-sdk/x/stake/tags" @@ -437,7 +441,12 @@ func handleMsgSideChainUndelegate(ctx sdk.Context, msg MsgSideChainUndelegate, k return err.Result() } - ubd, err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, shares) + var ( + ubd types.UnbondingDelegation + events sdk.Events + ) + + ubd, err = k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, shares, true) if err != nil { return err.Result() } @@ -468,7 +477,112 @@ func handleMsgSideChainUndelegate(ctx sdk.Context, msg MsgSideChainUndelegate, k k.PbsbServer.Publish(event) } - return sdk.Result{Data: finishTime, Tags: tags} + return sdk.Result{Data: finishTime, Tags: tags, Events: events} +} + +func handleMsgSideChainStakeMigration(ctx sdk.Context, msg MsgSideChainStakeMigration, k keeper.Keeper) sdk.Result { + if scCtx, err := k.ScKeeper.PrepareCtxForSideChain(ctx, k.DestChainName); err != nil { + return ErrInvalidSideChainId(k.Codespace()).Result() + } else { + ctx = scCtx + } + + denom := k.BondDenom(ctx) + if msg.Amount.Denom != denom { + return ErrBadDenom(k.Codespace()).Result() + } + + shares, sdkErr := k.ValidateUnbondAmount(ctx, msg.RefundAddr, msg.ValidatorSrcAddr, msg.Amount.Amount) + if sdkErr != nil { + return sdkErr.Result() + } + + // unbond immediately + ubd, events, sdkErr := k.UnboundDelegation(ctx, msg.RefundAddr, msg.ValidatorSrcAddr, shares) + if sdkErr != nil { + return sdkErr.Result() + } + + // send coins to pegAccount + relayFee := sdk.NewCoin(denom, types.StakeMigrationRelayFee) + transferAmt := sdk.Coins{ubd.Balance}.Plus(sdk.Coins{relayFee}) + _, sdkErr = k.BankKeeper.SendCoins(ctx, msg.RefundAddr, sdk.PegAccount, transferAmt) + if sdkErr != nil { + return sdkErr.Result() + } + + // send cross-chain package + bscAmount := bsc.ConvertBCAmountToBSCAmount(ubd.Balance.Amount) + stakeMigrationSynPackage := types.StakeMigrationSynPackage{ + OperatorAddress: msg.ValidatorDstAddr, + DelegatorAddress: msg.DelegatorAddr, + RefundAddress: msg.RefundAddr, + Amount: bscAmount, + } + + encodedPackage, err := rlp.EncodeToBytes(stakeMigrationSynPackage) + if err != nil { + return sdk.ErrInternal("encode stake migration package error").Result() + } + + bscRelayFee := bsc.ConvertBCAmountToBSCAmount(relayFee.Amount) + sendSeq, sdkErr := k.IbcKeeper.CreateRawIBCPackageByIdWithFee(ctx.DepriveSideChainKeyPrefix(), k.DestChainId, types.StakeMigrationChannelID, sdk.SynCrossChainPackageType, + encodedPackage, *bscRelayFee) + if sdkErr != nil { + return sdkErr.Result() + } + + if k.PbsbServer != nil && ctx.IsDeliverTx() { + uEvent := types.ChainUndelegateEvent{ + UndelegateEvent: types.UndelegateEvent{ + StakeEvent: types.StakeEvent{ + IsFromTx: true, + }, + Delegator: msg.RefundAddr, + Validator: msg.ValidatorSrcAddr, + Amount: msg.Amount.Amount, + Denom: msg.Amount.Denom, + TxHash: ctx.Value(baseapp.TxHashKey).(string), + }, + ChainId: k.DestChainName, + } + k.PbsbServer.Publish(uEvent) + + completedUBDEvent := types.CompletedUBDEvent{ + CompUBDs: []types.UnbondingDelegation{ubd}, + ChainId: k.DestChainName, + } + k.PbsbServer.Publish(completedUBDEvent) + + ctEvent := pubsub.CrossTransferEvent{ + ChainId: k.DestChainName, + RelayerFee: types.StakeMigrationRelayFee, + Type: types.TransferOutType, + From: msg.RefundAddr.String(), + Denom: denom, + To: []pubsub.CrossReceiver{{sdk.PegAccount.String(), ubd.Balance.Amount}}, + } + k.PbsbServer.Publish(ctEvent) + } + + finishTime := types.MsgCdc.MustMarshalBinaryLengthPrefixed(ubd.MinTime) + txTags := sdk.NewTags( + tags.Delegator, []byte(msg.RefundAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.EndTime, finishTime, + ) + + for _, coin := range transferAmt { + if coin.Amount > 0 { + txTags = append(txTags, sdk.GetPegInTag(coin.Denom, coin.Amount)) + } + } + txTags = append(txTags, sdk.MakeTag(types.TagStakeMigrationSendSequence, []byte(strconv.FormatUint(sendSeq, 10)))) + + return sdk.Result{ + Tags: txTags, + Events: events, + } } // we allow the self-delegator delegating/redelegating to its validator. diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index d4ec96cfb..01494de81 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/hex" + "errors" "fmt" "math/big" "strconv" @@ -16,6 +17,11 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +var ( + ErrNotEnoughRelayerFeeForCrossPkg = sdk.ErrInternal("not enough funds to cover relay fee") + ErrNoFeeCalculator = sdk.ErrInternal("no fee calculator of distributeUndelegated") +) + // return a specific delegation func (k Keeper) GetDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) ( @@ -45,6 +51,11 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati return delegations } +func (k Keeper) IteratorAllDelegations(ctx sdk.Context) sdk.Iterator { + store := ctx.KVStore(k.storeKey) + return sdk.KVStorePrefixIterator(store, DelegationKey) +} + // return a given amount of all the delegations from a delegator func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { @@ -151,7 +162,7 @@ func (k Keeper) RemoveDelegationByVal(ctx sdk.Context, delAddr sdk.AccAddress, v store.Delete(GetDelegationKeyByValIndexKey(valAddr, delAddr)) } -//_____________________________________________________________________________________ +// _____________________________________________________________________________________ func (k Keeper) SetSimplifiedDelegations(ctx sdk.Context, height int64, validator sdk.ValAddress, simDels []types.SimplifiedDelegation) { store := ctx.KVStore(k.storeKey) @@ -174,7 +185,7 @@ func (k Keeper) RemoveSimplifiedDelegations(ctx sdk.Context, height int64, valid store.Delete(GetSimplifiedDelegationsKey(height, validator)) } -//_____________________________________________________________________________________ +// _____________________________________________________________________________________ // return a given amount of all the delegator unbonding-delegations func (k Keeper) GetUnbondingDelegations(ctx sdk.Context, delegator sdk.AccAddress, @@ -325,7 +336,7 @@ func (k Keeper) DequeueAllMatureUnbondingQueue(ctx sdk.Context, currTime time.Ti return matureUnbonds } -//_____________________________________________________________________________________ +// _____________________________________________________________________________________ // return a given amount of all the delegator redelegations func (k Keeper) GetRedelegations(ctx sdk.Context, delegator sdk.AccAddress, @@ -478,7 +489,7 @@ func (k Keeper) DequeueAllMatureRedelegationQueue(ctx sdk.Context, currTime time return matureRedelegations } -//_____________________________________________________________________________________ +// _____________________________________________________________________________________ func (k Keeper) SyncDelegationByValDel(ctx sdk.Context, valAddr sdk.ValAddress, delAddr sdk.AccAddress) { delegation, found := k.GetDelegation(ctx, delAddr, valAddr) @@ -608,7 +619,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA return amount, nil } -//______________________________________________________________________________________________________ +// ______________________________________________________________________________________________________ // get info for begin functions: MinTime and CreationHeight func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( @@ -637,9 +648,25 @@ func (k Keeper) getBeginInfo(ctx sdk.Context, valSrcAddr sdk.ValAddress) ( } } +func (k Keeper) UnboundDelegation(ctx sdk.Context, + delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec, +) (types.UnbondingDelegation, sdk.Events, sdk.Error) { + ubd, err := k.BeginUnbonding(ctx, delAddr, valAddr, sharesAmount, false) + if err != nil { + return ubd, nil, err + } + ubd, events, err := k.CompleteUnbonding(ctx, ubd.DelegatorAddr, ubd.ValidatorAddr) + if err != nil { + return ubd, events, err + } + + return ubd, events, nil +} + // begin unbonding an unbonding record func (k Keeper) BeginUnbonding(ctx sdk.Context, - delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.UnbondingDelegation, sdk.Error) { + delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec, + enqueue bool) (types.UnbondingDelegation, sdk.Error) { // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 _, found := k.GetUnbondingDelegation(ctx, delAddr, valAddr) @@ -656,6 +683,9 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount.RawInt()) completionTime := ctx.BlockHeader().Time.Add(k.UnbondingTime(ctx)) + if !enqueue { + completionTime = ctx.BlockHeader().Time + } ubd := types.UnbondingDelegation{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, @@ -666,7 +696,9 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, CrossStake: ctx.CrossStake(), } k.SetUnbondingDelegation(ctx, ubd) - k.InsertUnbondingQueue(ctx, ubd) + if enqueue { + k.InsertUnbondingQueue(ctx, ubd) + } return ubd, nil } @@ -688,7 +720,12 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd if ubd.CrossStake { events, err = k.crossDistributeUndelegated(ctx, delAddr, valAddr) if err != nil { - return ubd, sdk.Events{}, err + if sdk.IsUpgrade(sdk.SecondSunsetFork) && + errors.Is(err, ErrNotEnoughRelayerFeeForCrossPkg) { + k.Logger(ctx).Error("not enough funds to cover relay fee, skip crossDistribute") + } else { + return ubd, sdk.Events{}, err + } } if k.AddrPool != nil { k.AddrPool.AddAddrs([]sdk.AccAddress{sdk.PegAccount}) @@ -702,6 +739,20 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd return ubd, events, nil } +func (k Keeper) IsAutoUnDelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) bool { + store := ctx.KVStore(k.storeKey) + key := GetAutoUnDelegateIndexKey(delAddr, valAddr) + + return store.Has(key) +} + +func (k Keeper) SetAutoUnDelegate(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + + key := GetAutoUnDelegateIndexKey(delAddr, valAddr) + store.Set(key, []byte{}) // index, store empty bytes +} + // complete unbonding an unbonding record func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { @@ -820,14 +871,22 @@ func (k Keeper) ValidateUnbondAmount( func (k Keeper) crossDistributeUndelegated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Events, sdk.Error) { denom := k.BondDenom(ctx) amount := k.BankKeeper.GetCoins(ctx, delAddr).AmountOf(denom) - relayFeeCalc := fees.GetCalculator(types.CrossDistributeUndelegatedRelayFee) + + var relayFeeCalc fees.FeeCalculator + isAutoUnDelegate := k.IsAutoUnDelegate(ctx, delAddr, valAddr) + if sdk.IsUpgrade(sdk.SecondSunsetFork) && isAutoUnDelegate { + relayFeeCalc = fees.FreeFeeCalculator() + } else { + relayFeeCalc = fees.GetCalculator(types.CrossDistributeUndelegatedRelayFee) + } if relayFeeCalc == nil { - return sdk.Events{}, sdk.ErrInternal("no fee calculator of distributeUndelegated") + return sdk.Events{}, ErrNoFeeCalculator } relayFee := relayFeeCalc(nil) if relayFee.Tokens.AmountOf(denom) >= amount { - return sdk.Events{}, sdk.ErrInternal("not enough funds to cover relay fee") + return sdk.Events{}, ErrNotEnoughRelayerFeeForCrossPkg } + bscRelayFee := bsc.ConvertBCAmountToBSCAmount(relayFee.Tokens.AmountOf(denom)) bscTransferAmount := new(big.Int).Sub(bsc.ConvertBCAmountToBSCAmount(amount), bscRelayFee) @@ -838,18 +897,30 @@ func (k Keeper) crossDistributeUndelegated(ctx sdk.Context, delAddr sdk.AccAddre return sdk.Events{}, sdk.ErrInternal(err.Error()) } - transferPackage := types.CrossStakeDistributeUndelegatedSynPackage{ - EventType: types.CrossStakeTypeDistributeUndelegated, - Amount: bscTransferAmount, - Recipient: recipient, - Validator: valAddr, + var transferPackage interface{} + if !sdk.IsUpgrade(sdk.SecondSunsetFork) { + transferPackage = types.CrossStakeDistributeUndelegatedSynPackage{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: bscTransferAmount, + Recipient: recipient, + Validator: valAddr, + } + } else { + transferPackage = types.CrossStakeDistributeUndelegatedSynPackageV2{ + EventType: types.CrossStakeTypeDistributeUndelegated, + Amount: bscTransferAmount, + Recipient: recipient, + Validator: valAddr, + IsAutoUnDelegate: isAutoUnDelegate, + } } + encodedPackage, err := rlp.EncodeToBytes(transferPackage) if err != nil { return sdk.Events{}, sdk.ErrInternal(err.Error()) } - sendSeq, sdkErr := k.ibcKeeper.CreateRawIBCPackageByIdWithFee(ctx.DepriveSideChainKeyPrefix(), k.DestChainId, types.CrossStakeChannelID, + sendSeq, sdkErr := k.IbcKeeper.CreateRawIBCPackageByIdWithFee(ctx.DepriveSideChainKeyPrefix(), k.DestChainId, types.CrossStakeChannelID, sdk.SynCrossChainPackageType, encodedPackage, *bscRelayFee) if sdkErr != nil { return sdk.Events{}, sdkErr diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 6a32c8a3b..687cb07b0 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -283,7 +283,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { keeper.SetDelegation(ctx, delegation) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block @@ -337,7 +337,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { // unbond the all self-delegation to put validator in unbonding state val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block @@ -357,7 +357,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockTime(blockTime2) // unbond some of the other delegation's shares - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(6)) + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(6), true) require.NoError(t, err) // retrieve the unbonding delegation @@ -409,7 +409,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block @@ -439,7 +439,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.Equal(t, 1, len(matureUbds)) // unbond all the other delegation's shares - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(10)) + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) @@ -491,7 +491,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block @@ -499,7 +499,7 @@ func TestUnbondingAllDelegationFromValidator(t *testing.T) { require.Equal(t, 1, len(updates)) // unbond all the remaining delegation - _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(10)) + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // validator should still be in state and still be in unbonding state @@ -713,7 +713,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block @@ -795,7 +795,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockTime(time.Unix(333, 0)) // unbond the all self-delegation to put validator in unbonding state - _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10)) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecWithoutFra(10), true) require.NoError(t, err) // end block diff --git a/x/stake/keeper/distribute.go b/x/stake/keeper/distribute.go index 81565f9b5..f5c54924e 100644 --- a/x/stake/keeper/distribute.go +++ b/x/stake/keeper/distribute.go @@ -202,7 +202,7 @@ func (k Keeper) DistributeInBreathBlock(ctx sdk.Context, sideChainId string) sdk } totalRewardDec = sdk.NewDec(totalReward) - //distribute commission + // distribute commission commission = totalRewardDec.Mul(validator.Commission.Rate) if commission.RawInt() > 0 { if _, _, err := k.BankKeeper.AddCoins(ctx, validator.GetFeeAddr(), sdk.Coins{sdk.NewCoin(bondDenom, commission.RawInt())}); err != nil { @@ -213,7 +213,7 @@ func (k Keeper) DistributeInBreathBlock(ctx sdk.Context, sideChainId string) sdk } } - //calculate rewards for delegators + // calculate rewards for delegators remainReward := totalRewardDec.Sub(commission) ctx.Logger().Info("FeeCalculation commission", "rate", validator.Commission.Rate, "commission", commission, "remainReward", remainReward, "delegations", delegations) rewards = allocate(simDelsToSharers(delegations), remainReward) @@ -233,12 +233,12 @@ func (k Keeper) DistributeInBreathBlock(ctx sdk.Context, sideChainId string) sdk toSaveRewards = append(toSaveRewards, toSaveReward) } - //track validator and distribution address mapping + // track validator and distribution address mapping toSaveValDistAddrs = append(toSaveValDistAddrs, types.StoredValDistAddr{ Validator: validator.OperatorAddr, DistributeAddr: validator.DistributionAddr}) - //update address pool + // update address pool changedAddrs := [2]sdk.AccAddress{validator.FeeAddr, validator.DistributionAddr} if k.AddrPool != nil { k.AddrPool.AddAddrs(changedAddrs[:]) @@ -255,14 +255,14 @@ func (k Keeper) DistributeInBreathBlock(ctx sdk.Context, sideChainId string) sdk ValTokens: validator.GetTokens(), TotalReward: totalRewardDec, Commission: commission, - Rewards: nil, //do not publish rewards in breathe blocks + Rewards: nil, // do not publish rewards in breathe blocks }) } } ctx.Logger().Info("FeeCalculation DistributeInBreathBlock", "toSaveRewards", toSaveRewards) - if len(toSaveRewards) > 0 { //to save rewards - //1) get batch size from parameters, 2) hard limit to make sure rewards can be distributed in a day + if len(toSaveRewards) > 0 { // to save rewards + // 1) get batch size from parameters, 2) hard limit to make sure rewards can be distributed in a day batchSize := getDistributionBatchSize(k.GetParams(ctx).RewardDistributionBatchSize, int64(len(toSaveRewards))) batchCount := int64(len(toSaveRewards)) / batchSize if int64(len(toSaveRewards))%batchSize != 0 { @@ -328,7 +328,7 @@ func (k Keeper) distributeSingleBatch(ctx sdk.Context, sideChainId string) sdk.E var toPublish []types.DistributionData // data to be published in blocks var toPublishRewards []types.Reward // rewards to be published in blocks - var changedAddrs []sdk.AccAddress //changed addresses + var changedAddrs []sdk.AccAddress // changed addresses bondDenom := k.BondDenom(ctx) var events sdk.Events @@ -368,7 +368,8 @@ func (k Keeper) distributeSingleBatch(ctx sdk.Context, sideChainId string) sdk.E // cross distribute reward for _, addr := range crossStakeAddrSet { balance := k.BankKeeper.GetCoins(ctx, addr).AmountOf(bondDenom) - if balance >= types.MinRewardThreshold { + if balance >= types.MinRewardThreshold || + (sdk.IsUpgrade(sdk.SecondSunsetFork) && balance >= types.MinRewardThresholdAfterSecondSunsetFork) { event, err := crossDistributeReward(k, ctx, addr, balance) if err != nil { panic(err) @@ -386,7 +387,7 @@ func (k Keeper) distributeSingleBatch(ctx sdk.Context, sideChainId string) sdk.E k.removeRewardValDistAddrs(ctx) } - //update address pool + // update address pool if k.AddrPool != nil { k.AddrPool.AddAddrs(changedAddrs[:]) } @@ -467,7 +468,7 @@ func crossDistributeReward(k Keeper, ctx sdk.Context, rewardCAoB sdk.AccAddress, return sdk.Events{}, err } - sendSeq, sdkErr := k.ibcKeeper.CreateRawIBCPackageByIdWithFee(ctx.DepriveSideChainKeyPrefix(), k.DestChainId, types.CrossStakeChannelID, sdk.SynCrossChainPackageType, + sendSeq, sdkErr := k.IbcKeeper.CreateRawIBCPackageByIdWithFee(ctx.DepriveSideChainKeyPrefix(), k.DestChainId, types.CrossStakeChannelID, sdk.SynCrossChainPackageType, encodedPackage, *bscRelayFee) if sdkErr != nil { return sdk.Events{}, sdkErr diff --git a/x/stake/keeper/ibc.go b/x/stake/keeper/ibc.go index 4e6a51f9d..f22242b44 100644 --- a/x/stake/keeper/ibc.go +++ b/x/stake/keeper/ibc.go @@ -10,23 +10,23 @@ const ChannelName = "stake" const ChannelId = sdk.ChannelID(8) func (k Keeper) SaveValidatorSetToIbc(ctx sdk.Context, sideChainId string, ibcPackage types.IbcValidatorSetPackage) (seq uint64, sdkErr sdk.Error) { - if k.ibcKeeper == nil { + if k.IbcKeeper == nil { return 0, sdk.ErrInternal("the keeper is not prepared for side chain") } bz, err := rlp.EncodeToBytes(ibcPackage) if err != nil { return 0, sdk.ErrInternal("failed to encode IbcValidatorSetPackage") } - return k.ibcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) + return k.IbcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) } func (k Keeper) SaveValidatorWithVoteAddrSetToIbc(ctx sdk.Context, sideChainId string, ibcPackage types.IbcValidatorWithVoteAddrSetPackage) (seq uint64, sdkErr sdk.Error) { - if k.ibcKeeper == nil { + if k.IbcKeeper == nil { return 0, sdk.ErrInternal("the keeper is not prepared for side chain") } bz, err := rlp.EncodeToBytes(ibcPackage) if err != nil { return 0, sdk.ErrInternal("failed to encode IbcValidatorSetPackage") } - return k.ibcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) + return k.IbcKeeper.CreateIBCSyncPackage(ctx, sideChainId, ChannelName, bz) } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 8b43491f3..9224e622c 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -33,7 +33,7 @@ type Keeper struct { // the two keepers are optional, // if you want to enable side chains, you need call `SetupForSideChain` - ibcKeeper *ibc.Keeper + IbcKeeper *ibc.Keeper ScKeeper *sidechain.Keeper DestChainId sdk.ChainID @@ -62,7 +62,7 @@ func NewKeeper(cdc *codec.Codec, key, rewardKey, tkey sdk.StoreKey, ck bank.Keep } func (k Keeper) initIbc() { - if k.ibcKeeper == nil { + if k.IbcKeeper == nil { return } err := k.ScKeeper.RegisterChannel(ChannelName, ChannelId, &k) @@ -73,7 +73,7 @@ func (k Keeper) initIbc() { func (k *Keeper) SetupForSideChain(scKeeper *sidechain.Keeper, ibcKeeper *ibc.Keeper) { k.ScKeeper = scKeeper - k.ibcKeeper = ibcKeeper + k.IbcKeeper = ibcKeeper k.initIbc() } @@ -102,6 +102,11 @@ func (k Keeper) Codespace() sdk.CodespaceType { return k.codespace } +// return the cdc +func (k Keeper) CDC() *codec.Codec { + return k.cdc +} + //_______________________________________________________________________ // load the pool diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index e76bcb124..288c62441 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -50,6 +50,8 @@ var ( // Keys for reward store prefix RewardBatchKey = []byte{0x01} // key for batch of rewards RewardValDistAddrKey = []byte{0x02} // key for rewards' validator <-> distribution address mapping + + AutoUndelegateIndexKey = []byte{0x61} // prefix for each key for an auto undelegate, by validator operator ) const ( @@ -340,3 +342,14 @@ func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddr func GetValLatestUpdateConsAddrTimeKey(valAddr sdk.ValAddress) []byte { return append(ValLatestUpdateConsAddrTimeKey, valAddr.Bytes()...) } + +// gets the prefix keyspace for the indexes of auto unDelegate delegations for a validator +func GetAutoUnDelegateByValIndexKey(valAddr sdk.ValAddress) []byte { + return append(AutoUndelegateIndexKey, valAddr.Bytes()...) +} + +// gets the index-key for an auto unDelegate delegation, stored by validator-index +// VALUE: none (key rearrangement used) +func GetAutoUnDelegateIndexKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { + return append(GetAutoUnDelegateByValIndexKey(valAddr), delAddr.Bytes()...) +} diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 5ff6e20b5..5953d3a58 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -61,6 +61,24 @@ func (k Keeper) GetValidatorBySideConsAddr(ctx sdk.Context, sideConsAddr []byte) return k.GetValidator(ctx, opAddr) } +func (k Keeper) GetAllStatusVotingPower(ctx sdk.Context) sdk.Dec { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsByConsAddrKey) + defer iterator.Close() + + votingPower := sdk.NewDecFromInt(0) + for ; iterator.Valid(); iterator.Next() { + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + ctx.Logger().Error("can't load validator", "operator_addr", string(address)) + continue + } + votingPower = votingPower.Add(validator.GetPower()) + } + return votingPower +} + func (k Keeper) GetValidatorBySideVoteAddr(ctx sdk.Context, sideVoteAddr []byte) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) opAddr := store.Get(GetValidatorBySideVoteAddrKey(sideVoteAddr)) diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go index cfad6b733..64ebb11da 100644 --- a/x/stake/querier/queryable_test.go +++ b/x/stake/querier/queryable_test.go @@ -305,7 +305,7 @@ func TestQueryDelegation(t *testing.T) { require.NotNil(t, err) // Query unbonging delegation - keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(sdk.NewDecWithoutFra(10).RawInt())) + keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(sdk.NewDecWithoutFra(10).RawInt()), true) query = abci.RequestQuery{ Path: "/custom/stake/unbondingDelegation", diff --git a/x/stake/stake.go b/x/stake/stake.go index da841e942..79dac91c4 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -42,6 +42,7 @@ type ( MsgSideChainDelegate = types.MsgSideChainDelegate MsgSideChainRedelegate = types.MsgSideChainRedelegate MsgSideChainUndelegate = types.MsgSideChainUndelegate + MsgSideChainStakeMigration = types.MsgSideChainStakeMigration DistributionEvent = types.DistributionEvent DistributionData = types.DistributionData diff --git a/x/stake/stake_migration/alias.go b/x/stake/stake_migration/alias.go new file mode 100644 index 000000000..74e797e40 --- /dev/null +++ b/x/stake/stake_migration/alias.go @@ -0,0 +1,9 @@ +package stake_migration + +import ( + "github.com/cosmos/cosmos-sdk/x/stake/keeper" +) + +type ( + Keeper = keeper.Keeper +) diff --git a/x/stake/stake_migration/pub.go b/x/stake/stake_migration/pub.go new file mode 100644 index 000000000..2d5e36a07 --- /dev/null +++ b/x/stake/stake_migration/pub.go @@ -0,0 +1,28 @@ +package stake_migration + +import ( + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/pubsub" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/cross_stake" +) + +func PublishStakeMigrationRefundEvent(ctx sdk.Context, keeper cross_stake.Keeper, from string, to []pubsub.CrossReceiver, symbol string, + eventType string, relayerFee int64, +) { + txHash := ctx.Value(baseapp.TxHashKey) + if txHashStr, ok := txHash.(string); ok { + event := pubsub.CrossTransferEvent{ + TxHash: txHashStr, + ChainId: keeper.DestChainName, + RelayerFee: relayerFee, + Type: eventType, + From: from, + Denom: symbol, + To: to, + } + keeper.PbsbServer.Publish(event) + } else { + ctx.Logger().With("module", "stake").Error("failed to get txhash, will not publish cross transfer event ") + } +} diff --git a/x/stake/stake_migration/serialize.go b/x/stake/stake_migration/serialize.go new file mode 100644 index 000000000..439ba1cbb --- /dev/null +++ b/x/stake/stake_migration/serialize.go @@ -0,0 +1,15 @@ +package stake_migration + +import ( + "github.com/cosmos/cosmos-sdk/bsc/rlp" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +func DeserializeStakeMigrationRefundPackage(serializedPackage []byte) (*types.StakeMigrationSynPackage, error) { + var pack types.StakeMigrationSynPackage + err := rlp.DecodeBytes(serializedPackage, &pack) + if err != nil { + return nil, err + } + return &pack, nil +} diff --git a/x/stake/stake_migration/stake_migration.go b/x/stake/stake_migration/stake_migration.go new file mode 100644 index 000000000..c743be9fb --- /dev/null +++ b/x/stake/stake_migration/stake_migration.go @@ -0,0 +1,87 @@ +package stake_migration + +import ( + "github.com/cosmos/cosmos-sdk/bsc" + "github.com/cosmos/cosmos-sdk/pubsub" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +type StakeMigrationApp struct { + stakeKeeper Keeper +} + +func NewStakeMigrationApp(stakeKeeper Keeper) *StakeMigrationApp { + return &StakeMigrationApp{ + stakeKeeper: stakeKeeper, + } +} + +func (app *StakeMigrationApp) ExecuteSynPackage(ctx sdk.Context, payload []byte, relayFee int64) sdk.ExecuteResult { + panic("receive unexpected syn package") +} + +func (app *StakeMigrationApp) ExecuteAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + if len(payload) == 0 { + app.stakeKeeper.Logger(ctx).Error("receive empty stake migration ack package") + return sdk.ExecuteResult{} + } + + pack, err := DeserializeStakeMigrationRefundPackage(payload) + if err != nil { + app.stakeKeeper.Logger(ctx).Error("unmarshal stake migration refund package error", "err", err.Error(), "package", string(payload)) + return sdk.ExecuteResult{} + } + + result, err := app.handleRefund(ctx, pack) + if err != nil { + app.stakeKeeper.Logger(ctx).Error("handle stake migration refund package error", "err", err.Error(), "package", string(payload)) + return sdk.ExecuteResult{} + } + + return result +} + +func (app *StakeMigrationApp) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) sdk.ExecuteResult { + if len(payload) == 0 { + app.stakeKeeper.Logger(ctx).Error("receive empty stake migration fail ack package") + return sdk.ExecuteResult{} + } + + pack, err := DeserializeStakeMigrationRefundPackage(payload) + if err != nil { + app.stakeKeeper.Logger(ctx).Error("unmarshal stake migration refund package error", "err", err.Error(), "package", string(payload)) + return sdk.ExecuteResult{} + } + + result, err := app.handleRefund(ctx, pack) + if err != nil { + app.stakeKeeper.Logger(ctx).Error("handle stake migration refund package error", "err", err.Error(), "package", string(payload)) + return sdk.ExecuteResult{} + } + + return result +} + +func (app *StakeMigrationApp) handleRefund(ctx sdk.Context, pack *types.StakeMigrationSynPackage) (sdk.ExecuteResult, error) { + symbol := app.stakeKeeper.BondDenom(ctx) + amount := bsc.ConvertBSCAmountToBCAmount(pack.Amount) + coins := sdk.Coins{sdk.NewCoin(symbol, amount)} + _, err := app.stakeKeeper.BankKeeper.SendCoins(ctx, sdk.PegAccount, pack.RefundAddress, coins) + if err != nil { + return sdk.ExecuteResult{}, err + } + + // publish event + if app.stakeKeeper.AddrPool != nil && ctx.IsDeliverTx() { + app.stakeKeeper.AddrPool.AddAddrs([]sdk.AccAddress{sdk.PegAccount, pack.RefundAddress}) + } + if app.stakeKeeper.PbsbServer != nil && ctx.IsDeliverTx() { + PublishStakeMigrationRefundEvent(ctx, app.stakeKeeper, sdk.PegAccount.String(), []pubsub.CrossReceiver{{pack.RefundAddress.String(), pack.Amount.Int64()}}, + app.stakeKeeper.BondDenom(ctx), types.TransferInType, 0) + } + + return sdk.ExecuteResult{ + Tags: sdk.Tags{sdk.GetPegOutTag(symbol, amount)}, + }, nil +} diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index 6d78180bb..08bb0fc25 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -23,6 +23,7 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSideChainDelegate{}, "cosmos-sdk/MsgSideChainDelegate", nil) cdc.RegisterConcrete(MsgSideChainRedelegate{}, "cosmos-sdk/MsgSideChainRedelegate", nil) cdc.RegisterConcrete(MsgSideChainUndelegate{}, "cosmos-sdk/MsgSideChainUndelegate", nil) + cdc.RegisterConcrete(MsgSideChainStakeMigration{}, "cosmos-sdk/MsgSideChainStakeMigration", nil) cdc.RegisterConcrete(&Params{}, "params/StakeParamSet", nil) } diff --git a/x/stake/types/cross_stake.go b/x/stake/types/cross_stake.go index 49b3f6052..74d9c76cb 100644 --- a/x/stake/types/cross_stake.go +++ b/x/stake/types/cross_stake.go @@ -38,7 +38,8 @@ const ( DelegateCAoBSalt string = "Delegate" RewardCAoBSalt string = "Reward" - MinRewardThreshold int64 = 1e8 + MinRewardThreshold int64 = 1e8 + MinRewardThresholdAfterSecondSunsetFork int64 = 1e7 ) type CrossStakeAckPackage struct { @@ -79,6 +80,14 @@ type CrossStakeDistributeUndelegatedSynPackage struct { Amount *big.Int } +type CrossStakeDistributeUndelegatedSynPackageV2 struct { + EventType CrossStakeEventType + Recipient sdk.SmartChainAddress + Validator sdk.ValAddress + Amount *big.Int + IsAutoUnDelegate bool +} + type RefundError uint32 const ( diff --git a/x/stake/types/stake_migration.go b/x/stake/types/stake_migration.go new file mode 100644 index 000000000..2f617f35c --- /dev/null +++ b/x/stake/types/stake_migration.go @@ -0,0 +1,79 @@ +package types + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + StakeMigrationChannel = "stakeMigration" + + StakeMigrationChannelID sdk.ChannelID = 17 + + TagStakeMigrationSendSequence = "StakeMigrationSendSequence" + + MsgTypeSideChainStakeMigration = "side_stake_migration" + + StakeMigrationRelayFee int64 = 200000 // 0.002BNB, decimal 8 +) + +type StakeMigrationSynPackage struct { + OperatorAddress sdk.SmartChainAddress + DelegatorAddress sdk.SmartChainAddress + RefundAddress sdk.AccAddress + Amount *big.Int +} + +type MsgSideChainStakeMigration struct { + ValidatorSrcAddr sdk.ValAddress `json:"validator_src_addr"` + ValidatorDstAddr sdk.SmartChainAddress `json:"validator_dst_addr"` + DelegatorAddr sdk.SmartChainAddress `json:"delegator_addr"` + RefundAddr sdk.AccAddress `json:"refund_addr"` + Amount sdk.Coin `json:"amount"` +} + +func NewMsgSideChainStakeMigration(valAddr sdk.ValAddress, operatorAddr, delegatorAddr sdk.SmartChainAddress, refundAddr sdk.AccAddress, amount sdk.Coin) MsgSideChainStakeMigration { + return MsgSideChainStakeMigration{ + ValidatorSrcAddr: valAddr, + ValidatorDstAddr: operatorAddr, + DelegatorAddr: delegatorAddr, + RefundAddr: refundAddr, + Amount: amount, + } +} + +func (msg MsgSideChainStakeMigration) Route() string { return MsgRoute } +func (msg MsgSideChainStakeMigration) Type() string { return MsgTypeSideChainStakeMigration } +func (msg MsgSideChainStakeMigration) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.RefundAddr} +} + +func (msg MsgSideChainStakeMigration) GetSignBytes() []byte { + bz := MsgCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg MsgSideChainStakeMigration) ValidateBasic() sdk.Error { + if len(msg.ValidatorSrcAddr) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("Expected validator address length is %d, actual length is %d", sdk.AddrLen, len(msg.ValidatorSrcAddr))) + } + if msg.ValidatorDstAddr.IsEmpty() { + return sdk.ErrInvalidAddress("smart chain operator address is empty") + } + if msg.DelegatorAddr.IsEmpty() { + return sdk.ErrInvalidAddress("smart chain beneficiary address is empty") + } + if len(msg.RefundAddr) != sdk.AddrLen { + return sdk.ErrInvalidAddress(fmt.Sprintf("Expected refund address length is %d, actual length is %d", sdk.AddrLen, len(msg.RefundAddr))) + } + if msg.Amount.Amount <= 0 { + return ErrBadDelegationAmount(DefaultCodespace, "stake migration amount must be positive") + } + return nil +} + +func (msg MsgSideChainStakeMigration) GetInvolvedAddresses() []sdk.AccAddress { + return []sdk.AccAddress{msg.RefundAddr, sdk.AccAddress(msg.ValidatorSrcAddr)} +}