diff --git a/app/upgrades/signetlaunch/btcstaking_params.go b/app/upgrades/signetlaunch/btcstaking_params.go new file mode 100644 index 000000000..2b189ca4c --- /dev/null +++ b/app/upgrades/signetlaunch/btcstaking_params.go @@ -0,0 +1,26 @@ +package signetlaunch + +// TODO Some default parameters. Consider how to switch those depending on network: +// mainnet, testnet, devnet etc. +const BtcStakingParamStr = ` + { + "covenant_pks": [ + "43311589af63c2adda04fcd7792c038a05c12a4fe40351b3eb1612ff6b2e5a0e", + "d415b187c6e7ce9da46ac888d20df20737d6f16a41639e68ea055311e1535dd9", + "d27cd27dbff481bc6fc4aa39dd19405eb6010237784ecba13bab130a4a62df5d", + "a3e107fee8879f5cf901161dbf4ff61c252ba5fec6f6407fe81b9453d244c02c", + "c45753e856ad0abb06f68947604f11476c157d13b7efd54499eaa0f6918cf716" + ], + "covenant_quorum": 3, + "min_staking_value_sat": "1000", + "max_staking_value_sat": "10000000000", + "min_staking_time_blocks": 10, + "max_staking_time_blocks": 65535, + "slashing_pk_script": "dqkUAQEBAQEBAQEBAQEBAQEBAQEBAQGIrA==", + "min_slashing_tx_fee_sat": "1000", + "slashing_rate": "0.100000000000000000", + "min_unbonding_time_blocks": 0, + "unbonding_fee_sat": "1000", + "min_commission_rate": "0.03", + "max_active_finality_providers": 100 +}` diff --git a/app/upgrades/signetlaunch/btcstaking_params_test.go b/app/upgrades/signetlaunch/btcstaking_params_test.go new file mode 100644 index 000000000..a025ad8dd --- /dev/null +++ b/app/upgrades/signetlaunch/btcstaking_params_test.go @@ -0,0 +1,16 @@ +package signetlaunch_test + +import ( + "testing" + + "github.com/babylonlabs-io/babylon/app" + v1 "github.com/babylonlabs-io/babylon/app/upgrades/signetlaunch" + "github.com/stretchr/testify/require" +) + +func TestHardCodedParamsAreValid(t *testing.T) { + bbnApp := app.NewTmpBabylonApp() + loadedParamas, err := v1.LoadBtcStakingParamsFromData(bbnApp.AppCodec()) + require.NoError(t, err) + require.NoError(t, loadedParamas.Validate()) +} diff --git a/app/upgrades/signetlaunch/upgrades.go b/app/upgrades/signetlaunch/upgrades.go index db37c5ff7..990f37500 100644 --- a/app/upgrades/signetlaunch/upgrades.go +++ b/app/upgrades/signetlaunch/upgrades.go @@ -53,6 +53,11 @@ func CreateUpgradeHandler( return nil, err } + // Upgrade the staking parameters as first, as other upgrades depend on it. + if err := upgradeBtcStakingParameters(ctx, keepers.EncCfg, &keepers.BTCStakingKeeper); err != nil { + panic(err) + } + if err := propLaunch(ctx, keepers.EncCfg, &keepers.BTCLightClientKeeper, &keepers.BTCStakingKeeper); err != nil { panic(err) } @@ -61,6 +66,37 @@ func CreateUpgradeHandler( } } +func LoadBtcStakingParamsFromData(cdc codec.Codec) (btcstktypes.Params, error) { + buff := bytes.NewBufferString(BtcStakingParamStr) + + var params btcstktypes.Params + err := cdc.UnmarshalJSON(buff.Bytes(), ¶ms) + if err != nil { + return btcstktypes.Params{}, err + } + + return params, nil +} + +func upgradeBtcStakingParameters( + ctx sdk.Context, + e *appparams.EncodingConfig, + k *btcstkkeeper.Keeper, +) error { + + cdc := e.Codec + + params, err := LoadBtcStakingParamsFromData(cdc) + + if err != nil { + return err + } + + // We are overwriting the params at version 0, as the upgrade is happening from + // TGE chain so there should be only one version of the params + return k.OverwriteParamsAtVersion(ctx, 0, params) +} + // propLaunch runs the proposal of launch that is meant to insert new BTC Headers. func propLaunch( ctx sdk.Context, diff --git a/app/upgrades/signetlaunch/upgrades_test.go b/app/upgrades/signetlaunch/upgrades_test.go index a3eba0782..6a5c68a68 100644 --- a/app/upgrades/signetlaunch/upgrades_test.go +++ b/app/upgrades/signetlaunch/upgrades_test.go @@ -73,6 +73,12 @@ func (s *UpgradeTestSuite) TestUpgrade() { resp, err := s.app.BTCStakingKeeper.FinalityProviders(s.ctx, &types.QueryFinalityProvidersRequest{}) s.NoError(err) oldFPsLen = len(resp.FinalityProviders) + + // Before upgrade, the params should be different + paramsFromUpgrade, err := v1.LoadBtcStakingParamsFromData(s.app.AppCodec()) + s.NoError(err) + moduleParams := s.app.BTCStakingKeeper.GetParams(s.ctx) + s.NotEqualValues(moduleParams, paramsFromUpgrade) }, func() { // inject upgrade plan @@ -128,6 +134,12 @@ func (s *UpgradeTestSuite) TestUpgrade() { s.EqualValues(fpFromKeeper.Commission.String(), fpInserted.Commission.String()) s.EqualValues(fpFromKeeper.Pop.String(), fpInserted.Pop.String()) } + + // Afer upgrade, the params should be the same + paramsFromUpgrade, err := v1.LoadBtcStakingParamsFromData(s.app.AppCodec()) + s.NoError(err) + moduleParams := s.app.BTCStakingKeeper.GetParams(s.ctx) + s.EqualValues(moduleParams, paramsFromUpgrade) }, }, } diff --git a/btcstaking/staking.go b/btcstaking/staking.go index d82cb0f2a..d872a4580 100644 --- a/btcstaking/staking.go +++ b/btcstaking/staking.go @@ -36,7 +36,8 @@ import ( func buildSlashingTxFromOutpoint( stakingOutput wire.OutPoint, stakingAmount, fee int64, - slashingAddress, changeAddress btcutil.Address, + slashingPkScript []byte, + changeAddress btcutil.Address, slashingRate sdkmath.LegacyDec, ) (*wire.MsgTx, error) { // Validate staking amount @@ -49,6 +50,10 @@ func buildSlashingTxFromOutpoint( return nil, ErrInvalidSlashingRate } + if len(slashingPkScript) == 0 { + return nil, fmt.Errorf("slashing pk script must not be empty") + } + // Calculate the amount to be slashed slashingRateFloat64, err := slashingRate.Float64() if err != nil { @@ -58,11 +63,6 @@ func buildSlashingTxFromOutpoint( if slashingAmount <= 0 { return nil, ErrInsufficientSlashingAmount } - // Generate script for slashing address - slashingAddrScript, err := txscript.PayToAddrScript(slashingAddress) - if err != nil { - return nil, err - } // Calculate the change amount changeAmount := btcutil.Amount(stakingAmount) - slashingAmount - btcutil.Amount(fee) @@ -81,7 +81,7 @@ func buildSlashingTxFromOutpoint( // means this tx is not replacable. input := wire.NewTxIn(&stakingOutput, nil, nil) tx.AddTxIn(input) - tx.AddTxOut(wire.NewTxOut(int64(slashingAmount), slashingAddrScript)) + tx.AddTxOut(wire.NewTxOut(int64(slashingAmount), slashingPkScript)) tx.AddTxOut(wire.NewTxOut(int64(changeAmount), changeAddrScript)) // Verify that the none of the outputs is a dust output. @@ -140,7 +140,7 @@ func getPossibleStakingOutput( func BuildSlashingTxFromStakingTxStrict( stakingTx *wire.MsgTx, stakingOutputIdx uint32, - slashingAddress btcutil.Address, + slashingPkScript []byte, stakerPk *btcec.PublicKey, slashChangeLockTime uint16, fee int64, @@ -172,7 +172,7 @@ func BuildSlashingTxFromStakingTxStrict( return buildSlashingTxFromOutpoint( *stakingOutpoint, stakingOutput.Value, fee, - slashingAddress, si.TapAddress, + slashingPkScript, si.TapAddress, slashingRate) } @@ -228,7 +228,7 @@ func IsSimpleTransfer(tx *wire.MsgTx) error { // - the min fee for slashing tx is preserved func validateSlashingTx( slashingTx *wire.MsgTx, - slashingAddress btcutil.Address, + slashingPkScript []byte, slashingRate sdkmath.LegacyDec, slashingTxMinFee, stakingOutputValue int64, stakerPk *btcec.PublicKey, @@ -270,11 +270,6 @@ func validateSlashingTx( return fmt.Errorf("slashing transaction must slash at least staking output value * slashing rate") } - // Verify that the first output pays to the provided slashing address. - slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) - if err != nil { - return fmt.Errorf("error creating slashing pk script: %w", err) - } if !bytes.Equal(slashingTx.TxOut[0].PkScript, slashingPkScript) { return fmt.Errorf("slashing transaction must pay to the provided slashing address") } @@ -341,7 +336,7 @@ func CheckTransactions( fundingOutputIdx uint32, slashingTxMinFee int64, slashingRate sdkmath.LegacyDec, - slashingAddress btcutil.Address, + slashingPkScript []byte, stakerPk *btcec.PublicKey, slashingChangeLockTime uint16, net *chaincfg.Params, @@ -376,7 +371,7 @@ func CheckTransactions( // 3. Check if slashing transaction is valid if err := validateSlashingTx( slashingTx, - slashingAddress, + slashingPkScript, slashingRate, slashingTxMinFee, stakingOutput.Value, diff --git a/btcstaking/staking_test.go b/btcstaking/staking_test.go index b54c97c95..f1a180038 100644 --- a/btcstaking/staking_test.go +++ b/btcstaking/staking_test.go @@ -151,11 +151,14 @@ func testSlashingTx( slashingAddress, err := genRandomBTCAddress(r) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + // Construct slashing transaction using the provided parameters slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( stakingTx, uint32(stakingOutputIdx), - slashingAddress, + slashingPkScript, stakerPk, slashingChangeLockTime, fee, @@ -196,7 +199,7 @@ func testSlashingTx( uint32(stakingOutputIdx), fee, slashingRate, - slashingAddress, + slashingPkScript, stakerPk, slashingChangeLockTime, &chaincfg.MainNetParams, @@ -280,11 +283,14 @@ func TestSlashingTxWithOverflowMustNotAccepted(t *testing.T) { slashingAddress, err := genRandomBTCAddress(r) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + // Construct slashing transaction using the provided parameters slashingTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( stakingTx, uint32(0), - slashingAddress, + slashingPkScript, sd.StakerKey, slashingLockTime, int64(minFee), @@ -303,7 +309,7 @@ func TestSlashingTxWithOverflowMustNotAccepted(t *testing.T) { uint32(0), int64(minFee), slashingRate, - slashingAddress, + slashingPkScript, sd.StakerKey, slashingLockTime, &chaincfg.MainNetParams, diff --git a/cmd/babylond/cmd/flags.go b/cmd/babylond/cmd/flags.go index 65713599a..899f1d1b1 100644 --- a/cmd/babylond/cmd/flags.go +++ b/cmd/babylond/cmd/flags.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/hex" "strings" "time" @@ -35,10 +36,14 @@ const ( flagVoteExtensionEnableHeight = "vote-extension-enable-height" flagCovenantPks = "covenant-pks" flagCovenantQuorum = "covenant-quorum" + flagMinStakingAmtSat = "min-staking-amount-sat" + flagMaxStakingAmtSat = "max-staking-amount-sat" + flagMinStakingTimeBlocks = "min-staking-time-blocks" + flagMaxStakingTimeBlocks = "max-staking-time-blocks" flagMaxActiveFinalityProviders = "max-active-finality-providers" flagMinUnbondingTime = "min-unbonding-time" - flagMinUnbondingRate = "min-unbonding-rate" - flagSlashingAddress = "slashing-address" + flagUnbondingFeeSat = "unbonding-fee-sat" + flagSlashingPkScript = "slashing-pk-script" flagMinSlashingFee = "min-slashing-fee-sat" flagSlashingRate = "slashing-rate" flagMinCommissionRate = "min-commission-rate" @@ -64,12 +69,16 @@ type GenesisCLIArgs struct { VoteExtensionEnableHeight int64 CovenantPKs []string CovenantQuorum uint32 - SlashingAddress string + MinStakingAmtSat int64 + MaxStakingAmtSat int64 + MinStakingTimeBlocks uint16 + MaxStakingTimeBlocks uint16 + SlashingPkScript string MinSlashingTransactionFeeSat int64 SlashingRate math.LegacyDec MaxActiveFinalityProviders uint32 MinUnbondingTime uint16 - MinUnbondingRate math.LegacyDec + UnbondingFeeSat int64 MinCommissionRate math.LegacyDec } @@ -91,13 +100,17 @@ func addGenesisFlags(cmd *cobra.Command) { // btcstaking args cmd.Flags().String(flagCovenantPks, strings.Join(btcstypes.DefaultParams().CovenantPksHex(), ","), "Bitcoin staking covenant public keys, comma separated") cmd.Flags().Uint32(flagCovenantQuorum, btcstypes.DefaultParams().CovenantQuorum, "Bitcoin staking covenant quorum") - cmd.Flags().String(flagSlashingAddress, btcstypes.DefaultParams().SlashingAddress, "Bitcoin staking slashing address") + cmd.Flags().Int64(flagMinStakingAmtSat, 500000, "Minimum staking amount in satoshis") + cmd.Flags().Int64(flagMaxStakingAmtSat, 100000000000, "Maximum staking amount in satoshis") + cmd.Flags().Uint16(flagMinStakingTimeBlocks, 100, "Minimum staking time in blocks") + cmd.Flags().Uint16(flagMaxStakingTimeBlocks, 10000, "Maximum staking time in blocks") + cmd.Flags().String(flagSlashingPkScript, hex.EncodeToString(btcstypes.DefaultParams().SlashingPkScript), "Bitcoin staking slashing pk script. Hex encoded.") cmd.Flags().Int64(flagMinSlashingFee, 1000, "Bitcoin staking minimum slashing fee") cmd.Flags().String(flagMinCommissionRate, "0", "Bitcoin staking validator minimum commission rate") cmd.Flags().String(flagSlashingRate, "0.1", "Bitcoin staking slashing rate") cmd.Flags().Uint32(flagMaxActiveFinalityProviders, 100, "Bitcoin staking maximum active finality providers") cmd.Flags().Uint16(flagMinUnbondingTime, 0, "Min timelock on unbonding transaction in btc blocks") - cmd.Flags().String(flagMinUnbondingRate, "0.8", "Min amount of btc required in unbonding output expressed as a fraction of staking output") + cmd.Flags().Int64(flagUnbondingFeeSat, 1000, "Required fee for unbonding transaction in satoshis") // inflation args cmd.Flags().Float64(flagInflationRateChange, 0.13, "Inflation rate change") cmd.Flags().Float64(flagInflationMax, 0.2, "Maximum inflation") @@ -123,13 +136,17 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { reporterAddresses, _ := cmd.Flags().GetString(flagAllowedReporterAddresses) covenantPks, _ := cmd.Flags().GetString(flagCovenantPks) covenantQuorum, _ := cmd.Flags().GetUint32(flagCovenantQuorum) - slashingAddress, _ := cmd.Flags().GetString(flagSlashingAddress) + minStakingAmtSat, _ := cmd.Flags().GetInt64(flagMinStakingAmtSat) + maxStakingAmtSat, _ := cmd.Flags().GetInt64(flagMaxStakingAmtSat) + minStakingTimeBlocks, _ := cmd.Flags().GetUint16(flagMinStakingTimeBlocks) + maxStakingTimeBlocks, _ := cmd.Flags().GetUint16(flagMaxStakingTimeBlocks) + slashingPkScript, _ := cmd.Flags().GetString(flagSlashingPkScript) minSlashingFee, _ := cmd.Flags().GetInt64(flagMinSlashingFee) minCommissionRate, _ := cmd.Flags().GetString(flagMinCommissionRate) slashingRate, _ := cmd.Flags().GetString(flagSlashingRate) maxActiveFinalityProviders, _ := cmd.Flags().GetUint32(flagMaxActiveFinalityProviders) minUnbondingTime, _ := cmd.Flags().GetUint16(flagMinUnbondingTime) - minUnbondingRate, _ := cmd.Flags().GetString(flagMinUnbondingRate) + unbondingFeeSat, _ := cmd.Flags().GetInt64(flagUnbondingFeeSat) genesisTimeUnix, _ := cmd.Flags().GetInt64(flagGenesisTime) inflationRateChange, _ := cmd.Flags().GetFloat64(flagInflationRateChange) inflationMax, _ := cmd.Flags().GetFloat64(flagInflationMax) @@ -162,13 +179,17 @@ func parseGenesisFlags(cmd *cobra.Command) *GenesisCLIArgs { AllowedReporterAddresses: allowedReporterAddresses, CovenantPKs: strings.Split(covenantPks, ","), CovenantQuorum: covenantQuorum, - SlashingAddress: slashingAddress, + MinStakingAmtSat: minStakingAmtSat, + MaxStakingAmtSat: maxStakingAmtSat, + MinStakingTimeBlocks: minStakingTimeBlocks, + MaxStakingTimeBlocks: maxStakingTimeBlocks, + SlashingPkScript: slashingPkScript, MinSlashingTransactionFeeSat: minSlashingFee, MinCommissionRate: math.LegacyMustNewDecFromStr(minCommissionRate), SlashingRate: math.LegacyMustNewDecFromStr(slashingRate), MaxActiveFinalityProviders: maxActiveFinalityProviders, MinUnbondingTime: minUnbondingTime, - MinUnbondingRate: math.LegacyMustNewDecFromStr(minUnbondingRate), + UnbondingFeeSat: unbondingFeeSat, GenesisTime: genesisTime, InflationRateChange: inflationRateChange, InflationMax: inflationMax, diff --git a/cmd/babylond/cmd/genesis.go b/cmd/babylond/cmd/genesis.go index 23a6d7dad..1a5acab27 100644 --- a/cmd/babylond/cmd/genesis.go +++ b/cmd/babylond/cmd/genesis.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/hex" "encoding/json" "fmt" "time" @@ -68,16 +69,37 @@ Example: var genesisParams GenesisParams if network == "testnet" { - genesisParams = TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, - genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, - genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, - genesisCliArgs.BaseBtcHeaderHeight, genesisCliArgs.AllowedReporterAddresses, - genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, - genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, - genesisCliArgs.MinCommissionRate, genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, - genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinUnbondingRate, genesisCliArgs.InflationRateChange, - genesisCliArgs.InflationMin, genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, - genesisCliArgs.BlocksPerYear, genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit, genesisCliArgs.VoteExtensionEnableHeight) + genesisParams = TestnetGenesisParams( + genesisCliArgs.MaxActiveValidators, + genesisCliArgs.BtcConfirmationDepth, + genesisCliArgs.BtcFinalizationTimeout, + genesisCliArgs.CheckpointTag, + genesisCliArgs.EpochInterval, + genesisCliArgs.BaseBtcHeaderHex, + genesisCliArgs.BaseBtcHeaderHeight, + genesisCliArgs.AllowedReporterAddresses, + genesisCliArgs.CovenantPKs, + genesisCliArgs.CovenantQuorum, + genesisCliArgs.MinStakingAmtSat, + genesisCliArgs.MaxStakingAmtSat, + genesisCliArgs.MinStakingTimeBlocks, + genesisCliArgs.MaxStakingTimeBlocks, + genesisCliArgs.SlashingPkScript, + genesisCliArgs.MinSlashingTransactionFeeSat, + genesisCliArgs.MinCommissionRate, + genesisCliArgs.SlashingRate, + genesisCliArgs.MaxActiveFinalityProviders, + genesisCliArgs.MinUnbondingTime, + genesisCliArgs.UnbondingFeeSat, + genesisCliArgs.InflationRateChange, + genesisCliArgs.InflationMin, + genesisCliArgs.InflationMax, + genesisCliArgs.GoalBonded, + genesisCliArgs.BlocksPerYear, + genesisCliArgs.GenesisTime, + genesisCliArgs.BlockGasLimit, + genesisCliArgs.VoteExtensionEnableHeight, + ) } else if network == "mainnet" { // TODO: mainnet genesis params panic("Mainnet params not implemented.") @@ -238,12 +260,37 @@ type GenesisParams struct { VoteExtensionsEnableHeight int64 } -func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint64, - btcFinalizationTimeout uint64, checkpointTag string, epochInterval uint64, baseBtcHeaderHex string, - baseBtcHeaderHeight uint64, allowedReporters []string, covenantPKs []string, covenantQuorum uint32, slashingAddress string, minSlashingFee int64, - minCommissionRate sdkmath.LegacyDec, slashingRate sdkmath.LegacyDec, maxActiveFinalityProviders uint32, minUnbondingTime uint16, minUnbondingRate sdkmath.LegacyDec, inflationRateChange float64, - inflationMin float64, inflationMax float64, goalBonded float64, - blocksPerYear uint64, genesisTime time.Time, blockGasLimit int64, voteExtensionEnableHeight int64) GenesisParams { +func TestnetGenesisParams( + maxActiveValidators uint32, + btcConfirmationDepth uint64, + btcFinalizationTimeout uint64, + checkpointTag string, + epochInterval uint64, + baseBtcHeaderHex string, + baseBtcHeaderHeight uint64, + allowedReporters []string, + covenantPKs []string, + covenantQuorum uint32, + minStakingAmtSat int64, + maxStakingAmtSat int64, + minStakingTimeBlocks uint16, + maxStakingTimeBlocks uint16, + slashingPkScriptHex string, + minSlashingFee int64, + minCommissionRate sdkmath.LegacyDec, + slashingRate sdkmath.LegacyDec, + maxActiveFinalityProviders uint32, + minUnbondingTime uint16, + unbondingFeeSat int64, + inflationRateChange float64, + inflationMin float64, + inflationMax float64, + goalBonded float64, + blocksPerYear uint64, + genesisTime time.Time, + blockGasLimit int64, + voteExtensionEnableHeight int64, +) GenesisParams { genParams := GenesisParams{} @@ -336,15 +383,25 @@ func TestnetGenesisParams(maxActiveValidators uint32, btcConfirmationDepth uint6 } covenantPKsBIP340 = append(covenantPKsBIP340, *pk) } + + slashingPkScript, err := hex.DecodeString(slashingPkScriptHex) + if err != nil { + panic(err) + } + genParams.BtcstakingParams.CovenantPks = covenantPKsBIP340 genParams.BtcstakingParams.CovenantQuorum = covenantQuorum - genParams.BtcstakingParams.SlashingAddress = slashingAddress + genParams.BtcstakingParams.MinStakingValueSat = minStakingAmtSat + genParams.BtcstakingParams.MaxStakingValueSat = maxStakingAmtSat + genParams.BtcstakingParams.MinStakingTimeBlocks = uint32(minStakingTimeBlocks) + genParams.BtcstakingParams.MaxStakingTimeBlocks = uint32(maxStakingTimeBlocks) + genParams.BtcstakingParams.SlashingPkScript = slashingPkScript genParams.BtcstakingParams.MinSlashingTxFeeSat = minSlashingFee genParams.BtcstakingParams.MinCommissionRate = minCommissionRate genParams.BtcstakingParams.SlashingRate = slashingRate genParams.BtcstakingParams.MaxActiveFinalityProviders = maxActiveFinalityProviders - genParams.BtcstakingParams.MinUnbondingTime = uint32(minUnbondingTime) - genParams.BtcstakingParams.MinUnbondingRate = minUnbondingRate + genParams.BtcstakingParams.MinUnbondingTimeBlocks = uint32(minUnbondingTime) + genParams.BtcstakingParams.UnbondingFeeSat = unbondingFeeSat if err := genParams.BtcstakingParams.Validate(); err != nil { panic(err) } diff --git a/cmd/babylond/cmd/genhelpers/set_btc_delegations_test.go b/cmd/babylond/cmd/genhelpers/set_btc_delegations_test.go index ccaa5a4bd..dde1d54a7 100644 --- a/cmd/babylond/cmd/genhelpers/set_btc_delegations_test.go +++ b/cmd/babylond/cmd/genhelpers/set_btc_delegations_test.go @@ -16,6 +16,7 @@ import ( btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" btcstktypes "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/cosmos/cosmos-sdk/client" genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" @@ -63,6 +64,8 @@ func FuzzCmdSetBtcDels(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.RegressionNetParams) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) startHeight := datagen.RandomInt(r, 100) + 1 endHeight := datagen.RandomInt(r, 1000) + startHeight + btcctypes.DefaultParams().CheckpointFinalizationTimeout + 1 @@ -82,7 +85,7 @@ func FuzzCmdSetBtcDels(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, startHeight, endHeight, 10000, slashingRate, slashingChangeLockTime, @@ -151,7 +154,7 @@ func FuzzCmdSetBtcDels(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, startHeight, endHeight, 10000, slashingRate, slashingChangeLockTime, diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index aff015cb5..e0c0d918d 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -92,14 +92,37 @@ Example: return errors.New("base Bitcoin header height should be a uint64") } - genesisParams := TestnetGenesisParams(genesisCliArgs.MaxActiveValidators, - genesisCliArgs.BtcConfirmationDepth, genesisCliArgs.BtcFinalizationTimeout, genesisCliArgs.CheckpointTag, - genesisCliArgs.EpochInterval, genesisCliArgs.BaseBtcHeaderHex, genesisCliArgs.BaseBtcHeaderHeight, - genesisCliArgs.AllowedReporterAddresses, genesisCliArgs.CovenantPKs, genesisCliArgs.CovenantQuorum, - genesisCliArgs.SlashingAddress, genesisCliArgs.MinSlashingTransactionFeeSat, genesisCliArgs.MinCommissionRate, - genesisCliArgs.SlashingRate, genesisCliArgs.MaxActiveFinalityProviders, genesisCliArgs.MinUnbondingTime, genesisCliArgs.MinUnbondingRate, genesisCliArgs.InflationRateChange, genesisCliArgs.InflationMin, - genesisCliArgs.InflationMax, genesisCliArgs.GoalBonded, genesisCliArgs.BlocksPerYear, - genesisCliArgs.GenesisTime, genesisCliArgs.BlockGasLimit, genesisCliArgs.VoteExtensionEnableHeight) + genesisParams := TestnetGenesisParams( + genesisCliArgs.MaxActiveValidators, + genesisCliArgs.BtcConfirmationDepth, + genesisCliArgs.BtcFinalizationTimeout, + genesisCliArgs.CheckpointTag, + genesisCliArgs.EpochInterval, + genesisCliArgs.BaseBtcHeaderHex, + genesisCliArgs.BaseBtcHeaderHeight, + genesisCliArgs.AllowedReporterAddresses, + genesisCliArgs.CovenantPKs, + genesisCliArgs.CovenantQuorum, + genesisCliArgs.MinStakingAmtSat, + genesisCliArgs.MaxStakingAmtSat, + genesisCliArgs.MinStakingTimeBlocks, + genesisCliArgs.MaxStakingTimeBlocks, + genesisCliArgs.SlashingPkScript, + genesisCliArgs.MinSlashingTransactionFeeSat, + genesisCliArgs.MinCommissionRate, + genesisCliArgs.SlashingRate, + genesisCliArgs.MaxActiveFinalityProviders, + genesisCliArgs.MinUnbondingTime, + genesisCliArgs.UnbondingFeeSat, + genesisCliArgs.InflationRateChange, + genesisCliArgs.InflationMin, + genesisCliArgs.InflationMax, + genesisCliArgs.GoalBonded, + genesisCliArgs.BlocksPerYear, + genesisCliArgs.GenesisTime, + genesisCliArgs.BlockGasLimit, + genesisCliArgs.VoteExtensionEnableHeight, + ) return InitTestnet( clientCtx, cmd, config, mbm, genBalIterator, outputDir, genesisCliArgs.ChainID, minGasPrices, diff --git a/proto/babylon/btcstaking/v1/params.proto b/proto/babylon/btcstaking/v1/params.proto index 5746ffb25..c857f3cc3 100644 --- a/proto/babylon/btcstaking/v1/params.proto +++ b/proto/babylon/btcstaking/v1/params.proto @@ -9,47 +9,52 @@ option go_package = "github.com/babylonlabs-io/babylon/x/btcstaking/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; - + // PARAMETERS COVERING STAKING // covenant_pks is the list of public keys held by the covenant committee // each PK follows encoding in BIP-340 spec on Bitcoin repeated bytes covenant_pks = 1 [ (gogoproto.customtype) = "github.com/babylonlabs-io/babylon/types.BIP340PubKey" ]; // covenant_quorum is the minimum number of signatures needed for the covenant // multisignature uint32 covenant_quorum = 2; - // slashing address is the address that the slashed BTC goes to - // the address is in string on Bitcoin - string slashing_address = 3; + // min_staking_value_sat is the minimum of satoshis locked in staking output + int64 min_staking_value_sat = 3; + // max_staking_value_sat is the maxiumum of satoshis locked in staking output + int64 max_staking_value_sat = 4; + // min_staking_time is the minimum lock time specified in staking output script + uint32 min_staking_time_blocks = 5; + // max_staking_time_blocks is the maximum lock time time specified in staking output script + uint32 max_staking_time_blocks = 6; + // PARAMETERS COVERING SLASHING + // slashing_pk_script is the pk_script expected in slashing output ie. the first + // output of slashing transaction + bytes slashing_pk_script = 7; // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified - // in Satoshi) needed for the pre-signed slashing tx - // TODO: change to satoshi per byte? - int64 min_slashing_tx_fee_sat = 4; - // min_commission_rate is the chain-wide minimum commission rate that a finality provider can charge their delegators - string min_commission_rate = 5 [ - (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", - (gogoproto.nullable) = false - ]; + // in Satoshi) needed for the pre-signed slashing tx. It covers both: + // staking slashing transaction and unbonding slashing transaction + int64 min_slashing_tx_fee_sat = 8; // slashing_rate determines the portion of the staked amount to be slashed, - // expressed as a decimal (e.g., 0.5 for 50%). - string slashing_rate = 6 [ + // expressed as a decimal (e.g., 0.5 for 50%). Maximal precion is 2 decimal + // places + string slashing_rate = 9 [ (cosmos_proto.scalar) = "cosmos.Dec", (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; - // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol - uint32 max_active_finality_providers = 7; + // PARAMETERS COVERING UNBONDING // min_unbonding_time is the minimum time for unbonding transaction timelock in BTC blocks - uint32 min_unbonding_time = 8; - - // min_unbonding_rate is the minimum amount of BTC that are required in unbonding - // output, expressed as a fraction of staking output - // example: if min_unbonding_rate=0.9, then the unbonding output value - // must be at least 90% of staking output, for staking request to be considered - // valid - string min_unbonding_rate = 9 [ - (cosmos_proto.scalar) = "cosmos.Dec", + uint32 min_unbonding_time_blocks = 10; + // unbonding_fee exact fee required for unbonding transaction + int64 unbonding_fee_sat = 11; + // PARAMETERS COVERING FINALITY PROVIDERS + // min_commission_rate is the chain-wide minimum commission rate that a finality provider + // can charge their delegators expressed as a decimal (e.g., 0.5 for 50%). Maximal precion + // is 2 decimal places + string min_commission_rate = 12 [ (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", (gogoproto.nullable) = false ]; + // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol + uint32 max_active_finality_providers = 13; } // StoredParams attach information about the version of stored parameters diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index f4f67a203..85f3dff10 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -907,7 +907,7 @@ func (s *BTCStakingTestSuite) BTCStakingUnbondSlashInfo( covenantQuorum, stakingTimeBlocks, stakingValue, - params.SlashingAddress, + params.SlashingPkScript, params.SlashingRate, unbondingTime, ) @@ -942,7 +942,7 @@ func (s *BTCStakingTestSuite) BTCStakingUnbondSlashInfo( wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), stakingTimeBlocks, unbondingValue, - params.SlashingAddress, + params.SlashingPkScript, params.SlashingRate, unbondingTime, ) diff --git a/test/e2e/software_upgrade_e2e_signet_launch_test.go b/test/e2e/software_upgrade_e2e_signet_launch_test.go index 1288d7053..c7d06d810 100644 --- a/test/e2e/software_upgrade_e2e_signet_launch_test.go +++ b/test/e2e/software_upgrade_e2e_signet_launch_test.go @@ -100,4 +100,14 @@ func (s *SoftwareUpgradeSignetLaunchTestSuite) TestUpgradeSignetLaunch() { s.EqualValues(fpFromKeeper.Commission.String(), fpInserted.Commission.String()) s.EqualValues(fpFromKeeper.Pop.String(), fpInserted.Pop.String()) } + + // check that staking params correctly deserialize and that they are the same + // as the one from the data + stakingParams := n.QueryBTCStakingParams() + + paramsFromData, err := v1.LoadBtcStakingParamsFromData(bbnApp.AppCodec()) + s.NoError(err) + + s.EqualValues(paramsFromData, *stakingParams) + } diff --git a/testutil/datagen/btcstaking.go b/testutil/datagen/btcstaking.go index ed2ed828a..0b9e0433b 100644 --- a/testutil/datagen/btcstaking.go +++ b/testutil/datagen/btcstaking.go @@ -88,7 +88,7 @@ func GenRandomBTCDelegation( covenantSKs []*btcec.PrivateKey, covenantPks []*btcec.PublicKey, covenantQuorum uint32, - slashingAddress string, + slashingPkScript []byte, startHeight, endHeight, totalSat uint64, slashingRate sdkmath.LegacyDec, slashingChangeLockTime uint16, @@ -114,7 +114,7 @@ func GenRandomBTCDelegation( covenantQuorum, uint16(endHeight-startHeight), int64(totalSat), - slashingAddress, + slashingPkScript, slashingRate, slashingChangeLockTime, ) @@ -183,7 +183,7 @@ func GenRandomBTCDelegation( wire.NewOutPoint(&stkTxHash, StakingOutIdx), w+1, int64(unbondingValue), - slashingAddress, + slashingPkScript, slashingRate, slashingChangeLockTime, ) @@ -242,7 +242,7 @@ func GenBTCStakingSlashingInfoWithOutPoint( covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingPkScript []byte, slashingRate sdkmath.LegacyDec, slashingChangeLockTime uint16, ) *TestStakingSlashingInfo { @@ -271,14 +271,10 @@ func GenBTCStakingSlashingInfoWithOutPoint( tx.AddTxOut(wire.NewTxOut(10000, changeAddrScript)) // output for change - // construct slashing tx - slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) - require.NoError(t, err) - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, StakingOutIdx, - slashingAddrBtc, + slashingPkScript, stakerSK.PubKey(), slashingChangeLockTime, 2000, @@ -305,7 +301,7 @@ func GenBTCStakingSlashingInfo( covenantQuorum uint32, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingPkScript []byte, slashingRate sdkmath.LegacyDec, slashingChangeLockTime uint16, ) *TestStakingSlashingInfo { @@ -323,7 +319,7 @@ func GenBTCStakingSlashingInfo( covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress, + slashingPkScript, slashingRate, slashingChangeLockTime, ) @@ -340,7 +336,7 @@ func GenBTCUnbondingSlashingInfo( stakingTransactionOutpoint *wire.OutPoint, stakingTimeBlocks uint16, stakingValue int64, - slashingAddress string, + slashingPkScript []byte, slashingRate sdkmath.LegacyDec, slashingChangeLockTime uint16, ) *TestUnbondingSlashingInfo { @@ -362,14 +358,10 @@ func GenBTCUnbondingSlashingInfo( tx.AddTxIn(txIn) tx.AddTxOut(unbondingInfo.UnbondingOutput) - // construct slashing tx - slashingAddrBtc, err := btcutil.DecodeAddress(slashingAddress, btcNet) - require.NoError(t, err) - slashingMsgTx, err := btcstaking.BuildSlashingTxFromStakingTxStrict( tx, StakingOutIdx, - slashingAddrBtc, + slashingPkScript, stakerSK.PubKey(), slashingChangeLockTime, 2000, diff --git a/x/btcstaking/keeper/grpc_query_test.go b/x/btcstaking/keeper/grpc_query_test.go index b131ac81b..c55b9e9f6 100644 --- a/x/btcstaking/keeper/grpc_query_test.go +++ b/x/btcstaking/keeper/grpc_query_test.go @@ -8,6 +8,7 @@ import ( sdkmath "cosmossdk.io/math" + "github.com/btcsuite/btcd/txscript" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/golang/mock/gomock" @@ -181,6 +182,8 @@ func FuzzPendingBTCDelegations(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) slashingChangeLockTime := uint16(101) // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. @@ -220,7 +223,7 @@ func FuzzPendingBTCDelegations(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, startHeight, endHeight, 10000, slashingRate, slashingChangeLockTime, @@ -385,7 +388,8 @@ func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) - + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) slashingChangeLockTime := uint16(101) // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. @@ -427,7 +431,7 @@ func FuzzActiveFinalityProvidersAtHeight(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, 1, 1000, 10000, slashingRate, slashingChangeLockTime, @@ -505,6 +509,8 @@ func FuzzFinalityProviderDelegations(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum := datagen.GenCovenantCommittee(r) slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) slashingChangeLockTime := uint16(101) // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. @@ -537,7 +543,7 @@ func FuzzFinalityProviderDelegations(f *testing.F) { covenantSKs, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, startHeight, endHeight, 10000, slashingRate, slashingChangeLockTime, diff --git a/x/btcstaking/keeper/keeper_test.go b/x/btcstaking/keeper/keeper_test.go index 325e8a205..39bb17bc5 100644 --- a/x/btcstaking/keeper/keeper_test.go +++ b/x/btcstaking/keeper/keeper_test.go @@ -8,6 +8,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -96,16 +97,22 @@ func (h *Helper) GenAndApplyCustomParams( h.NoError(err) slashingAddress, err := datagen.GenRandomBTCAddress(r, h.Net) h.NoError(err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + h.NoError(err) err = h.BTCStakingKeeper.SetParams(h.Ctx, types.Params{ CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), CovenantQuorum: 3, - SlashingAddress: slashingAddress.EncodeAddress(), + MinStakingValueSat: 1000, + MaxStakingValueSat: int64(4 * 10e8), + MinStakingTimeBlocks: 10, + MaxStakingTimeBlocks: 10000, + SlashingPkScript: slashingPkScript, MinSlashingTxFeeSat: 10, MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), MaxActiveFinalityProviders: 100, - MinUnbondingTime: minUnbondingTime, - MinUnbondingRate: sdkmath.LegacyMustNewDecFromStr("0.8"), + MinUnbondingTimeBlocks: minUnbondingTime, + UnbondingFeeSat: 1000, }) h.NoError(err) return covenantSKs, covenantPKs @@ -170,7 +177,7 @@ func (h *Helper) CreateDelegationCustom( bsParams.CovenantQuorum, stakingTimeBlocks, stakingValue, - bsParams.SlashingAddress, + bsParams.SlashingPkScript, bsParams.SlashingRate, unbondingTime, ) @@ -228,7 +235,7 @@ func (h *Helper) CreateDelegationCustom( wire.NewOutPoint(&stkTxHash, stkOutputIdx), unbondingTime, unbondingValue, - bsParams.SlashingAddress, + bsParams.SlashingPkScript, bsParams.SlashingRate, unbondingTime, ) diff --git a/x/btcstaking/keeper/msg_server.go b/x/btcstaking/keeper/msg_server.go index f797f922f..fb4a43a8b 100644 --- a/x/btcstaking/keeper/msg_server.go +++ b/x/btcstaking/keeper/msg_server.go @@ -166,7 +166,7 @@ func (ms msgServer) CreateBTCDelegation(goCtx context.Context, req *types.MsgCre btccParams := ms.btccKeeper.GetParams(ctx) - paramsValidationResult, err := types.ValidateParams(parsedMsg, &vp.Params, &btccParams, ms.btcNet) + paramsValidationResult, err := types.ValidateParsedMessageAgainstTheParams(parsedMsg, &vp.Params, &btccParams, ms.btcNet) if err != nil { return nil, err diff --git a/x/btcstaking/keeper/msg_server_test.go b/x/btcstaking/keeper/msg_server_test.go index d5a9b4efb..431dfdea0 100644 --- a/x/btcstaking/keeper/msg_server_test.go +++ b/x/btcstaking/keeper/msg_server_test.go @@ -10,6 +10,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" @@ -223,7 +224,7 @@ func TestProperVersionInDelegation(t *testing.T) { customMinUnbondingTime := uint32(2000) currentParams := h.BTCStakingKeeper.GetParams(h.Ctx) - currentParams.MinUnbondingTime = 2000 + currentParams.MinUnbondingTimeBlocks = 2000 // Update new params err = h.BTCStakingKeeper.SetParams(h.Ctx, currentParams) require.NoError(t, err) @@ -576,7 +577,7 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { bsParams.CovenantQuorum, stakingTimeBlocks, stakingValue, - bsParams.SlashingAddress, + bsParams.SlashingPkScript, bsParams.SlashingRate, slashingChangeLockTime, ) @@ -627,7 +628,7 @@ func TestDoNotAllowDelegationWithoutFinalityProvider(t *testing.T) { wire.NewOutPoint(&stkTxHash, datagen.StakingOutIdx), uint16(unbondingTime), unbondingValue, - bsParams.SlashingAddress, + bsParams.SlashingPkScript, bsParams.SlashingRate, slashingChangeLockTime, ) @@ -744,78 +745,6 @@ func TestCorrectUnbondingTimeInDelegation(t *testing.T) { } } -func TestMinimalUnbondingRate(t *testing.T) { - tests := []struct { - name string - stakingValue int64 - unbondingValueInDelegation int64 - err error - }{ - { - name: "successful delegation when unbonding value is >=80% of staking value", - stakingValue: 10000, - unbondingValueInDelegation: 8000, - err: nil, - }, - { - name: "failed delegation when unbonding value is <80% of staking value", - stakingValue: 10000, - unbondingValueInDelegation: 7999, - err: types.ErrInvalidUnbondingTx, - }, - { - name: "failed delegation when unbonding value >= stake value", - stakingValue: 10000, - unbondingValueInDelegation: 10000, - err: types.ErrInvalidUnbondingTx, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := rand.New(rand.NewSource(time.Now().Unix())) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - // mock BTC light client and BTC checkpoint modules - btclcKeeper := types.NewMockBTCLightClientKeeper(ctrl) - btccKeeper := types.NewMockBtcCheckpointKeeper(ctrl) - ckptKeeper := types.NewMockCheckpointingKeeper(ctrl) - h := NewHelper(t, btclcKeeper, btccKeeper, ckptKeeper) - - // set all parameters, by default minimal unbonding value is 80% of staking value - _, _ = h.GenAndApplyParams(r) - - changeAddress, err := datagen.GenRandomBTCAddress(r, h.Net) - require.NoError(t, err) - - // generate and insert new finality provider - _, fpPK, _ := h.CreateFinalityProvider(r) - - // generate and insert new BTC delegation - stakingTxHash, _, _, _, err := h.CreateDelegationCustom( - r, - fpPK, - changeAddress.EncodeAddress(), - tt.stakingValue, - 1000, - tt.unbondingValueInDelegation, - 1000, - ) - if tt.err != nil { - require.Error(t, err) - require.True(t, errors.Is(err, tt.err)) - } else { - require.NoError(t, err) - // Retrieve delegation from keeper - delegation, err := h.BTCStakingKeeper.GetBTCDelegation(h.Ctx, stakingTxHash) - require.NoError(t, err) - require.NotNil(t, delegation) - } - }) - } -} - func createNDelegationsForFinalityProvider( r *rand.Rand, t *testing.T, @@ -834,6 +763,8 @@ func createNDelegationsForFinalityProvider( slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) @@ -846,7 +777,7 @@ func createNDelegationsForFinalityProvider( covenatnSks, covenantPks, quorum, - slashingAddress.EncodeAddress(), + slashingPkScript, 0, 0+math.MaxUint16, uint64(stakingValue), diff --git a/x/btcstaking/keeper/params.go b/x/btcstaking/keeper/params.go index 0f47ba169..be11a06e1 100644 --- a/x/btcstaking/keeper/params.go +++ b/x/btcstaking/keeper/params.go @@ -82,6 +82,29 @@ func (k Keeper) SetParams(ctx context.Context, p types.Params) error { return nil } +func (k Keeper) OverwriteParamsAtVersion(ctx context.Context, v uint32, p types.Params) error { + if err := p.Validate(); err != nil { + return fmt.Errorf("cannot overwrite params at version %d: %w", v, err) + } + + paramsStore := k.paramsStore(ctx) + + // check if the params at version v exists + spBytes := paramsStore.Get(uint32ToBytes(v)) + + if len(spBytes) == 0 { + return fmt.Errorf("params at version %d not found", v) + } + + sp := types.StoredParams{ + Params: p, + Version: v, + } + + paramsStore.Set(uint32ToBytes(v), k.cdc.MustMarshal(&sp)) + return nil +} + func (k Keeper) GetAllParams(ctx context.Context) []*types.Params { paramsStore := k.paramsStore(ctx) it := paramsStore.Iterator(nil, nil) diff --git a/x/btcstaking/keeper/params_test.go b/x/btcstaking/keeper/params_test.go index 58f8500f4..3ba6246a1 100644 --- a/x/btcstaking/keeper/params_test.go +++ b/x/btcstaking/keeper/params_test.go @@ -65,7 +65,7 @@ func FuzzParamsVersioning(f *testing.F) { params := types.DefaultParams() // randomize two parameters so each params are slightly different params.MinSlashingTxFeeSat = r.Int63() - params.MinUnbondingTime = uint32(r.Intn(math.MaxUint16)) + params.MinUnbondingTimeBlocks = uint32(r.Intn(math.MaxUint16)) err := k.SetParams(ctx, params) require.NoError(t, err) generatedParams = append(generatedParams, ¶ms) diff --git a/x/btcstaking/keeper/query_params_test.go b/x/btcstaking/keeper/query_params_test.go index 6d0239d0b..e38176972 100644 --- a/x/btcstaking/keeper/query_params_test.go +++ b/x/btcstaking/keeper/query_params_test.go @@ -25,11 +25,11 @@ func TestParamsByVersionQuery(t *testing.T) { // starting with `1` as BTCStakingKeeper creates params with version 0 params1 := types.DefaultParams() - params1.MinUnbondingTime = 10000 + params1.MinUnbondingTimeBlocks = 10000 params2 := types.DefaultParams() - params2.MinUnbondingTime = 20000 + params2.MinUnbondingTimeBlocks = 20000 params3 := types.DefaultParams() - params3.MinUnbondingTime = 30000 + params3.MinUnbondingTimeBlocks = 30000 // Check that after update we always return the latest version of params throuh Params query err := keeper.SetParams(ctx, params1) diff --git a/x/btcstaking/types/btc_delegation_test.go b/x/btcstaking/types/btc_delegation_test.go index a97535c43..75785529f 100644 --- a/x/btcstaking/types/btc_delegation_test.go +++ b/x/btcstaking/types/btc_delegation_test.go @@ -8,6 +8,7 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/require" asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" @@ -100,6 +101,8 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { stakingValue := int64(2 * 10e8) slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) unbondingTime := uint16(100) + 1 @@ -118,7 +121,7 @@ func FuzzBTCDelegation_SlashingTx(f *testing.F) { covenantSigners, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, 1000, uint64(1000+stakingTimeBlocks), uint64(stakingValue), diff --git a/x/btcstaking/types/btc_slashing_tx_test.go b/x/btcstaking/types/btc_slashing_tx_test.go index aa90099ad..10a77da0e 100644 --- a/x/btcstaking/types/btc_slashing_tx_test.go +++ b/x/btcstaking/types/btc_slashing_tx_test.go @@ -11,6 +11,7 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/require" ) @@ -31,6 +32,9 @@ func FuzzSlashingTx_VerifySigAndASig(f *testing.F) { // slashing address and key pairs slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -75,7 +79,7 @@ func FuzzSlashingTx_VerifySigAndASig(f *testing.F) { covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress.EncodeAddress(), + slashingPkScript, slashingRate, slashingChangeLockTime, ) @@ -126,6 +130,9 @@ func FuzzSlashingTxWithWitness(f *testing.F) { // slashing address and key pairs slashingAddress, err := datagen.GenRandomBTCAddress(r, net) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + // Generate a slashing rate in the range [0.1, 0.50] i.e., 10-50%. // NOTE - if the rate is higher or lower, it may produce slashing or change outputs // with value below the dust threshold, causing test failure. @@ -171,7 +178,7 @@ func FuzzSlashingTxWithWitness(f *testing.F) { covenantQuorum, stakingTimeBlocks, stakingValue, - slashingAddress.EncodeAddress(), + slashingPkScript, slashingRate, slashingChangeLockTime, ) diff --git a/x/btcstaking/types/btc_undelegation_test.go b/x/btcstaking/types/btc_undelegation_test.go index 75b72c7bf..59694c426 100644 --- a/x/btcstaking/types/btc_undelegation_test.go +++ b/x/btcstaking/types/btc_undelegation_test.go @@ -11,6 +11,7 @@ import ( bbn "github.com/babylonlabs-io/babylon/types" "github.com/babylonlabs-io/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/stretchr/testify/require" ) @@ -53,6 +54,9 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.SimNetParams) require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + slashingRate := sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2) unbondingTime := uint16(100) + 1 slashingChangeLockTime := unbondingTime @@ -67,7 +71,7 @@ func FuzzBTCUndelegation_SlashingTx(f *testing.F) { covenantSigners, covenantPKs, covenantQuorum, - slashingAddress.EncodeAddress(), + slashingPkScript, 1000, uint64(1000+stakingTimeBlocks), uint64(stakingValue), diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 456950a45..7c4b6abff 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -122,7 +122,7 @@ func MinimumUnbondingTime( stakingParams *Params, checkpointingParams *btcctypes.Params) uint64 { return math.Max[uint64]( - uint64(stakingParams.MinUnbondingTime), + uint64(stakingParams.MinUnbondingTimeBlocks), checkpointingParams.CheckpointFinalizationTimeout, ) } diff --git a/x/btcstaking/types/genesis_test.go b/x/btcstaking/types/genesis_test.go index 8e1720227..2d1f4759a 100644 --- a/x/btcstaking/types/genesis_test.go +++ b/x/btcstaking/types/genesis_test.go @@ -12,66 +12,85 @@ import ( func TestGenesisState_Validate(t *testing.T) { tests := []struct { desc string - genState *types.GenesisState + genState func() *types.GenesisState valid bool }{ { - desc: "default is valid", - genState: types.DefaultGenesis(), - valid: true, + desc: "default is valid", + genState: func() *types.GenesisState { + return types.DefaultGenesis() + }, + valid: true, }, { desc: "valid genesis state", - genState: &types.GenesisState{ - Params: []*types.Params{&types.Params{ - CovenantPks: types.DefaultParams().CovenantPks, - CovenantQuorum: types.DefaultParams().CovenantQuorum, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), - MaxActiveFinalityProviders: 100, - MinUnbondingRate: sdkmath.LegacyMustNewDecFromStr("0.8"), - }}, + genState: func() *types.GenesisState { + return &types.GenesisState{ + Params: []*types.Params{ + &types.Params{ + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, + MinStakingValueSat: 1000, + MaxStakingValueSat: 100000000, + MinStakingTimeBlocks: 100, + MaxStakingTimeBlocks: 1000, + SlashingPkScript: types.DefaultParams().SlashingPkScript, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), + MaxActiveFinalityProviders: 100, + UnbondingFeeSat: types.DefaultParams().UnbondingFeeSat, + }, + }, + } }, valid: true, }, { desc: "invalid slashing rate in genesis", - genState: &types.GenesisState{ - Params: []*types.Params{&types.Params{ - CovenantPks: types.DefaultParams().CovenantPks, - CovenantQuorum: types.DefaultParams().CovenantQuorum, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate - MaxActiveFinalityProviders: 100, - MinUnbondingRate: sdkmath.LegacyMustNewDecFromStr("0.8"), - }, - }}, + genState: func() *types.GenesisState { + return &types.GenesisState{ + Params: []*types.Params{ + &types.Params{ + CovenantPks: types.DefaultParams().CovenantPks, + CovenantQuorum: types.DefaultParams().CovenantQuorum, + SlashingPkScript: types.DefaultParams().SlashingPkScript, + MinSlashingTxFeeSat: 500, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), + SlashingRate: sdkmath.LegacyZeroDec(), // invalid slashing rate + MaxActiveFinalityProviders: 100, + UnbondingFeeSat: types.DefaultParams().UnbondingFeeSat, + }, + }, + } + }, + valid: false, + }, + { + desc: "min staking time larger than max staking time", + genState: func() *types.GenesisState { + d := types.DefaultGenesis() + d.Params[0].MinStakingTimeBlocks = 1000 + d.Params[0].MaxStakingTimeBlocks = 100 + return d + }, valid: false, }, { - desc: "invalid unbonding value in genesis", - genState: &types.GenesisState{ - Params: []*types.Params{&types.Params{ - CovenantPks: types.DefaultParams().CovenantPks, - CovenantQuorum: types.DefaultParams().CovenantQuorum, - SlashingAddress: types.DefaultParams().SlashingAddress, - MinSlashingTxFeeSat: 500, - MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.5"), - SlashingRate: sdkmath.LegacyMustNewDecFromStr("0.1"), - MaxActiveFinalityProviders: 100, - MinUnbondingRate: sdkmath.LegacyZeroDec(), - }, - }}, + desc: "min staking value larger than max staking value", + genState: func() *types.GenesisState { + d := types.DefaultGenesis() + d.Params[0].MinStakingValueSat = 1000 + d.Params[0].MaxStakingValueSat = 100 + return d + }, valid: false, }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - err := tc.genState.Validate() + state := tc.genState() + err := state.Validate() if tc.valid { require.NoError(t, err) } else { diff --git a/x/btcstaking/types/params.go b/x/btcstaking/types/params.go index d599cdd2c..0370ac128 100644 --- a/x/btcstaking/types/params.go +++ b/x/btcstaking/types/params.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" "github.com/cometbft/cometbft/crypto/tmhash" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" @@ -34,14 +35,19 @@ func DefaultCovenantCommittee() ([]*btcec.PrivateKey, []*btcec.PublicKey, uint32 return sks, pks, 3 } -func defaultSlashingAddress() string { +func defaultSlashingPkScript() []byte { // 20 bytes pkHash := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} addr, err := btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.SimNetParams) if err != nil { panic(err) } - return addr.EncodeAddress() + + pkScript, err := txscript.PayToAddrScript(addr) + if err != nil { + panic(err) + } + return pkScript } // ParamKeyTable the param key table for launch module @@ -53,19 +59,22 @@ func ParamKeyTable() paramtypes.KeyTable { func DefaultParams() Params { _, pks, quorum := DefaultCovenantCommittee() return Params{ - CovenantPks: bbn.NewBIP340PKsFromBTCPKs(pks), - CovenantQuorum: quorum, - SlashingAddress: defaultSlashingAddress(), - MinSlashingTxFeeSat: 1000, - MinCommissionRate: sdkmath.LegacyZeroDec(), + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(pks), + CovenantQuorum: quorum, + MinStakingValueSat: 1000, + MaxStakingValueSat: 10 * 10e8, + MinStakingTimeBlocks: 10, + MaxStakingTimeBlocks: math.MaxUint16, + SlashingPkScript: defaultSlashingPkScript(), + MinSlashingTxFeeSat: 1000, + MinCommissionRate: sdkmath.LegacyZeroDec(), // The Default slashing rate is 0.1 i.e., 10% of the total staked BTC will be burned. SlashingRate: sdkmath.LegacyNewDecWithPrec(1, 1), // 1 * 10^{-1} = 0.1 MaxActiveFinalityProviders: defaultMaxActiveFinalityProviders, // The default minimum unbonding time is 0, which effectively defaults to checkpoint // finalization timeout. - MinUnbondingTime: 0, - // By default unbonding value is 0.8 - MinUnbondingRate: sdkmath.LegacyNewDecWithPrec(8, 1), // 8 * 10^{-1} = 0.8 + MinUnbondingTimeBlocks: 0, + UnbondingFeeSat: 1000, } } @@ -121,6 +130,46 @@ func validateMinUnbondingTime(minUnbondingTimeBlocks uint32) error { return nil } +func validateStakingAmout(minStakingAmt, maxStakingAmt int64) error { + if minStakingAmt <= 0 { + return fmt.Errorf("minimum staking amount has to be positive") + } + + if maxStakingAmt <= 0 { + return fmt.Errorf("maximum staking amount has to be positive") + } + + if minStakingAmt > maxStakingAmt { + return fmt.Errorf("minimum staking amount cannot be greater than maximum staking amount") + } + + return nil +} + +func validateStakingTime(minStakingTime, maxStakingTime uint32) error { + if minStakingTime == 0 { + return fmt.Errorf("minimum staking time has to be positive") + } + + if minStakingTime > math.MaxUint16 { + return fmt.Errorf("minimum staking time cannot be greater than %d", math.MaxUint16) + } + + if maxStakingTime == 0 { + return fmt.Errorf("maximum staking time has to be positive") + } + + if maxStakingTime > math.MaxUint16 { + return fmt.Errorf("maximum staking time cannot be greater than %d", math.MaxUint16) + } + + if minStakingTime > maxStakingTime { + return fmt.Errorf("minimum staking time cannot be greater than maximum staking time") + } + + return nil +} + // Validate validates the set of params func (p Params) Validate() error { if p.CovenantQuorum == 0 { @@ -129,6 +178,15 @@ func (p Params) Validate() error { if p.CovenantQuorum*2 <= uint32(len(p.CovenantPks)) { return fmt.Errorf("covenant quorum size has to be more than 1/2 of the covenant committee size") } + + if err := validateStakingAmout(p.MinStakingValueSat, p.MaxStakingValueSat); err != nil { + return err + } + + if err := validateStakingTime(p.MinStakingTimeBlocks, p.MaxStakingTimeBlocks); err != nil { + return err + } + if err := validateCovenantPks(p.CovenantPks); err != nil { return err } @@ -144,15 +202,11 @@ func (p Params) Validate() error { return btcstaking.ErrInvalidSlashingRate } - if !btcstaking.IsRateValid(p.MinUnbondingRate) { - return fmt.Errorf("minimum unbonding value is invalid. it should be fraction in range (0, 1) with at 2 decimal places precision") - } - if err := validateMaxActiveFinalityProviders(p.MaxActiveFinalityProviders); err != nil { return err } - if err := validateMinUnbondingTime(p.MinUnbondingTime); err != nil { + if err := validateMinUnbondingTime(p.MinUnbondingTimeBlocks); err != nil { return err } @@ -174,14 +228,6 @@ func (p Params) HasCovenantPK(pk *bbn.BIP340PubKey) bool { return false } -func (p Params) MustGetSlashingAddress(btcParams *chaincfg.Params) btcutil.Address { - slashingAddr, err := btcutil.DecodeAddress(p.SlashingAddress, btcParams) - if err != nil { - panic(fmt.Errorf("failed to decode slashing address in genesis: %w", err)) - } - return slashingAddr -} - func (p Params) CovenantPksHex() []string { covPksHex := make([]string, 0, len(p.CovenantPks)) for _, pk := range p.CovenantPks { diff --git a/x/btcstaking/types/params.pb.go b/x/btcstaking/types/params.pb.go index 097fff47a..773816ad3 100644 --- a/x/btcstaking/types/params.pb.go +++ b/x/btcstaking/types/params.pb.go @@ -28,34 +28,45 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { + // PARAMETERS COVERING STAKING // covenant_pks is the list of public keys held by the covenant committee // each PK follows encoding in BIP-340 spec on Bitcoin CovenantPks []github_com_babylonlabs_io_babylon_types.BIP340PubKey `protobuf:"bytes,1,rep,name=covenant_pks,json=covenantPks,proto3,customtype=github.com/babylonlabs-io/babylon/types.BIP340PubKey" json:"covenant_pks,omitempty"` // covenant_quorum is the minimum number of signatures needed for the covenant // multisignature CovenantQuorum uint32 `protobuf:"varint,2,opt,name=covenant_quorum,json=covenantQuorum,proto3" json:"covenant_quorum,omitempty"` - // slashing address is the address that the slashed BTC goes to - // the address is in string on Bitcoin - SlashingAddress string `protobuf:"bytes,3,opt,name=slashing_address,json=slashingAddress,proto3" json:"slashing_address,omitempty"` + // min_staking_value_sat is the minimum of satoshis locked in staking output + MinStakingValueSat int64 `protobuf:"varint,3,opt,name=min_staking_value_sat,json=minStakingValueSat,proto3" json:"min_staking_value_sat,omitempty"` + // max_staking_value_sat is the maxiumum of satoshis locked in staking output + MaxStakingValueSat int64 `protobuf:"varint,4,opt,name=max_staking_value_sat,json=maxStakingValueSat,proto3" json:"max_staking_value_sat,omitempty"` + // min_staking_time is the minimum lock time specified in staking output script + MinStakingTimeBlocks uint32 `protobuf:"varint,5,opt,name=min_staking_time_blocks,json=minStakingTimeBlocks,proto3" json:"min_staking_time_blocks,omitempty"` + // max_staking_time_blocks is the maximum lock time time specified in staking output script + MaxStakingTimeBlocks uint32 `protobuf:"varint,6,opt,name=max_staking_time_blocks,json=maxStakingTimeBlocks,proto3" json:"max_staking_time_blocks,omitempty"` + // PARAMETERS COVERING SLASHING + // slashing_pk_script is the pk_script expected in slashing output ie. the first + // output of slashing transaction + SlashingPkScript []byte `protobuf:"bytes,7,opt,name=slashing_pk_script,json=slashingPkScript,proto3" json:"slashing_pk_script,omitempty"` // min_slashing_tx_fee_sat is the minimum amount of tx fee (quantified - // in Satoshi) needed for the pre-signed slashing tx - // TODO: change to satoshi per byte? - MinSlashingTxFeeSat int64 `protobuf:"varint,4,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` - // min_commission_rate is the chain-wide minimum commission rate that a finality provider can charge their delegators - MinCommissionRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_commission_rate"` + // in Satoshi) needed for the pre-signed slashing tx. It covers both: + // staking slashing transaction and unbonding slashing transaction + MinSlashingTxFeeSat int64 `protobuf:"varint,8,opt,name=min_slashing_tx_fee_sat,json=minSlashingTxFeeSat,proto3" json:"min_slashing_tx_fee_sat,omitempty"` // slashing_rate determines the portion of the staked amount to be slashed, - // expressed as a decimal (e.g., 0.5 for 50%). - SlashingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=slashing_rate,json=slashingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slashing_rate"` - // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol - MaxActiveFinalityProviders uint32 `protobuf:"varint,7,opt,name=max_active_finality_providers,json=maxActiveFinalityProviders,proto3" json:"max_active_finality_providers,omitempty"` + // expressed as a decimal (e.g., 0.5 for 50%). Maximal precion is 2 decimal + // places + SlashingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,9,opt,name=slashing_rate,json=slashingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"slashing_rate"` + // PARAMETERS COVERING UNBONDING // min_unbonding_time is the minimum time for unbonding transaction timelock in BTC blocks - MinUnbondingTime uint32 `protobuf:"varint,8,opt,name=min_unbonding_time,json=minUnbondingTime,proto3" json:"min_unbonding_time,omitempty"` - // min_unbonding_rate is the minimum amount of BTC that are required in unbonding - // output, expressed as a fraction of staking output - // example: if min_unbonding_rate=0.9, then the unbonding output value - // must be at least 90% of staking output, for staking request to be considered - // valid - MinUnbondingRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,9,opt,name=min_unbonding_rate,json=minUnbondingRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_unbonding_rate"` + MinUnbondingTimeBlocks uint32 `protobuf:"varint,10,opt,name=min_unbonding_time_blocks,json=minUnbondingTimeBlocks,proto3" json:"min_unbonding_time_blocks,omitempty"` + // unbonding_fee exact fee required for unbonding transaction + UnbondingFeeSat int64 `protobuf:"varint,11,opt,name=unbonding_fee_sat,json=unbondingFeeSat,proto3" json:"unbonding_fee_sat,omitempty"` + // PARAMETERS COVERING FINALITY PROVIDERS + // min_commission_rate is the chain-wide minimum commission rate that a finality provider + // can charge their delegators expressed as a decimal (e.g., 0.5 for 50%). Maximal precion + // is 2 decimal places + MinCommissionRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,12,opt,name=min_commission_rate,json=minCommissionRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_commission_rate"` + // max_active_finality_providers is the maximum number of active finality providers in the BTC staking protocol + MaxActiveFinalityProviders uint32 `protobuf:"varint,13,opt,name=max_active_finality_providers,json=maxActiveFinalityProviders,proto3" json:"max_active_finality_providers,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -97,11 +108,39 @@ func (m *Params) GetCovenantQuorum() uint32 { return 0 } -func (m *Params) GetSlashingAddress() string { +func (m *Params) GetMinStakingValueSat() int64 { + if m != nil { + return m.MinStakingValueSat + } + return 0 +} + +func (m *Params) GetMaxStakingValueSat() int64 { + if m != nil { + return m.MaxStakingValueSat + } + return 0 +} + +func (m *Params) GetMinStakingTimeBlocks() uint32 { if m != nil { - return m.SlashingAddress + return m.MinStakingTimeBlocks } - return "" + return 0 +} + +func (m *Params) GetMaxStakingTimeBlocks() uint32 { + if m != nil { + return m.MaxStakingTimeBlocks + } + return 0 +} + +func (m *Params) GetSlashingPkScript() []byte { + if m != nil { + return m.SlashingPkScript + } + return nil } func (m *Params) GetMinSlashingTxFeeSat() int64 { @@ -111,16 +150,23 @@ func (m *Params) GetMinSlashingTxFeeSat() int64 { return 0 } -func (m *Params) GetMaxActiveFinalityProviders() uint32 { +func (m *Params) GetMinUnbondingTimeBlocks() uint32 { if m != nil { - return m.MaxActiveFinalityProviders + return m.MinUnbondingTimeBlocks + } + return 0 +} + +func (m *Params) GetUnbondingFeeSat() int64 { + if m != nil { + return m.UnbondingFeeSat } return 0 } -func (m *Params) GetMinUnbondingTime() uint32 { +func (m *Params) GetMaxActiveFinalityProviders() uint32 { if m != nil { - return m.MinUnbondingTime + return m.MaxActiveFinalityProviders } return 0 } @@ -191,42 +237,46 @@ func init() { } var fileDescriptor_8d1392776a3e15b9 = []byte{ - // 547 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0xcb, 0x6e, 0xd3, 0x40, - 0x14, 0x8d, 0x49, 0x48, 0xe9, 0x34, 0xa5, 0x65, 0x00, 0x61, 0x82, 0xea, 0x44, 0x61, 0x41, 0x90, - 0xa8, 0x4d, 0x68, 0x16, 0x08, 0x56, 0x09, 0xa8, 0x12, 0x02, 0xa1, 0xe0, 0x14, 0x16, 0xb0, 0x18, - 0x8d, 0xed, 0xa9, 0x33, 0x4a, 0x66, 0x26, 0x78, 0x26, 0x56, 0xfc, 0x17, 0x2c, 0x59, 0xf2, 0x11, - 0x7c, 0x44, 0x97, 0x15, 0x2b, 0xd4, 0x45, 0x84, 0x92, 0x5f, 0xe0, 0x03, 0x90, 0xc7, 0x76, 0x78, - 0x88, 0x45, 0xd5, 0x9d, 0xef, 0xb9, 0xe7, 0x9e, 0xfb, 0xf0, 0x19, 0xd0, 0xf2, 0xb0, 0x97, 0x4c, - 0x04, 0x77, 0x3c, 0xe5, 0x4b, 0x85, 0xc7, 0x94, 0x87, 0x4e, 0xdc, 0x71, 0xa6, 0x38, 0xc2, 0x4c, - 0xda, 0xd3, 0x48, 0x28, 0x01, 0x6f, 0xe6, 0x1c, 0xfb, 0x37, 0xc7, 0x8e, 0x3b, 0xf5, 0x1b, 0xa1, - 0x08, 0x85, 0x66, 0x38, 0xe9, 0x57, 0x46, 0xae, 0xdf, 0xf6, 0x85, 0x64, 0x42, 0xa2, 0x2c, 0x91, - 0x05, 0x59, 0xaa, 0xf5, 0xb3, 0x02, 0xaa, 0x03, 0x2d, 0x0c, 0x3f, 0x80, 0x9a, 0x2f, 0x62, 0xc2, - 0x31, 0x57, 0x68, 0x3a, 0x96, 0xa6, 0xd1, 0x2c, 0xb7, 0x6b, 0xfd, 0xc7, 0x67, 0x8b, 0x46, 0x37, - 0xa4, 0x6a, 0x34, 0xf3, 0x6c, 0x5f, 0x30, 0x27, 0xef, 0x3b, 0xc1, 0x9e, 0xdc, 0xa7, 0xa2, 0x08, - 0x1d, 0x95, 0x4c, 0x89, 0xb4, 0xfb, 0x2f, 0x06, 0x07, 0xdd, 0x87, 0x83, 0x99, 0xf7, 0x92, 0x24, - 0xee, 0x56, 0xa1, 0x36, 0x18, 0x4b, 0x78, 0x0f, 0xec, 0xac, 0xc5, 0x3f, 0xce, 0x44, 0x34, 0x63, - 0xe6, 0xa5, 0xa6, 0xd1, 0xde, 0x76, 0xaf, 0x16, 0xf0, 0x1b, 0x8d, 0xc2, 0xfb, 0x60, 0x57, 0x4e, - 0xb0, 0x1c, 0x51, 0x1e, 0x22, 0x1c, 0x04, 0x11, 0x91, 0xd2, 0x2c, 0x37, 0x8d, 0xf6, 0xa6, 0xbb, - 0x53, 0xe0, 0xbd, 0x0c, 0x86, 0x5d, 0x70, 0x8b, 0x51, 0x8e, 0xd6, 0x74, 0x35, 0x47, 0xc7, 0x84, - 0x20, 0x89, 0x95, 0x59, 0x69, 0x1a, 0xed, 0xb2, 0x7b, 0x9d, 0x51, 0x3e, 0xcc, 0xb3, 0x47, 0xf3, - 0x43, 0x42, 0x86, 0x58, 0xc1, 0x21, 0x48, 0x61, 0xe4, 0x0b, 0xc6, 0xa8, 0x94, 0x54, 0x70, 0x14, - 0x61, 0x45, 0xcc, 0xcb, 0x69, 0x8f, 0xfe, 0xdd, 0x93, 0x45, 0xa3, 0x74, 0xb6, 0x68, 0xdc, 0xc9, - 0x8e, 0x24, 0x83, 0xb1, 0x4d, 0x85, 0xc3, 0xb0, 0x1a, 0xd9, 0xaf, 0x48, 0x88, 0xfd, 0xe4, 0x39, - 0xf1, 0xdd, 0x6b, 0x8c, 0xf2, 0x67, 0xeb, 0x72, 0x17, 0x2b, 0x02, 0xdf, 0x81, 0xed, 0xf5, 0x18, - 0x5a, 0xae, 0xaa, 0xe5, 0x3a, 0xe7, 0x90, 0xfb, 0xf6, 0x75, 0x1f, 0xe4, 0xbf, 0x24, 0x15, 0xaf, - 0x15, 0x3a, 0x5a, 0xb7, 0x07, 0xf6, 0x18, 0x9e, 0x23, 0xec, 0x2b, 0x1a, 0x13, 0x74, 0x4c, 0x39, - 0x9e, 0x50, 0x95, 0xa4, 0x3f, 0x32, 0xa6, 0x01, 0x89, 0xa4, 0xb9, 0xa1, 0x8f, 0x58, 0x67, 0x78, - 0xde, 0xd3, 0x9c, 0xc3, 0x9c, 0x32, 0x28, 0x18, 0xf0, 0x01, 0x80, 0xe9, 0xbe, 0x33, 0xee, 0x09, - 0x1e, 0xe8, 0x33, 0x51, 0x46, 0xcc, 0x2b, 0xba, 0x6e, 0x97, 0x51, 0xfe, 0xb6, 0x48, 0x1c, 0x51, - 0x46, 0x20, 0xfa, 0x97, 0xad, 0xb7, 0xd9, 0xbc, 0xe8, 0x36, 0x7f, 0x35, 0x48, 0x37, 0x7a, 0x52, - 0xf9, 0xfc, 0xa5, 0x51, 0x6a, 0x11, 0x50, 0x1b, 0x2a, 0x11, 0x91, 0x20, 0xf7, 0x9e, 0x09, 0x36, - 0x62, 0x12, 0xa5, 0xe7, 0x34, 0x0d, 0x3d, 0x59, 0x11, 0xc2, 0xa7, 0xa0, 0x9a, 0x19, 0x5f, 0xfb, - 0x65, 0xeb, 0xd1, 0x9e, 0xfd, 0x5f, 0xe7, 0xdb, 0x99, 0x50, 0xbf, 0x92, 0xce, 0xe8, 0xe6, 0x25, - 0xfd, 0xd7, 0x27, 0x4b, 0xcb, 0x38, 0x5d, 0x5a, 0xc6, 0x8f, 0xa5, 0x65, 0x7c, 0x5a, 0x59, 0xa5, - 0xd3, 0x95, 0x55, 0xfa, 0xbe, 0xb2, 0x4a, 0xef, 0xcf, 0x61, 0xe9, 0xf9, 0x9f, 0xef, 0x4f, 0xfb, - 0xdb, 0xab, 0xea, 0x47, 0x73, 0xf0, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x60, 0x31, 0x6e, 0xa0, 0xa2, - 0x03, 0x00, 0x00, + // 619 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0x4f, 0x4f, 0xd4, 0x4e, + 0x18, 0xc7, 0xb7, 0x3f, 0xf8, 0x2d, 0x32, 0x2c, 0x22, 0x15, 0xb4, 0x60, 0xd8, 0xdd, 0xe0, 0xc1, + 0x8d, 0x91, 0xd6, 0x15, 0x4c, 0xfc, 0x73, 0xa2, 0x12, 0x12, 0xa3, 0x31, 0xb5, 0x8b, 0x1c, 0xf4, + 0xd0, 0x4c, 0xbb, 0xc3, 0x32, 0x69, 0x67, 0xa6, 0x76, 0xa6, 0x4d, 0xf7, 0x5d, 0x78, 0xf4, 0x68, + 0xe2, 0x5b, 0xf0, 0x45, 0x70, 0x24, 0x9e, 0x0c, 0x07, 0x62, 0xe0, 0x8d, 0x98, 0x4e, 0xa7, 0xbb, + 0x0d, 0xe1, 0xc0, 0x6d, 0x67, 0xbe, 0xcf, 0xf7, 0x79, 0x9e, 0x4f, 0x3a, 0xdf, 0x05, 0x9b, 0x3e, + 0xf4, 0xc7, 0x11, 0xa3, 0x96, 0x2f, 0x02, 0x2e, 0x60, 0x88, 0xe9, 0xc8, 0xca, 0xfa, 0x56, 0x0c, + 0x13, 0x48, 0xb8, 0x19, 0x27, 0x4c, 0x30, 0x7d, 0x55, 0xd5, 0x98, 0xd3, 0x1a, 0x33, 0xeb, 0xaf, + 0xaf, 0x8c, 0xd8, 0x88, 0xc9, 0x0a, 0xab, 0xf8, 0x55, 0x16, 0xaf, 0xaf, 0x05, 0x8c, 0x13, 0xc6, + 0xbd, 0x52, 0x28, 0x0f, 0xa5, 0xb4, 0xf9, 0xb3, 0x09, 0x9a, 0x8e, 0x6c, 0xac, 0x7f, 0x01, 0xad, + 0x80, 0x65, 0x88, 0x42, 0x2a, 0xbc, 0x38, 0xe4, 0x86, 0xd6, 0x9d, 0xe9, 0xb5, 0xec, 0x17, 0x67, + 0xe7, 0x9d, 0x9d, 0x11, 0x16, 0xc7, 0xa9, 0x6f, 0x06, 0x8c, 0x58, 0x6a, 0x6e, 0x04, 0x7d, 0xbe, + 0x85, 0x59, 0x75, 0xb4, 0xc4, 0x38, 0x46, 0xdc, 0xb4, 0xdf, 0x3a, 0xdb, 0x3b, 0x4f, 0x9d, 0xd4, + 0x7f, 0x87, 0xc6, 0xee, 0x42, 0xd5, 0xcd, 0x09, 0xb9, 0xfe, 0x08, 0x2c, 0x4d, 0x9a, 0x7f, 0x4d, + 0x59, 0x92, 0x12, 0xe3, 0xbf, 0xae, 0xd6, 0x5b, 0x74, 0x6f, 0x57, 0xd7, 0x1f, 0xe5, 0xad, 0xde, + 0x07, 0xab, 0x04, 0x53, 0x4f, 0x31, 0x79, 0x19, 0x8c, 0x52, 0xe4, 0x71, 0x28, 0x8c, 0x99, 0xae, + 0xd6, 0x9b, 0x71, 0x75, 0x82, 0xe9, 0xa0, 0xd4, 0x0e, 0x0b, 0x69, 0x00, 0x85, 0xb4, 0xc0, 0xfc, + 0x1a, 0xcb, 0xac, 0xb2, 0xc0, 0xfc, 0xaa, 0xe5, 0x39, 0xb8, 0x5f, 0x9f, 0x22, 0x30, 0x41, 0x9e, + 0x1f, 0xb1, 0x20, 0xe4, 0xc6, 0xff, 0x72, 0xad, 0x95, 0xe9, 0x9c, 0x03, 0x4c, 0x90, 0x2d, 0x35, + 0x69, 0xab, 0x4d, 0xaa, 0xdb, 0x9a, 0xca, 0x36, 0x99, 0x55, 0xb3, 0x3d, 0x01, 0x3a, 0x8f, 0x20, + 0x3f, 0x2e, 0x3c, 0x71, 0xe8, 0xf1, 0x20, 0xc1, 0xb1, 0x30, 0xe6, 0xba, 0x5a, 0xaf, 0xe5, 0xde, + 0xa9, 0x14, 0x27, 0x1c, 0xc8, 0x7b, 0x7d, 0x47, 0xed, 0x56, 0x39, 0x44, 0xee, 0x1d, 0xa1, 0x12, + 0xe8, 0x96, 0x04, 0xba, 0x5b, 0xec, 0xa6, 0xd4, 0x83, 0x7c, 0x1f, 0x49, 0xa2, 0x43, 0xb0, 0x38, + 0x71, 0x24, 0x50, 0x20, 0x63, 0xbe, 0xab, 0xf5, 0xe6, 0xed, 0xfe, 0xc9, 0x79, 0xa7, 0x71, 0x76, + 0xde, 0x79, 0x50, 0x7e, 0x75, 0x3e, 0x0c, 0x4d, 0xcc, 0x2c, 0x02, 0xc5, 0xb1, 0xf9, 0x1e, 0x8d, + 0x60, 0x30, 0xde, 0x43, 0xc1, 0xef, 0x5f, 0x5b, 0x40, 0x3d, 0x8a, 0x3d, 0x14, 0xb8, 0xad, 0xaa, + 0x8f, 0x0b, 0x05, 0xd2, 0x5f, 0x82, 0xb5, 0x62, 0x9b, 0x94, 0xfa, 0x8c, 0x0e, 0xaf, 0x42, 0x03, + 0x09, 0x7d, 0x8f, 0x60, 0xfa, 0xa9, 0xd2, 0x6b, 0xd8, 0x8f, 0xc1, 0xf2, 0xd4, 0x56, 0x21, 0x2c, + 0x48, 0x84, 0xa5, 0x89, 0xa0, 0xd6, 0x1f, 0x80, 0x82, 0xca, 0x0b, 0x18, 0x21, 0x98, 0x73, 0xcc, + 0x68, 0x09, 0xd1, 0x92, 0x10, 0x0f, 0x6f, 0x00, 0xe1, 0x2e, 0x13, 0x4c, 0xdf, 0x4c, 0xec, 0x72, + 0xf7, 0x5d, 0xb0, 0x51, 0x7c, 0x2e, 0x18, 0x08, 0x9c, 0x21, 0xef, 0x08, 0x53, 0x18, 0x61, 0x31, + 0x2e, 0x62, 0x90, 0xe1, 0x21, 0x4a, 0xb8, 0xb1, 0x28, 0xf7, 0x5f, 0x27, 0x30, 0xdf, 0x95, 0x35, + 0xfb, 0xaa, 0xc4, 0xa9, 0x2a, 0x5e, 0xcd, 0x7e, 0xff, 0xd1, 0x69, 0x6c, 0x22, 0xd0, 0x1a, 0x08, + 0x96, 0xa0, 0xa1, 0x8a, 0x8a, 0x01, 0xe6, 0x32, 0x94, 0x14, 0x73, 0x0c, 0x4d, 0xb6, 0xa8, 0x8e, + 0xfa, 0x6b, 0xd0, 0x2c, 0x73, 0x2a, 0x9f, 0xf7, 0xc2, 0xb3, 0x0d, 0xf3, 0xda, 0xa0, 0x9a, 0x65, + 0x23, 0x7b, 0xb6, 0x20, 0x73, 0x95, 0xc5, 0xfe, 0x70, 0x72, 0xd1, 0xd6, 0x4e, 0x2f, 0xda, 0xda, + 0xdf, 0x8b, 0xb6, 0xf6, 0xed, 0xb2, 0xdd, 0x38, 0xbd, 0x6c, 0x37, 0xfe, 0x5c, 0xb6, 0x1b, 0x9f, + 0x6f, 0x90, 0xc0, 0xbc, 0xfe, 0x77, 0x21, 0xe3, 0xe8, 0x37, 0x65, 0xc6, 0xb7, 0xff, 0x05, 0x00, + 0x00, 0xff, 0xff, 0xc6, 0x9f, 0xaa, 0x8d, 0x51, 0x04, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -249,25 +299,30 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxActiveFinalityProviders != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveFinalityProviders)) + i-- + dAtA[i] = 0x68 + } { - size := m.MinUnbondingRate.Size() + size := m.MinCommissionRate.Size() i -= size - if _, err := m.MinUnbondingRate.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.MinCommissionRate.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintParams(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x4a - if m.MinUnbondingTime != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.MinUnbondingTime)) + dAtA[i] = 0x62 + if m.UnbondingFeeSat != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.UnbondingFeeSat)) i-- - dAtA[i] = 0x40 + dAtA[i] = 0x58 } - if m.MaxActiveFinalityProviders != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.MaxActiveFinalityProviders)) + if m.MinUnbondingTimeBlocks != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinUnbondingTimeBlocks)) i-- - dAtA[i] = 0x38 + dAtA[i] = 0x50 } { size := m.SlashingRate.Size() @@ -278,28 +333,38 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintParams(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x32 - { - size := m.MinCommissionRate.Size() - i -= size - if _, err := m.MinCommissionRate.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintParams(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x2a + dAtA[i] = 0x4a if m.MinSlashingTxFeeSat != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MinSlashingTxFeeSat)) i-- + dAtA[i] = 0x40 + } + if len(m.SlashingPkScript) > 0 { + i -= len(m.SlashingPkScript) + copy(dAtA[i:], m.SlashingPkScript) + i = encodeVarintParams(dAtA, i, uint64(len(m.SlashingPkScript))) + i-- + dAtA[i] = 0x3a + } + if m.MaxStakingTimeBlocks != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxStakingTimeBlocks)) + i-- + dAtA[i] = 0x30 + } + if m.MinStakingTimeBlocks != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinStakingTimeBlocks)) + i-- + dAtA[i] = 0x28 + } + if m.MaxStakingValueSat != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxStakingValueSat)) + i-- dAtA[i] = 0x20 } - if len(m.SlashingAddress) > 0 { - i -= len(m.SlashingAddress) - copy(dAtA[i:], m.SlashingAddress) - i = encodeVarintParams(dAtA, i, uint64(len(m.SlashingAddress))) + if m.MinStakingValueSat != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MinStakingValueSat)) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x18 } if m.CovenantQuorum != 0 { i = encodeVarintParams(dAtA, i, uint64(m.CovenantQuorum)) @@ -387,25 +452,38 @@ func (m *Params) Size() (n int) { if m.CovenantQuorum != 0 { n += 1 + sovParams(uint64(m.CovenantQuorum)) } - l = len(m.SlashingAddress) + if m.MinStakingValueSat != 0 { + n += 1 + sovParams(uint64(m.MinStakingValueSat)) + } + if m.MaxStakingValueSat != 0 { + n += 1 + sovParams(uint64(m.MaxStakingValueSat)) + } + if m.MinStakingTimeBlocks != 0 { + n += 1 + sovParams(uint64(m.MinStakingTimeBlocks)) + } + if m.MaxStakingTimeBlocks != 0 { + n += 1 + sovParams(uint64(m.MaxStakingTimeBlocks)) + } + l = len(m.SlashingPkScript) if l > 0 { n += 1 + l + sovParams(uint64(l)) } if m.MinSlashingTxFeeSat != 0 { n += 1 + sovParams(uint64(m.MinSlashingTxFeeSat)) } - l = m.MinCommissionRate.Size() - n += 1 + l + sovParams(uint64(l)) l = m.SlashingRate.Size() n += 1 + l + sovParams(uint64(l)) - if m.MaxActiveFinalityProviders != 0 { - n += 1 + sovParams(uint64(m.MaxActiveFinalityProviders)) + if m.MinUnbondingTimeBlocks != 0 { + n += 1 + sovParams(uint64(m.MinUnbondingTimeBlocks)) } - if m.MinUnbondingTime != 0 { - n += 1 + sovParams(uint64(m.MinUnbondingTime)) + if m.UnbondingFeeSat != 0 { + n += 1 + sovParams(uint64(m.UnbondingFeeSat)) } - l = m.MinUnbondingRate.Size() + l = m.MinCommissionRate.Size() n += 1 + l + sovParams(uint64(l)) + if m.MaxActiveFinalityProviders != 0 { + n += 1 + sovParams(uint64(m.MaxActiveFinalityProviders)) + } return n } @@ -513,10 +591,10 @@ func (m *Params) Unmarshal(dAtA []byte) error { } } case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SlashingAddress", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStakingValueSat", wireType) } - var stringLen uint64 + m.MinStakingValueSat = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -526,29 +604,54 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.MinStakingValueSat |= int64(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthParams + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxStakingValueSat", wireType) } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthParams + m.MaxStakingValueSat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxStakingValueSat |= int64(b&0x7F) << shift + if b < 0x80 { + break + } } - if postIndex > l { - return io.ErrUnexpectedEOF + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinStakingTimeBlocks", wireType) } - m.SlashingAddress = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: + m.MinStakingTimeBlocks = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinStakingTimeBlocks |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinSlashingTxFeeSat", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MaxStakingTimeBlocks", wireType) } - m.MinSlashingTxFeeSat = 0 + m.MaxStakingTimeBlocks = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -558,16 +661,16 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MinSlashingTxFeeSat |= int64(b&0x7F) << shift + m.MaxStakingTimeBlocks |= uint32(b&0x7F) << shift if b < 0x80 { break } } - case 5: + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MinCommissionRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SlashingPkScript", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -577,27 +680,46 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthParams } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex < 0 { return ErrInvalidLengthParams } if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.MinCommissionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err + m.SlashingPkScript = append(m.SlashingPkScript[:0], dAtA[iNdEx:postIndex]...) + if m.SlashingPkScript == nil { + m.SlashingPkScript = []byte{} } iNdEx = postIndex - case 6: + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinSlashingTxFeeSat", wireType) + } + m.MinSlashingTxFeeSat = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinSlashingTxFeeSat |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field SlashingRate", wireType) } @@ -631,11 +753,11 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 10: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveFinalityProviders", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MinUnbondingTimeBlocks", wireType) } - m.MaxActiveFinalityProviders = 0 + m.MinUnbondingTimeBlocks = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -645,16 +767,16 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MaxActiveFinalityProviders |= uint32(b&0x7F) << shift + m.MinUnbondingTimeBlocks |= uint32(b&0x7F) << shift if b < 0x80 { break } } - case 8: + case 11: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinUnbondingTime", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingFeeSat", wireType) } - m.MinUnbondingTime = 0 + m.UnbondingFeeSat = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -664,14 +786,14 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MinUnbondingTime |= uint32(b&0x7F) << shift + m.UnbondingFeeSat |= int64(b&0x7F) << shift if b < 0x80 { break } } - case 9: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MinUnbondingRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MinCommissionRate", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -699,10 +821,29 @@ func (m *Params) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.MinUnbondingRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.MinCommissionRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxActiveFinalityProviders", wireType) + } + m.MaxActiveFinalityProviders = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxActiveFinalityProviders |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/btcstaking/types/parsed_message_validator.go b/x/btcstaking/types/validate_parsed_message.go similarity index 80% rename from x/btcstaking/types/parsed_message_validator.go rename to x/btcstaking/types/validate_parsed_message.go index 81be55108..a8d8d3abb 100644 --- a/x/btcstaking/types/parsed_message_validator.go +++ b/x/btcstaking/types/validate_parsed_message.go @@ -6,9 +6,7 @@ import ( "github.com/babylonlabs-io/babylon/btcstaking" bbn "github.com/babylonlabs-io/babylon/types" btcckpttypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/wire" ) type ParamsValidationResult struct { @@ -16,21 +14,8 @@ type ParamsValidationResult struct { UnbondingOutputIdx uint32 } -// caluculateMinimumUnbondingValue calculates minimum unbonding value basend on current staking output value -// and params.MinUnbondingRate -func caluculateMinimumUnbondingValue( - stakingOutput *wire.TxOut, - params *Params, -) btcutil.Amount { - // this conversions must always succeed, as it is part of our params - minUnbondingRate := params.MinUnbondingRate.MustFloat64() - // Caluclate min unbonding output value based on staking output, use btc native multiplication - minUnbondingOutputValue := btcutil.Amount(stakingOutput.Value).MulF64(minUnbondingRate) - return minUnbondingOutputValue -} - -// ValidateParams validates parsed message against parameters -func ValidateParams( +// ValidateParsedMessageAgainstTheParams validates parsed message against parameters +func ValidateParsedMessageAgainstTheParams( pm *ParsedCreateDelegationMessage, parameters *Params, btcheckpointParamseters *btcckpttypes.Params, @@ -48,10 +33,10 @@ func ValidateParams( stakingTxHash := pm.StakingTx.Transaction.TxHash() covenantPks := parameters.MustGetCovenantPks() - slashingAddr := parameters.MustGetSlashingAddress(net) // 2. Validate all data related to staking tx: // - it has valid staking output + // - that staking time and value are correct // - slashing tx is relevent to staking tx // - slashing tx signature is valid stakingInfo, err := btcstaking.BuildStakingInfo( @@ -73,13 +58,33 @@ func ValidateParams( return nil, ErrInvalidStakingTx.Wrap("staking tx does not contain expected staking output") } + if uint32(pm.StakingTime) < parameters.MinStakingTimeBlocks || + uint32(pm.StakingTime) > parameters.MaxStakingTimeBlocks { + return nil, ErrInvalidStakingTx.Wrapf( + "staking time %d is out of bounds. Min: %d, Max: %d", + pm.StakingTime, + parameters.MinStakingTimeBlocks, + parameters.MaxStakingTimeBlocks, + ) + } + + if pm.StakingTx.Transaction.TxOut[stakingOutputIdx].Value < parameters.MinStakingValueSat || + pm.StakingTx.Transaction.TxOut[stakingOutputIdx].Value > parameters.MaxStakingValueSat { + return nil, ErrInvalidStakingTx.Wrapf( + "staking value %d is out of bounds. Min: %d, Max: %d", + pm.StakingTx.Transaction.TxOut[stakingOutputIdx].Value, + parameters.MinStakingValueSat, + parameters.MaxStakingValueSat, + ) + } + if err := btcstaking.CheckTransactions( pm.StakingSlashingTx.Transaction, pm.StakingTx.Transaction, stakingOutputIdx, parameters.MinSlashingTxFeeSat, parameters.SlashingRate, - slashingAddr, + parameters.SlashingPkScript, pm.StakerPK.PublicKey, pm.UnbondingTime, net, @@ -130,7 +135,7 @@ func ValidateParams( unbondingOutputIdx, parameters.MinSlashingTxFeeSat, parameters.SlashingRate, - slashingAddr, + parameters.SlashingPkScript, pm.StakerPK.PublicKey, pm.UnbondingTime, net, @@ -174,9 +179,11 @@ func ValidateParams( return nil, ErrInvalidUnbondingTx.Wrapf("unbonding tx fee must be larger that 0") } - minUnbondingValue := caluculateMinimumUnbondingValue(pm.StakingTx.Transaction.TxOut[stakingOutputIdx], parameters) - if btcutil.Amount(pm.UnbondingTx.Transaction.TxOut[0].Value) < minUnbondingValue { - return nil, ErrInvalidUnbondingTx.Wrapf("unbonding output value must be at least %s, based on staking output", minUnbondingValue) + // 6. Check that unbonding tx fee is as expected. + unbondingTxFee := pm.StakingTx.Transaction.TxOut[stakingOutputIdx].Value - pm.UnbondingTx.Transaction.TxOut[0].Value + + if unbondingTxFee != parameters.UnbondingFeeSat { + return nil, ErrInvalidUnbondingTx.Wrapf("unbonding tx fee must be %d, but got %d", parameters.UnbondingFeeSat, unbondingTxFee) } return &ParamsValidationResult{ diff --git a/x/btcstaking/types/validate_parsed_message_test.go b/x/btcstaking/types/validate_parsed_message_test.go new file mode 100644 index 000000000..1c133abe4 --- /dev/null +++ b/x/btcstaking/types/validate_parsed_message_test.go @@ -0,0 +1,788 @@ +package types_test + +import ( + "math/rand" + "testing" + "time" + + sdkmath "cosmossdk.io/math" + "github.com/babylonlabs-io/babylon/btcstaking" + "github.com/babylonlabs-io/babylon/testutil/datagen" + bbn "github.com/babylonlabs-io/babylon/types" + btcckpttypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" + "github.com/babylonlabs-io/babylon/x/btcstaking/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +// testStakingParams generates valid staking parameters with randomized +// - covenant committee members public keys +// - slashing address +func testStakingParams( + r *rand.Rand, + t *testing.T, +) *types.Params { + // randomise covenant committee + _, covenantPKs, err := datagen.GenRandomBTCKeyPairs(r, 5) + require.NoError(t, err) + slashingAddress, err := datagen.GenRandomBTCAddress(r, &chaincfg.MainNetParams) + require.NoError(t, err) + slashingPkScript, err := txscript.PayToAddrScript(slashingAddress) + require.NoError(t, err) + + return &types.Params{ + CovenantPks: bbn.NewBIP340PKsFromBTCPKs(covenantPKs), + CovenantQuorum: 3, + MinStakingValueSat: 100000, + MaxStakingValueSat: int64(4 * 10e8), + MinStakingTimeBlocks: 10, + MaxStakingTimeBlocks: 10000, + SlashingPkScript: slashingPkScript, + MinSlashingTxFeeSat: 1000, + MinCommissionRate: sdkmath.LegacyMustNewDecFromStr("0.01"), + SlashingRate: sdkmath.LegacyNewDecWithPrec(int64(datagen.RandomInt(r, 41)+10), 2), + MaxActiveFinalityProviders: 100, + MinUnbondingTimeBlocks: 200, + UnbondingFeeSat: 1000, + } +} + +// testCheckpointParams generates valid btccheckpoint parameters +func testCheckpointParams() *btcckpttypes.Params { + return &btcckpttypes.Params{ + BtcConfirmationDepth: 10, + CheckpointFinalizationTimeout: 100, + } +} + +func randRange( + r *rand.Rand, + min, max int) int { + return r.Intn(max-min) + min +} + +type unbondingInfo struct { + unbondingSlashingTx *types.BTCSlashingTx + unbondingSlashinSig *bbn.BIP340Signature + serializedUnbondingTx []byte +} + +// generateUnbondingInfo generates valid: +// - unbonding transaction +// - unbonding slashing transaction +// - unbonding slashing transactions staker signature +func generateUnbondingInfo( + r *rand.Rand, + t *testing.T, + delSK *btcec.PrivateKey, + fpPk *btcec.PublicKey, + stkTxHash chainhash.Hash, + stkOutputIdx uint32, + unbondingTime uint16, + unbondingValue int64, + p *types.Params, +) *unbondingInfo { + + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(p.CovenantPks) + require.NoError(t, err) + + testUnbondingInfo := datagen.GenBTCUnbondingSlashingInfo( + r, + t, + &chaincfg.MainNetParams, + delSK, + []*btcec.PublicKey{fpPk}, + covPKs, + p.CovenantQuorum, + wire.NewOutPoint(&stkTxHash, stkOutputIdx), + unbondingTime, + unbondingValue, + p.SlashingPkScript, + p.SlashingRate, + unbondingTime, + ) + + delSlashingTxSig, err := testUnbondingInfo.GenDelSlashingTxSig(delSK) + require.NoError(t, err) + + serializedUnbondingTx, err := bbn.SerializeBTCTx(testUnbondingInfo.UnbondingTx) + require.NoError(t, err) + + return &unbondingInfo{ + unbondingSlashingTx: testUnbondingInfo.SlashingTx, + unbondingSlashinSig: delSlashingTxSig, + serializedUnbondingTx: serializedUnbondingTx, + } +} + +// createMsgDelegationForParams creates a valid message to create delegation +// based on provided parameters. +// It randomly generates: +// - staker address and btc key pair +// - finality provider btc key pair +// - staking time +// - staking value +func createMsgDelegationForParams( + r *rand.Rand, + t *testing.T, + p *types.Params, + cp *btcckpttypes.Params, +) (*types.MsgCreateBTCDelegation, *btcec.PrivateKey) { + // staker related date + delSK, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + stPk := bbn.NewBIP340PubKeyFromBTCPK(delPK) + staker := sdk.MustAccAddressFromBech32(datagen.GenRandomAccount().Address) + pop, err := types.NewPoPBTC(staker, delSK) + require.NoError(t, err) + // finality provider related data + _, fpPk, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + fpPkBBn := bbn.NewBIP340PubKeyFromBTCPK(fpPk) + // covenants + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(p.CovenantPks) + require.NoError(t, err) + + stakingTimeBlocks := uint16(randRange(r, int(p.MinStakingTimeBlocks), int(p.MaxStakingTimeBlocks))) + stakingValue := int64(randRange(r, int(p.MinStakingValueSat), int(p.MaxStakingValueSat))) + + // always chose minimum unbonding time possible + unbondingTime := uint16(types.MinimumUnbondingTime(p, cp)) + 1 + + testStakingInfo := datagen.GenBTCStakingSlashingInfo( + r, + t, + &chaincfg.MainNetParams, + delSK, + []*btcec.PublicKey{fpPk}, + covPKs, + p.CovenantQuorum, + stakingTimeBlocks, + stakingValue, + p.SlashingPkScript, + p.SlashingRate, + unbondingTime, + ) + + slashingSpendInfo, err := testStakingInfo.StakingInfo.SlashingPathSpendInfo() + require.NoError(t, err) + + // generate proper delegator sig + delegatorSig, err := testStakingInfo.SlashingTx.Sign( + testStakingInfo.StakingTx, + 0, + slashingSpendInfo.GetPkScriptPath(), + delSK, + ) + require.NoError(t, err) + + prevBlock, _ := datagen.GenRandomBtcdBlock(r, 0, nil) + btcHeaderWithProof := datagen.CreateBlockWithTransaction(r, &prevBlock.Header, testStakingInfo.StakingTx) + btcHeader := btcHeaderWithProof.HeaderBytes + serializedStakingTx, err := bbn.SerializeBTCTx(testStakingInfo.StakingTx) + require.NoError(t, err) + + txInfo := btcckpttypes.NewTransactionInfo( + &btcckpttypes.TransactionKey{Index: 1, Hash: btcHeader.Hash()}, + serializedStakingTx, + btcHeaderWithProof.SpvProof.MerkleNodes, + ) + + stkTxHash := testStakingInfo.StakingTx.TxHash() + stkOutputIdx := uint32(0) + + unbondingValue := stakingValue - p.UnbondingFeeSat + + unbondingInfo := generateUnbondingInfo( + r, + t, + delSK, + fpPk, + stkTxHash, + stkOutputIdx, + unbondingTime, + unbondingValue, + p, + ) + + msgCreateBTCDel := &types.MsgCreateBTCDelegation{ + StakerAddr: staker.String(), + BtcPk: stPk, + FpBtcPkList: []bbn.BIP340PubKey{*fpPkBBn}, + Pop: pop, + StakingTime: uint32(stakingTimeBlocks), + StakingValue: stakingValue, + StakingTx: txInfo, + SlashingTx: testStakingInfo.SlashingTx, + DelegatorSlashingSig: delegatorSig, + UnbondingTx: unbondingInfo.serializedUnbondingTx, + UnbondingTime: uint32(unbondingTime), + UnbondingValue: unbondingValue, + UnbondingSlashingTx: unbondingInfo.unbondingSlashingTx, + DelegatorUnbondingSlashingSig: unbondingInfo.unbondingSlashinSig, + } + + return msgCreateBTCDel, delSK +} + +func TestValidateParsedMessageAgainstTheParams(t *testing.T) { + tests := []struct { + name string + fn func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) + err error + }{ + { + name: "valid create delegation message", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + return msg, params, checkpointParams + }, + err: nil, + }, + { + name: "too low unbonding time", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.UnbondingTime = msg.StakingTime - 1 + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.BtcPk do not match pk in staking transaction", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + _, delPK, err := datagen.GenRandomBTCKeyPair(r) + require.NoError(t, err) + + stPk := bbn.NewBIP340PubKeyFromBTCPK(delPK) + + msg.BtcPk = stPk + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingTime do not match staking time commited in staking transaction", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.StakingTime = msg.StakingTime + 1 + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingValue do not match staking time commited in staking transaction", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.StakingValue = msg.StakingValue + 1 + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingValue is lower than params.MinStakingValueSat", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.StakingValue = params.MinStakingValueSat - 1 + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingValue is higher than params.MinStakingValueSat", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + msg.StakingValue = params.MaxStakingValueSat + 1 + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingTime is lower than params.MinStakingTimeBlocks", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + // modify staking output so that staking output is valid but it will have + // invalid time + currentStakingTx, err := bbn.NewBTCTxFromBytes(msg.StakingTx.Transaction) + require.NoError(t, err) + + invalidStakingTime := uint16(params.MinStakingTimeBlocks - 1) + + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(params.CovenantPks) + require.NoError(t, err) + + stakingOutput, err := btcstaking.BuildStakingInfo( + msg.BtcPk.MustToBTCPK(), + []*btcec.PublicKey{msg.FpBtcPkList[0].MustToBTCPK()}, + covPKs, + params.CovenantQuorum, + invalidStakingTime, + btcutil.Amount(msg.StakingValue), + &chaincfg.MainNetParams, + ) + require.NoError(t, err) + + currentStakingTx.TxOut[0] = stakingOutput.StakingOutput + + serializedNewStakingTx, err := bbn.SerializeBTCTx(currentStakingTx) + require.NoError(t, err) + + msg.StakingTime = uint32(invalidStakingTime) + msg.StakingTx.Transaction = serializedNewStakingTx + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingTime is higher than params.MinStakingTimeBlocks", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + // modify staking output so that staking output is valid but it will have + // invalid time + currentStakingTx, err := bbn.NewBTCTxFromBytes(msg.StakingTx.Transaction) + require.NoError(t, err) + + invalidStakingTime := uint16(params.MaxStakingTimeBlocks + 1) + + covPKs, err := bbn.NewBTCPKsFromBIP340PKs(params.CovenantPks) + require.NoError(t, err) + + stakingOutput, err := btcstaking.BuildStakingInfo( + msg.BtcPk.MustToBTCPK(), + []*btcec.PublicKey{msg.FpBtcPkList[0].MustToBTCPK()}, + covPKs, + params.CovenantQuorum, + invalidStakingTime, + btcutil.Amount(msg.StakingValue), + &chaincfg.MainNetParams, + ) + require.NoError(t, err) + + currentStakingTx.TxOut[0] = stakingOutput.StakingOutput + + serializedNewStakingTx, err := bbn.SerializeBTCTx(currentStakingTx) + require.NoError(t, err) + + msg.StakingTime = uint32(invalidStakingTime) + msg.StakingTx.Transaction = serializedNewStakingTx + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingValue is lower than params.MinStakingValueSat", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + // modify staking output so that staking output is valid but it will have + // invalid time + currentStakingTx, err := bbn.NewBTCTxFromBytes(msg.StakingTx.Transaction) + require.NoError(t, err) + + invalidStakingValue := params.MinStakingValueSat - 1 + + currentStakingTx.TxOut[0].Value = invalidStakingValue + + serializedNewStakingTx, err := bbn.SerializeBTCTx(currentStakingTx) + require.NoError(t, err) + + msg.StakingValue = invalidStakingValue + msg.StakingTx.Transaction = serializedNewStakingTx + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.StakingValue is higher than params.MaxStakingValueSat", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + // modify staking output so that staking output is valid but it will have + // invalid time + currentStakingTx, err := bbn.NewBTCTxFromBytes(msg.StakingTx.Transaction) + require.NoError(t, err) + + invalidStakingValue := params.MaxStakingValueSat + 1 + + currentStakingTx.TxOut[0].Value = invalidStakingValue + + serializedNewStakingTx, err := bbn.SerializeBTCTx(currentStakingTx) + require.NoError(t, err) + + msg.StakingValue = invalidStakingValue + msg.StakingTx.Transaction = serializedNewStakingTx + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.SlashingTx have invalid pk script", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.SlashingTx) + require.NoError(t, err) + + invalidSlashingPkScript := make([]byte, len(params.SlashingPkScript)) + copy(invalidSlashingPkScript, params.SlashingPkScript) + // change one byte in the pk script + invalidSlashingPkScript[0] = invalidSlashingPkScript[0] + 1 + + // slashing output must always be first output + currentSlashingTx.TxOut[0].PkScript = invalidSlashingPkScript + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentSlashingTx) + require.NoError(t, err) + msg.SlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.SlashingTx does not point to staking tx hash", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.SlashingTx) + require.NoError(t, err) + + invalidHashBytes := currentSlashingTx.TxIn[0].PreviousOutPoint.Hash.CloneBytes() + // change one byte in the hash + invalidHashBytes[0] = invalidHashBytes[0] + 1 + + invalidHash, err := chainhash.NewHash(invalidHashBytes) + require.NoError(t, err) + + currentSlashingTx.TxIn[0].PreviousOutPoint.Hash = *invalidHash + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentSlashingTx) + require.NoError(t, err) + msg.SlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.SlashingTx does not point to staking tx output index", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.SlashingTx) + require.NoError(t, err) + + currentSlashingTx.TxIn[0].PreviousOutPoint.Index = currentSlashingTx.TxIn[0].PreviousOutPoint.Index + 1 + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentSlashingTx) + require.NoError(t, err) + msg.SlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidStakingTx, + }, + { + name: "Msg.DelegatorSlashingSig is invalid signature", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + sigInMessage := msg.DelegatorSlashingSig.MustMarshal() + + invalidSlashingSig := make([]byte, len(sigInMessage)) + copy(invalidSlashingSig, sigInMessage) + // change last byte is sig + invalidSlashingSig[63] = invalidSlashingSig[63] + 1 + + newSig, err := bbn.NewBIP340Signature(invalidSlashingSig) + require.NoError(t, err) + + msg.DelegatorSlashingSig = newSig + + return msg, params, checkpointParams + }, + err: types.ErrInvalidSlashingTx, + }, + { + name: "Msg.UnbondingSlashingTx does not point to unbonding tx hash", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.UnbondingSlashingTx) + require.NoError(t, err) + + invalidHashBytes := currentSlashingTx.TxIn[0].PreviousOutPoint.Hash.CloneBytes() + // change one byte in the hash + invalidHashBytes[0] = invalidHashBytes[0] + 1 + + invalidHash, err := chainhash.NewHash(invalidHashBytes) + require.NoError(t, err) + + currentSlashingTx.TxIn[0].PreviousOutPoint.Hash = *invalidHash + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentSlashingTx) + require.NoError(t, err) + msg.UnbondingSlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.UnbondingSlashingTx does not point to unbonding tx output index", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.UnbondingSlashingTx) + require.NoError(t, err) + + currentSlashingTx.TxIn[0].PreviousOutPoint.Index = currentSlashingTx.TxIn[0].PreviousOutPoint.Index + 1 + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentSlashingTx) + require.NoError(t, err) + msg.UnbondingSlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.UnbondingSlashingTx have invalid pk script", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentUnbondingSlashingTx, err := bbn.NewBTCTxFromBytes(*msg.UnbondingSlashingTx) + require.NoError(t, err) + + invalidSlashingPkScript := make([]byte, len(params.SlashingPkScript)) + copy(invalidSlashingPkScript, params.SlashingPkScript) + // change one byte in the pk script + invalidSlashingPkScript[0] = invalidSlashingPkScript[0] + 1 + + // slashing output must always be first output + currentUnbondingSlashingTx.TxOut[0].PkScript = invalidSlashingPkScript + + serializedNewSlashingTx, err := bbn.SerializeBTCTx(currentUnbondingSlashingTx) + require.NoError(t, err) + msg.UnbondingSlashingTx = types.NewBtcSlashingTxFromBytes(serializedNewSlashingTx) + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.DelegatorUnbondingSlashingSig is invalid signature", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, _ := createMsgDelegationForParams(r, t, params, checkpointParams) + + sigInMessage := msg.DelegatorUnbondingSlashingSig.MustMarshal() + + invalidSlashingSig := make([]byte, len(sigInMessage)) + copy(invalidSlashingSig, sigInMessage) + // change last byte is sig + invalidSlashingSig[63] = invalidSlashingSig[63] + 1 + + newSig, err := bbn.NewBIP340Signature(invalidSlashingSig) + require.NoError(t, err) + + msg.DelegatorUnbondingSlashingSig = newSig + + return msg, params, checkpointParams + }, + err: types.ErrInvalidSlashingTx, + }, + { + name: "Msg.UnbondingTx does not point to staking tx hash", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, delSk := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentUnbondingTx, err := bbn.NewBTCTxFromBytes(msg.UnbondingTx) + require.NoError(t, err) + + invalidHashBytes := currentUnbondingTx.TxIn[0].PreviousOutPoint.Hash.CloneBytes() + // change one byte in the hash + invalidHashBytes[0] = invalidHashBytes[0] + 1 + + invalidHash, err := chainhash.NewHash(invalidHashBytes) + require.NoError(t, err) + + // generate unbonding info with invalid stakig tx hash + newUnbondingInfdo := generateUnbondingInfo( + r, + t, + delSk, + msg.FpBtcPkList[0].MustToBTCPK(), + *invalidHash, + 0, + uint16(msg.UnbondingTime), + msg.UnbondingValue, + params, + ) + + msg.UnbondingTx = newUnbondingInfdo.serializedUnbondingTx + msg.UnbondingSlashingTx = newUnbondingInfdo.unbondingSlashingTx + msg.DelegatorUnbondingSlashingSig = newUnbondingInfdo.unbondingSlashinSig + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.UnbondingTx does not point to staking tx output index", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, delSk := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentUnbondingTx, err := bbn.NewBTCTxFromBytes(msg.UnbondingTx) + require.NoError(t, err) + + // generate unbonding info with invalid staking idx + newUnbondingInfdo := generateUnbondingInfo( + r, + t, + delSk, + msg.FpBtcPkList[0].MustToBTCPK(), + currentUnbondingTx.TxIn[0].PreviousOutPoint.Hash, + currentUnbondingTx.TxIn[0].PreviousOutPoint.Index+1, + uint16(msg.UnbondingTime), + msg.UnbondingValue, + params, + ) + + msg.UnbondingTx = newUnbondingInfdo.serializedUnbondingTx + msg.UnbondingSlashingTx = newUnbondingInfdo.unbondingSlashingTx + msg.DelegatorUnbondingSlashingSig = newUnbondingInfdo.unbondingSlashinSig + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + { + name: "Msg.UnbondingTx does not have required fee", + fn: func(r *rand.Rand, t *testing.T) (*types.MsgCreateBTCDelegation, *types.Params, *btcckpttypes.Params) { + params := testStakingParams(r, t) + checkpointParams := testCheckpointParams() + msg, delSk := createMsgDelegationForParams(r, t, params, checkpointParams) + + currentUnbondingTx, err := bbn.NewBTCTxFromBytes(msg.UnbondingTx) + require.NoError(t, err) + + // generate unbonding info with invalid staking idx + newUnbondingInfdo := generateUnbondingInfo( + r, + t, + delSk, + msg.FpBtcPkList[0].MustToBTCPK(), + currentUnbondingTx.TxIn[0].PreviousOutPoint.Hash, + currentUnbondingTx.TxIn[0].PreviousOutPoint.Index, + uint16(msg.UnbondingTime), + // adding 1 to unbonding value, will decrease fee by 1 sat and now it + // won't be enough + msg.UnbondingValue+1, + params, + ) + + msg.UnbondingValue = msg.UnbondingValue + 1 + msg.UnbondingTx = newUnbondingInfdo.serializedUnbondingTx + msg.UnbondingSlashingTx = newUnbondingInfdo.unbondingSlashingTx + msg.DelegatorUnbondingSlashingSig = newUnbondingInfdo.unbondingSlashinSig + + return msg, params, checkpointParams + }, + err: types.ErrInvalidUnbondingTx, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().Unix())) + + msg, params, checkpointParams := tt.fn(r, t) + + parsed, err := types.ParseCreateDelegationMessage(msg) + require.NoError(t, err) + + got, err := types.ValidateParsedMessageAgainstTheParams( + parsed, + params, + checkpointParams, + &chaincfg.MainNetParams, + ) + + if tt.err != nil { + require.Error(t, err) + require.ErrorAs(t, err, &tt.err) + } else { + require.NoError(t, err) + require.NotNil(t, got) + } + + }) + } +}