diff --git a/x/delegation/keeper/abci.go b/x/delegation/keeper/abci.go index 922da13c3..c8fefe536 100644 --- a/x/delegation/keeper/abci.go +++ b/x/delegation/keeper/abci.go @@ -1,8 +1,6 @@ package keeper import ( - "strings" - assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types" "github.com/ExocoreNetwork/exocore/x/delegation/types" @@ -13,92 +11,133 @@ import ( // EndBlock : completed Undelegation events according to the canCompleted blockHeight // This function will be triggered at the end of every block, it will query the undelegation state to get the records that need to be handled and try to complete the undelegation task. -func (k *Keeper) EndBlock(oCtx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - // #nosec G703 // the error is always nil - records, _ := k.GetPendingUndelegationRecords(oCtx, uint64(oCtx.BlockHeight())) +func (k *Keeper) EndBlock( + originalCtx sdk.Context, _ abci.RequestEndBlock, +) []abci.ValidatorUpdate { + logger := k.Logger(originalCtx) + records, err := k.GetPendingUndelegationRecords( + originalCtx, uint64(originalCtx.BlockHeight()), + ) + if err != nil { + logger.Error("failed to get pending undelegation records", "error", err) + } if len(records) == 0 { + logger.Info("no pending undelegation records") return []abci.ValidatorUpdate{} } - for _, record := range records { - ctx, writeCache := oCtx.CacheContext() - // check if the operator has been slashed or frozen + for i := range records { + record := records[i] // avoid implicit memory aliasing + ctx, writeCache := originalCtx.CacheContext() + // we can use `Must` here because we stored this record ourselves. operatorAccAddress := sdk.MustAccAddressFromBech32(record.OperatorAddr) - // todo: don't think about freezing the operator in current implementation - /* if k.slashKeeper.IsOperatorFrozen(ctx, operatorAccAddress) { - // reSet the completed height if the operator is frozen - record.CompleteBlockNumber = k.operatorKeeper.GetUnbondingExpirationBlockNumber(ctx, operatorAccAddress, record.BlockNumber) - if record.CompleteBlockNumber <= uint64(ctx.BlockHeight()) { - panic(fmt.Sprintf("the reset completedHeight isn't in future,setHeight:%v,curHeight:%v", record.CompleteBlockNumber, ctx.BlockHeight())) - } - _, innerError = k.SetSingleUndelegationRecord(ctx, record) - if innerError != nil { - panic(innerError) - } - continue - }*/ - - recordID := types.GetUndelegationRecordKey(record.BlockNumber, record.LzTxNonce, record.TxHash, record.OperatorAddr) + // TODO check if the operator has been slashed or frozen + recordID := types.GetUndelegationRecordKey( + record.BlockNumber, record.LzTxNonce, record.TxHash, record.OperatorAddr, + ) if k.GetUndelegationHoldCount(ctx, recordID) > 0 { - // delete it from state. then rewrite it with the next block - // #nosec G703 // the error is always nil - _ = k.DeleteUndelegationRecord(ctx, record) - // store it again with the next block and move on + // delete from all 3 states + if err := k.DeleteUndelegationRecord(ctx, record); err != nil { + logger.Error("failed to delete undelegation record", "error", err) + continue + } + // add back to all 3 states, with the new block height // #nosec G701 record.CompleteBlockNumber = uint64(ctx.BlockHeight()) + 1 - // we need to store two things here: one is the updated record in itself - // #nosec G703 // the error is always nil - recordKey, _ := k.SetSingleUndelegationRecord(ctx, record) - // and the other is the fact that it matures at the next block - // #nosec G703 // the error is always nil - _ = k.StorePendingUndelegationRecord(ctx, recordKey, record) + if err := k.SetUndelegationRecords( + ctx, []types.UndelegationRecord{*record}, + ); err != nil { + logger.Error("failed to set undelegation records", "error", err) + continue + } writeCache() continue } - // TODO(mike): ensure that operator is required to perform self delegation to match above. recordAmountNeg := record.Amount.Neg() // update delegation state deltaAmount := &types.DeltaDelegationAmounts{ WaitUndelegationAmount: recordAmountNeg, } - _, err := k.UpdateDelegationState(ctx, record.StakerID, record.AssetID, record.OperatorAddr, deltaAmount) - if err != nil { + if _, err := k.UpdateDelegationState( + ctx, record.StakerID, record.AssetID, record.OperatorAddr, deltaAmount, + ); err != nil { // use oCtx so that the error is logged on the original context - k.Logger(oCtx).Error("failed to update delegation state", "error", err) + logger.Error( + "failed to update delegation state", + "error", err, + ) continue } // update the staker state if record.AssetID == assetstypes.NativeAssetID { - parsedStakerID := strings.Split(record.StakerID, "_") - stakerAddr := sdk.AccAddress(hexutil.MustDecode(parsedStakerID[0])) - if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount(ctx, types.DelegatedPoolName, stakerAddr, sdk.NewCoins(sdk.NewCoin(assetstypes.NativeAssetDenom, record.ActualCompletedAmount))); err != nil { - k.Logger(oCtx).Error("failed to undelegate coins from module to account", "error", err) + stakerAddrHex, _, err := assetstypes.ParseID(record.StakerID) + if err != nil { + logger.Error( + "failed to parse staker ID", + "error", err, + ) continue } - } else { - err = k.assetsKeeper.UpdateStakerAssetState(ctx, record.StakerID, record.AssetID, assetstypes.DeltaStakerSingleAsset{ - WithdrawableAmount: record.ActualCompletedAmount, - PendingUndelegationAmount: recordAmountNeg, - }) + stakerAddrBytes, err := hexutil.Decode(stakerAddrHex) if err != nil { - k.Logger(oCtx).Error("failed to update staker asset state", "error", err) + logger.Error( + "failed to decode staker address", + "error", err, + ) + continue + } + stakerAddr := sdk.AccAddress(stakerAddrBytes) + if err := k.bankKeeper.UndelegateCoinsFromModuleToAccount( + ctx, types.DelegatedPoolName, stakerAddr, + sdk.NewCoins( + sdk.NewCoin(assetstypes.NativeAssetDenom, record.ActualCompletedAmount), + ), + ); err != nil { + logger.Error( + "failed to undelegate coins from module to account", + "error", err, + ) + continue + } + } else { + if err := k.assetsKeeper.UpdateStakerAssetState( + ctx, record.StakerID, record.AssetID, + assetstypes.DeltaStakerSingleAsset{ + WithdrawableAmount: record.ActualCompletedAmount, + PendingUndelegationAmount: recordAmountNeg, + }, + ); err != nil { + logger.Error( + "failed to update staker asset state", + "error", err, + ) continue } } // update the operator state - err = k.assetsKeeper.UpdateOperatorAssetState(ctx, operatorAccAddress, record.AssetID, assetstypes.DeltaOperatorSingleAsset{ - PendingUndelegationAmount: recordAmountNeg, - }) - if err != nil { - k.Logger(oCtx).Error("failed to update operator asset state", "error", err) + if err := k.assetsKeeper.UpdateOperatorAssetState( + ctx, operatorAccAddress, record.AssetID, + assetstypes.DeltaOperatorSingleAsset{ + PendingUndelegationAmount: recordAmountNeg, + }, + ); err != nil { + logger.Error( + "failed to update operator asset state", + "error", err, + ) continue } // delete the Undelegation records that have been complemented - // #nosec G703 // the error is always nil - _ = k.DeleteUndelegationRecord(ctx, record) + if err := k.DeleteUndelegationRecord(ctx, record); err != nil { + logger.Error( + "failed to delete undelegation record", + "error", err, + ) + continue + } // when calling `writeCache`, events are automatically emitted on the parent context writeCache() } diff --git a/x/delegation/keeper/msg_server.go b/x/delegation/keeper/msg_server.go index 735b58a3d..b538c928a 100644 --- a/x/delegation/keeper/msg_server.go +++ b/x/delegation/keeper/msg_server.go @@ -13,18 +13,16 @@ import ( var _ types.MsgServer = &Keeper{} -// DelegateAssetToOperator todo: Delegation and Undelegation from exoCore chain directly will be implemented in future.At the moment,they are executed from client chain -func (k *Keeper) DelegateAssetToOperator(goCtx context.Context, msg *types.MsgDelegation) (*types.DelegationResponse, error) { +// DelegateAssetToOperator delegates asset to operator. Currently, it only supports native token +func (k *Keeper) DelegateAssetToOperator( + goCtx context.Context, msg *types.MsgDelegation, +) (*types.DelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) logger := k.Logger(ctx) - // TODO: currently we only support delegation with native token by invoking service - if msg.AssetID != assetstypes.NativeAssetID { - logger.Error("failed to delegate asset", "error", types.ErrNotSupportYet, "assetID", msg.AssetID) - return nil, types.ErrNotSupportYet.Wrap("assets other than native token are not supported yet") - } - logger.Info("DelegateAssetToOperator-nativeToken", "msg", msg) - + // no need to validate whether assetID == native token, since that is done by ValidateBasic. + // we can use `Must` since pre-validated fromAddr := sdk.MustAccAddressFromBech32(msg.BaseInfo.FromAddress) + // create nonce and unique hash nonce, err := k.accountKeeper.GetSequence(ctx, fromAddr) if err != nil { logger.Error("failed to get nonce", "error", err) @@ -35,30 +33,36 @@ func (k *Keeper) DelegateAssetToOperator(goCtx context.Context, msg *types.MsgDe combined := fmt.Sprintf("%s-%d", txHash, nonce) uniqueHash := sha256.Sum256([]byte(combined)) - // test for refactor - delegationParamsList := newDelegationParams(msg.BaseInfo, assetstypes.NativeAssetAddr, assetstypes.NativeChainLzID, nonce, uniqueHash) + delegationParamsList := newDelegationParams( + msg.BaseInfo, assetstypes.NativeAssetAddr, assetstypes.NativeChainLzID, + nonce, uniqueHash, + ) + cachedCtx, writeFunc := ctx.CacheContext() for _, delegationParams := range delegationParamsList { - if err := k.DelegateTo(ctx, delegationParams); err != nil { - logger.Error("failed to delegate asset", "error", err, "delegationParams", delegationParams) - return &types.DelegationResponse{}, err - + if err := k.DelegateTo(cachedCtx, delegationParams); err != nil { + logger.Error( + "failed to delegate asset", + "error", err, + "delegationParams", delegationParams, + ) + return nil, err } } - + writeFunc() return &types.DelegationResponse{}, nil } -func (k *Keeper) UndelegateAssetFromOperator(goCtx context.Context, msg *types.MsgUndelegation) (*types.UndelegationResponse, error) { +// UndelegateAssetFromOperator undelegates asset from operator. Currently, it only supports +// native token. +func (k *Keeper) UndelegateAssetFromOperator( + goCtx context.Context, msg *types.MsgUndelegation, +) (*types.UndelegationResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) logger := k.Logger(ctx) - // TODO: currently we only support undelegation with native token by invoking service - if msg.AssetID != assetstypes.NativeAssetID { - logger.Error("failed to undelegate asset", "error", types.ErrNotSupportYet, "assetID", msg.AssetID) - return nil, types.ErrNotSupportYet.Wrap("assets other than native token are not supported yet") - } - logger.Info("UndelegateAssetFromOperator", "msg", msg) - + // can use `Must` since pre-validated fromAddr := sdk.MustAccAddressFromBech32(msg.BaseInfo.FromAddress) + // no need to check that `assetID` is native token, since that is done by ValidateBasic. + // create nonce and unique hash nonce, err := k.accountKeeper.GetSequence(ctx, fromAddr) if err != nil { logger.Error("failed to get nonce", "error", err) @@ -69,19 +73,31 @@ func (k *Keeper) UndelegateAssetFromOperator(goCtx context.Context, msg *types.M combined := fmt.Sprintf("%s-%d", txHash, nonce) uniqueHash := sha256.Sum256([]byte(combined)) - inputParamsList := newDelegationParams(msg.BaseInfo, assetstypes.NativeAssetAddr, assetstypes.NativeChainLzID, nonce, uniqueHash) + inputParamsList := newDelegationParams( + msg.BaseInfo, assetstypes.NativeAssetAddr, assetstypes.NativeChainLzID, + nonce, uniqueHash, + ) + cachedCtx, writeFunc := ctx.CacheContext() for _, inputParams := range inputParamsList { - if err := k.UndelegateFrom(ctx, inputParams); err != nil { + if err := k.UndelegateFrom(cachedCtx, inputParams); err != nil { return nil, err } } + writeFunc() return &types.UndelegationResponse{}, nil } -func newDelegationParams(baseInfo *types.DelegationIncOrDecInfo, assetAddrStr string, clientChainLzID, txNonce uint64, txHash common.Hash) []*types.DelegationOrUndelegationParams { +// newDelegationParams creates delegation params from the given base info. +func newDelegationParams( + baseInfo *types.DelegationIncOrDecInfo, + assetAddrStr string, clientChainLzID uint64, txNonce uint64, + txHash common.Hash, +) []*types.DelegationOrUndelegationParams { + // can use `Must` since pre-validated stakerAddr := sdk.MustAccAddressFromBech32(baseInfo.FromAddress).Bytes() res := make([]*types.DelegationOrUndelegationParams, 0, 1) for _, kv := range baseInfo.PerOperatorAmounts { + // can use `Must` since pre-validated operatorAddr := sdk.MustAccAddressFromBech32(kv.Key) inputParams := types.NewDelegationOrUndelegationParams( clientChainLzID, diff --git a/x/delegation/keeper/un_delegation_state.go b/x/delegation/keeper/un_delegation_state.go index d0b1f72df..2f11bc568 100644 --- a/x/delegation/keeper/un_delegation_state.go +++ b/x/delegation/keeper/un_delegation_state.go @@ -13,6 +13,8 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +// AllUndelegations function returns all the undelegation records in the module. +// It is used during `ExportGenesis` to export the undelegation records. func (k Keeper) AllUndelegations(ctx sdk.Context) (undelegations []types.UndelegationRecord, err error) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixUndelegationInfo) iterator := sdk.KVStorePrefixIterator(store, []byte{}) @@ -27,8 +29,13 @@ func (k Keeper) AllUndelegations(ctx sdk.Context) (undelegations []types.Undeleg return ret, nil } -// SetUndelegationRecords function saves the undelegation records to be handled when the handle time expires. -// When we save the undelegation records, we save them in three kv stores which are `KeyPrefixUndelegationInfo` `KeyPrefixStakerUndelegationInfo` and `KeyPrefixPendingUndelegations` +// SetUndelegationRecords stores the provided undelegation records. +// The records are stored with 3 different keys: +// (1) recordKey == blockNumber + lzNonce + txHash + operatorAddress => record +// (2) stakerID + assetID + lzNonce => recordKey +// (3) completeBlockNumber + lzNonce => recordKey +// If a record exists with the same key, it will be overwritten; however, that is not a big +// concern since the lzNonce and txHash are unique for each record. func (k *Keeper) SetUndelegationRecords(ctx sdk.Context, records []types.UndelegationRecord) error { singleRecordStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixUndelegationInfo) stakerUndelegationStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixStakerUndelegationInfo) @@ -53,6 +60,8 @@ func (k *Keeper) SetUndelegationRecords(ctx sdk.Context, records []types.Undeleg return nil } +// DeleteUndelegationRecord deletes the undelegation record from the module. +// The deletion is performed from all the 3 stores. func (k *Keeper) DeleteUndelegationRecord(ctx sdk.Context, record *types.UndelegationRecord) error { singleRecordStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixUndelegationInfo) stakerUndelegationStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixStakerUndelegationInfo) @@ -69,24 +78,7 @@ func (k *Keeper) DeleteUndelegationRecord(ctx sdk.Context, record *types.Undeleg return nil } -func (k *Keeper) SetSingleUndelegationRecord(ctx sdk.Context, record *types.UndelegationRecord) (recordKey []byte, err error) { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixUndelegationInfo) - bz := k.cdc.MustMarshal(record) - key := types.GetUndelegationRecordKey(record.BlockNumber, record.LzTxNonce, record.TxHash, record.OperatorAddr) - store.Set(key, bz) - return key, nil -} - -// StorePendingUndelegationRecord add it to handle the delay of completing undelegation caused by onHoldCount -// In the event that the undelegation is held by another module, this function is used within the EndBlocker to increment the scheduled completion block number by 1. -// Then the completion time of the undelegation will be delayed to the next block. -func (k *Keeper) StorePendingUndelegationRecord(ctx sdk.Context, singleRecKey []byte, record *types.UndelegationRecord) error { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixPendingUndelegations) - pendingUndelegationKey := types.GetPendingUndelegationRecordKey(record.CompleteBlockNumber, record.LzTxNonce) - store.Set(pendingUndelegationKey, singleRecKey) - return nil -} - +// GetUndelegationRecords returns the undelegation records for the provided record keys. func (k *Keeper) GetUndelegationRecords(ctx sdk.Context, singleRecordKeys []string) (record []*types.UndelegationRecord, err error) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixUndelegationInfo) ret := make([]*types.UndelegationRecord, 0) @@ -103,9 +95,9 @@ func (k *Keeper) GetUndelegationRecords(ctx sdk.Context, singleRecordKeys []stri return ret, nil } -// IterateUndelegationsByOperator iterate the undelegation records according to the operator -// and height filter. If the heightFilter isn't nil, only return the undelegations that the -// created height is greater than or equal to the filter height. +// IterateUndelegationsByOperator iterates over the undelegation records belonging to the +// provided operator and filter. If the filter is non-nil, it will only iterate over the +// records for which the block height is greater than or equal to the filter. func (k *Keeper) IterateUndelegationsByOperator( ctx sdk.Context, operator string, heightFilter *uint64, isUpdate bool, opFunc func(undelegation *types.UndelegationRecord) error, @@ -139,13 +131,8 @@ func (k *Keeper) IterateUndelegationsByOperator( return nil } -func (k *Keeper) SetStakerUndelegationInfo(ctx sdk.Context, stakerID, assetID string, recordKey []byte, lzNonce uint64) error { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixStakerUndelegationInfo) - key := types.GetStakerUndelegationRecordKey(stakerID, assetID, lzNonce) - store.Set(key, recordKey) - return nil -} - +// GetStakerUndelegationRecKeys returns the undelegation record keys corresponding to the provided +// staker and asset. func (k *Keeper) GetStakerUndelegationRecKeys(ctx sdk.Context, stakerID, assetID string) (recordKeyList []string, err error) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixStakerUndelegationInfo) iterator := sdk.KVStorePrefixIterator(store, []byte(strings.Join([]string{stakerID, assetID}, "/"))) @@ -158,6 +145,7 @@ func (k *Keeper) GetStakerUndelegationRecKeys(ctx sdk.Context, stakerID, assetID return ret, nil } +// GetStakerUndelegationRecords returns the undelegation records for the provided staker and asset. func (k *Keeper) GetStakerUndelegationRecords(ctx sdk.Context, stakerID, assetID string) (records []*types.UndelegationRecord, err error) { recordKeys, err := k.GetStakerUndelegationRecKeys(ctx, stakerID, assetID) if err != nil { @@ -167,13 +155,8 @@ func (k *Keeper) GetStakerUndelegationRecords(ctx sdk.Context, stakerID, assetID return k.GetUndelegationRecords(ctx, recordKeys) } -func (k *Keeper) SetPendingUndelegationInfo(ctx sdk.Context, height, lzNonce uint64, recordKey string) error { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixPendingUndelegations) - key := types.GetPendingUndelegationRecordKey(height, lzNonce) - store.Set(key, []byte(recordKey)) - return nil -} - +// GetPendingUndelegationRecKeys returns the undelegation record keys scheduled to mature at the +// end of the block with the provided height. func (k *Keeper) GetPendingUndelegationRecKeys(ctx sdk.Context, height uint64) (recordKeyList []string, err error) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixPendingUndelegations) iterator := sdk.KVStorePrefixIterator(store, []byte(hexutil.EncodeUint64(height))) @@ -186,6 +169,8 @@ func (k *Keeper) GetPendingUndelegationRecKeys(ctx sdk.Context, height uint64) ( return ret, nil } +// GetPendingUndelegationRecords returns the undelegation records scheduled to mature at the end +// of the block with the provided height. func (k *Keeper) GetPendingUndelegationRecords(ctx sdk.Context, height uint64) (records []*types.UndelegationRecord, err error) { recordKeys, err := k.GetPendingUndelegationRecKeys(ctx, height) if err != nil { @@ -199,6 +184,7 @@ func (k *Keeper) GetPendingUndelegationRecords(ctx sdk.Context, height uint64) ( return k.GetUndelegationRecords(ctx, recordKeys) } +// IncrementUndelegationHoldCount increments the hold count for the undelegation record key. func (k Keeper) IncrementUndelegationHoldCount(ctx sdk.Context, recordKey []byte) error { prev := k.GetUndelegationHoldCount(ctx, recordKey) if prev == math.MaxUint64 { @@ -210,12 +196,14 @@ func (k Keeper) IncrementUndelegationHoldCount(ctx sdk.Context, recordKey []byte return nil } +// GetUndelegationHoldCount returns the hold count for the undelegation record key. func (k *Keeper) GetUndelegationHoldCount(ctx sdk.Context, recordKey []byte) uint64 { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetUndelegationOnHoldKey(recordKey)) return sdk.BigEndianToUint64(bz) } +// DecrementUndelegationHoldCount decrements the hold count for the undelegation record key. func (k Keeper) DecrementUndelegationHoldCount(ctx sdk.Context, recordKey []byte) error { prev := k.GetUndelegationHoldCount(ctx, recordKey) if prev == 0 { diff --git a/x/delegation/types/msg.go b/x/delegation/types/msg.go index 1a2e22fa2..8ed203b82 100644 --- a/x/delegation/types/msg.go +++ b/x/delegation/types/msg.go @@ -23,13 +23,18 @@ func (m *MsgDelegation) ValidateBasic() error { } // new message to delegate asset to operator -func NewMsgDelegation(assetID, fromAddress string, amountPerOperator []KeyValue) *MsgDelegation { +func NewMsgDelegation( + assetID string, fromAddress string, amountPerOperator []KeyValue, +) *MsgDelegation { baseInfo := &DelegationIncOrDecInfo{ FromAddress: fromAddress, PerOperatorAmounts: make([]KeyValue, 0, 1), } for _, kv := range amountPerOperator { - baseInfo.PerOperatorAmounts = append(baseInfo.PerOperatorAmounts, KeyValue{Key: kv.Key, Value: kv.Value}) + baseInfo.PerOperatorAmounts = append( + baseInfo.PerOperatorAmounts, + KeyValue{Key: kv.Key, Value: kv.Value}, + ) } return &MsgDelegation{ AssetID: assetID, @@ -65,7 +70,10 @@ func NewMsgUndelegation(assetID, fromAddress string, amountPerOperator []KeyValu PerOperatorAmounts: make([]KeyValue, 0, 1), } for _, kv := range amountPerOperator { - baseInfo.PerOperatorAmounts = append(baseInfo.PerOperatorAmounts, KeyValue{Key: kv.Key, Value: kv.Value}) + baseInfo.PerOperatorAmounts = append( + baseInfo.PerOperatorAmounts, + KeyValue{Key: kv.Key, Value: kv.Value}, + ) } return &MsgUndelegation{ AssetID: assetID, @@ -73,18 +81,28 @@ func NewMsgUndelegation(assetID, fromAddress string, amountPerOperator []KeyValu } } -// TODO: delegation and undelegation have the same params, try to use one single message with different flag to indicate action:delegation/undelegation +// validateDelegationInfo validates the delegation or undelegation info. +// (1) the operator amounts are positive, and the operator addresses are valid. +// (2) the assetID is native only, since only native token is supported for this mechanism. +// (3) the from address is valid. +// TODO: delegation and undelegation have the same params, try to use one single message with +// different flag to indicate action:delegation/undelegation func validateDelegationInfo(assetID string, baseInfo *DelegationIncOrDecInfo) error { for _, kv := range baseInfo.PerOperatorAmounts { if _, err := sdk.AccAddressFromBech32(kv.Key); err != nil { return errorsmod.Wrap(err, "invalid operator address delegateTO") } if !kv.Value.Amount.IsPositive() { - return errorsmod.Wrapf(ErrAmountIsNotPositive, "amount should be positive, got%s", kv.Value.Amount.String()) + return ErrAmountIsNotPositive.Wrapf( + "amount should be positive, got %s", kv.Value.Amount.String(), + ) } } if assetID != assetstype.NativeAssetID { - return errorsmod.Wrapf(ErrInvalidAssetID, "only nativeToken is support, expected:%s,got:%s", assetstype.NativeAssetID, assetID) + return ErrInvalidAssetID.Wrapf( + "only nativeToken is support, expected:%s, got:%s", + assetstype.NativeAssetID, assetID, + ) } if _, err := sdk.AccAddressFromBech32(baseInfo.FromAddress); err != nil { return errorsmod.Wrap(err, "invalid from address")