From a8377e27fc4f0840a8dbc99a3086a5f33517a0b9 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 23 Jun 2022 14:28:35 -0500 Subject: [PATCH] feat!: bring back the cliff vesting command (#111) (#271) * Bring back the cliff vesting command https://github.com/osmosis-labs/cosmos-sdk/pull/111 * Wrap error (cherry picked from commit bf5b1635292f743c9d54a54721016fcbe8faf51f) # Conflicts: # x/auth/vesting/client/cli/tx.go --- x/auth/vesting/client/cli/tx.go | 231 ++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/x/auth/vesting/client/cli/tx.go b/x/auth/vesting/client/cli/tx.go index 686a941e5de2..33c4362fe9a7 100644 --- a/x/auth/vesting/client/cli/tx.go +++ b/x/auth/vesting/client/cli/tx.go @@ -2,6 +2,7 @@ package cli import ( "strconv" + "time" "github.com/spf13/cobra" @@ -9,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" ) @@ -29,6 +31,12 @@ func GetTxCmd() *cobra.Command { txCmd.AddCommand( NewMsgCreateVestingAccountCmd(), +<<<<<<< HEAD +======= + NewMsgCreateCliffVestingAccountCmd(), + NewMsgCreateClawbackVestingAccountCmd(), + NewMsgClawbackCmd(), +>>>>>>> bf5b16352 (feat!: bring back the cliff vesting command (#111) (#271)) ) return txCmd @@ -79,3 +87,226 @@ timestamp.`, return cmd } +<<<<<<< HEAD +======= + +type VestingData struct { + StartTime int64 `json:"start_time"` + Periods []InputPeriod `json:"periods"` +} + +type InputPeriod struct { + Coins string `json:"coins"` + Length int64 `json:"length"` +} + +// readScheduleFile reads the file at path and unmarshals it to get the schedule. +// Returns start time, periods, and error. +func readScheduleFile(path string) (int64, []types.Period, error) { + contents, err := ioutil.ReadFile(path) + if err != nil { + return 0, nil, err + } + + var data VestingData + if err := json.Unmarshal(contents, &data); err != nil { + return 0, nil, err + } + + startTime := data.StartTime + periods := make([]types.Period, len(data.Periods)) + + for i, p := range data.Periods { + amount, err := sdk.ParseCoinsNormalized(p.Coins) + if err != nil { + return 0, nil, err + } + if p.Length < 1 { + return 0, nil, fmt.Errorf("invalid period length of %d in period %d, length must be greater than 0", p.Length, i) + } + + periods[i] = types.Period{Length: p.Length, Amount: amount} + } + + return startTime, periods, nil +} + +// NewMsgCreateClawbackVestingAccountCmd returns a CLI command handler for creating a +// MsgCreateClawbackVestingAccount transaction. +func NewMsgCreateClawbackVestingAccountCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-clawback-vesting-account [to_address]", + Short: "Create a new vesting account funded with an allocation of tokens, subject to clawback.", + Long: `Must provide a lockup periods file (--lockup), a vesting periods file (--vesting), or both. +If both files are given, they must describe schedules for the same total amount. +If one file is omitted, it will default to a schedule that immediately unlocks or vests the entire amount. +The described amount of coins will be transferred from the --from address to the vesting account. +Unvested coins may be "clawed back" by the funder with the clawback command. +Coins may not be transferred out of the account if they are locked or unvested, but may be staked. +Staking rewards are subject to a proportional vesting encumbrance. + +A periods file is a JSON object describing a sequence of unlocking or vesting events, +with a start time and an array of coins strings and durations relative to the start or previous event.`, + Example: `Sample period file contents: +{ + "start_time": 1625204910, + "periods": [ + { + "coins": "10test", + "length": 2592000 // 30 days + }, + { + "coins": "10test", + "length": 2592000 // 30 days + } + ] +} +`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + toAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + lockupFile, _ := cmd.Flags().GetString(FlagLockup) + vestingFile, _ := cmd.Flags().GetString(FlagVesting) + if lockupFile == "" && vestingFile == "" { + return fmt.Errorf("must specify at least one of %s or %s", FlagLockup, FlagVesting) + } + + var ( + lockupStart, vestingStart int64 + lockupPeriods, vestingPeriods []types.Period + ) + if lockupFile != "" { + lockupStart, lockupPeriods, err = readScheduleFile(lockupFile) + if err != nil { + return err + } + } + if vestingFile != "" { + vestingStart, vestingPeriods, err = readScheduleFile(vestingFile) + if err != nil { + return err + } + } + + commonStart, _ := types.AlignSchedules(lockupStart, vestingStart, lockupPeriods, vestingPeriods) + + merge, _ := cmd.Flags().GetBool(FlagMerge) + + msg := types.NewMsgCreateClawbackVestingAccount(clientCtx.GetFromAddress(), toAddr, commonStart, lockupPeriods, vestingPeriods, merge) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().Bool(FlagMerge, false, "Merge new amount and schedule with existing ClawbackVestingAccount, if any") + cmd.Flags().String(FlagLockup, "", "Path to file containing unlocking periods") + cmd.Flags().String(FlagVesting, "", "Path to file containing vesting periods") + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewMsgClawbackCmd returns a CLI command handler for creating a +// MsgClawback transaction. +func NewMsgClawbackCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "clawback [address]", + Short: "Transfer unvested amount out of a ClawbackVestingAccount.", + Long: `Must be requested by the original funder address (--from). + May provide a destination address (--dest), otherwise the coins return to the funder. + Delegated or undelegating staking tokens will be transferred in the delegated (undelegating) state. + The recipient is vulnerable to slashing, and must act to unbond the tokens if desired. + `, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + var dest sdk.AccAddress + destString, _ := cmd.Flags().GetString(FlagDest) + if destString != "" { + dest, err = sdk.AccAddressFromBech32(destString) + if err != nil { + return fmt.Errorf("invalid destination address: %w", err) + } + } + + msg := types.NewMsgClawback(clientCtx.GetFromAddress(), addr, dest) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().String(FlagDest, "", "Address of destination (defaults to funder)") + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// NewMsgCreateDelayedVestingAccountCmd returns a CLI command handler for creating a +// NewMsgCreateDelayedVestingAccountCmd transaction. +// This is hacky, but meant to mitigate the pain of a very specific use case. +// Namely, make it easy to make cliff locks to an address. +func NewMsgCreateCliffVestingAccountCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-cliff-vesting-account [to_address] [amount] [cliff_duration]", + Short: "Create a new cliff vesting account funded with an allocation of tokens.", + Long: `Create a new delayed vesting account funded with an allocation of tokens. All vesting accouts created will have their start time +set by the committed block's time. The cliff duration should be specified in hours.`, + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + toAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + amount, err := sdk.ParseCoinsNormalized(args[1]) + if err != nil { + return err + } + + cliffDuration, err := time.ParseDuration(args[2]) + if err != nil { + err = errors.Wrap(err, "duration incorrectly formatted, see https://pkg.go.dev/time#ParseDuration") + return err + } + cliffVesting := true + + endTime := time.Now().Add(cliffDuration) + endEpochTime := endTime.Unix() + + msg := types.NewMsgCreateVestingAccount(clientCtx.GetFromAddress(), toAddr, amount, endEpochTime, cliffVesting) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} +>>>>>>> bf5b16352 (feat!: bring back the cliff vesting command (#111) (#271))