Skip to content

Commit

Permalink
feat!: Add start time for continuous vesting accounts (#342)
Browse files Browse the repository at this point in the history
* Draft changes to be able to add a start-time in the future for a continuous vesting acct

* Some minor cleanup for consistency

* chore: add a test to the cli

* chore: run make proto-gen using old scripts

* delete TestGetVestingCoinsContVestingAccStartTimeInFuture

* fix merge/rebase glitch

* make GetVested.. test consistent

* update spec

---------

Co-authored-by: evan-forbes <[email protected]>
  • Loading branch information
liamsi and evan-forbes authored Aug 31, 2023
1 parent c09602f commit 7babe18
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 61 deletions.
2 changes: 2 additions & 0 deletions proto/cosmos/vesting/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ message MsgCreateVestingAccount {
// end of vesting as unix time (in seconds).
int64 end_time = 4;
bool delayed = 5;
// start of vesting as unix time (in seconds).
int64 start_time = 6;
}

// MsgCreateVestingAccountResponse defines the Msg/CreateVestingAccount response type.
Expand Down
2 changes: 1 addition & 1 deletion x/auth/spec/07_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ simd tx vesting create-periodic-vesting-account cosmos1.. periods.json

#### create-vesting-account

The `create-vesting-account` command creates a new vesting account funded with an allocation of tokens. The account can either be a delayed or continuous vesting account, which is determined by the '--delayed' flag. All vesting accouts created will have their start time set by the committed block's time. The end_time must be provided as a UNIX epoch timestamp.
The `create-vesting-account` command creates a new vesting account funded with an allocation of tokens. The account can either be a delayed or continuous vesting account, which is determined by the '--delayed' flag. All vesting accounts created will have their start time set by the committed block's time unless specified explicitly using the `--start-time`flag. The `end_time` must be provided as a UNIX epoch timestamp.

```bash
simd tx vesting create-vesting-account [to_address] [amount] [end_time] [flags]
Expand Down
11 changes: 8 additions & 3 deletions x/auth/vesting/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import (

// Transaction command flags
const (
FlagDelayed = "delayed"
FlagDelayed = "delayed"
FlagStartTime = "start-time"
)

// GetTxCmd returns vesting module's transaction commands.
Expand Down Expand Up @@ -72,14 +73,18 @@ timestamp.`,
}

delayed, _ := cmd.Flags().GetBool(FlagDelayed)
startTime, err := cmd.Flags().GetInt64(FlagStartTime)
if err != nil {
return err
}

msg := types.NewMsgCreateVestingAccount(clientCtx.GetFromAddress(), toAddr, amount, endTime, delayed)

msg := types.NewMsgCreateVestingAccount(clientCtx.GetFromAddress(), toAddr, amount, startTime, endTime, delayed)
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

cmd.Flags().Bool(FlagDelayed, false, "Create a delayed vesting account if true")
cmd.Flags().Int64(FlagStartTime, 0, "Optional start time (as a UNIX epoch timestamp) for continuous vesting accounts. If 0 (default), the block's time of the block this tx is committed to will be used.")
flags.AddTxFlagsToCmd(cmd)

return cmd
Expand Down
15 changes: 15 additions & 0 deletions x/auth/vesting/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ func (s *IntegrationTestSuite) TestNewMsgCreateVestingAccountCmd() {
expectedCode: 0,
respType: &sdk.TxResponse{},
},
"create a continuous vesting account with start time": {
args: []string{
sdk.AccAddress("addr2_______________").String(),
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String(),
"4070908800",
fmt.Sprintf("--%s=%d", cli.FlagStartTime, 4070808800),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
},
expectErr: false,
expectedCode: 0,
respType: &sdk.TxResponse{},
},
"create a delayed vesting account": {
args: []string{
sdk.AccAddress("addr3_______________").String(),
Expand Down
14 changes: 13 additions & 1 deletion x/auth/vesting/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ func (s msgServer) CreateVestingAccount(goCtx context.Context, msg *types.MsgCre
return nil, err
}

if msg.EndTime <= 0 {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid end time")
}

if msg.EndTime <= msg.StartTime {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "invalid start and end time (must be start < end)")
}

if bk.BlockedAddr(to) {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress)
}
Expand All @@ -60,7 +68,11 @@ func (s msgServer) CreateVestingAccount(goCtx context.Context, msg *types.MsgCre
if msg.Delayed {
vestingAccount = types.NewDelayedVestingAccountRaw(baseVestingAccount)
} else {
vestingAccount = types.NewContinuousVestingAccountRaw(baseVestingAccount, ctx.BlockTime().Unix())
start := ctx.BlockTime().Unix()
if msg.StartTime != 0 {
start = msg.StartTime
}
vestingAccount = types.NewContinuousVestingAccountRaw(baseVestingAccount, start)
}

ak.SetAccount(ctx, vestingAccount)
Expand Down
3 changes: 2 additions & 1 deletion x/auth/vesting/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ var _ sdk.Msg = &MsgCreatePeriodicVestingAccount{}
// NewMsgCreateVestingAccount returns a reference to a new MsgCreateVestingAccount.
//
//nolint:interfacer
func NewMsgCreateVestingAccount(fromAddr, toAddr sdk.AccAddress, amount sdk.Coins, endTime int64, delayed bool) *MsgCreateVestingAccount {
func NewMsgCreateVestingAccount(fromAddr, toAddr sdk.AccAddress, amount sdk.Coins, startTime, endTime int64, delayed bool) *MsgCreateVestingAccount {
return &MsgCreateVestingAccount{
FromAddress: fromAddr.String(),
ToAddress: toAddr.String(),
Amount: amount,
StartTime: startTime,
EndTime: endTime,
Delayed: delayed,
}
Expand Down
119 changes: 79 additions & 40 deletions x/auth/vesting/types/tx.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 43 additions & 15 deletions x/auth/vesting/types/vesting_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,67 +38,95 @@ func (s *VestingAccountTestSuite) SetupTest() {

func TestGetVestedCoinsContVestingAcc(t *testing.T) {
now := tmtime.Now()
endTime := now.Add(24 * time.Hour)
startTime := now.Add(24 * time.Hour)
endTime := startTime.Add(24 * time.Hour)

bacc, origCoins := initBaseAccount()
cva := types.NewContinuousVestingAccount(bacc, origCoins, now.Unix(), endTime.Unix())
cva := types.NewContinuousVestingAccount(bacc, origCoins, startTime.Unix(), endTime.Unix())

// require no coins vested in the very beginning of the vesting schedule
// require no coins vested _before_ the start time of the vesting schedule
vestedCoins := cva.GetVestedCoins(now)
require.Nil(t, vestedCoins)

// require no coins vested _before_ the very beginning of the vesting schedule
vestedCoins = cva.GetVestedCoins(startTime.Add(-1))
require.Nil(t, vestedCoins)

// require all coins vested at the end of the vesting schedule
vestedCoins = cva.GetVestedCoins(endTime)
require.Equal(t, origCoins, vestedCoins)

// require 50% of coins vested
vestedCoins = cva.GetVestedCoins(now.Add(12 * time.Hour))
vestedCoins = cva.GetVestedCoins(startTime.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)

// require 75% of coins vested
vestedCoins = cva.GetVestedCoins(startTime.Add(18 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 750), sdk.NewInt64Coin(stakeDenom, 75)}, vestedCoins)

// require 100% of coins vested
vestedCoins = cva.GetVestedCoins(now.Add(48 * time.Hour))
vestedCoins = cva.GetVestedCoins(endTime)
require.Equal(t, origCoins, vestedCoins)
}

func TestGetVestingCoinsContVestingAcc(t *testing.T) {
now := tmtime.Now()
endTime := now.Add(24 * time.Hour)
startTime := now.Add(24 * time.Hour)
endTime := startTime.Add(24 * time.Hour)

bacc, origCoins := initBaseAccount()
cva := types.NewContinuousVestingAccount(bacc, origCoins, now.Unix(), endTime.Unix())
cva := types.NewContinuousVestingAccount(bacc, origCoins, startTime.Unix(), endTime.Unix())

// require all coins vesting in the beginning of the vesting schedule
// require all coins vesting before the start time of the vesting schedule
vestingCoins := cva.GetVestingCoins(now)
require.Equal(t, origCoins, vestingCoins)

// require all coins vesting right before the start time of the vesting schedule
vestingCoins = cva.GetVestingCoins(startTime.Add(-1))
require.Equal(t, origCoins, vestingCoins)

// require no coins vesting at the end of the vesting schedule
vestingCoins = cva.GetVestingCoins(endTime)
require.Nil(t, vestingCoins)

// require 50% of coins vesting
vestingCoins = cva.GetVestingCoins(now.Add(12 * time.Hour))
// require 50% of coins vesting in the middle between start and end time
vestingCoins = cva.GetVestingCoins(startTime.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestingCoins)

// require 25% of coins vesting after 3/4 of the time between start and end time has passed
vestingCoins = cva.GetVestingCoins(startTime.Add(18 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}, vestingCoins)
}

func TestSpendableCoinsContVestingAcc(t *testing.T) {
now := tmtime.Now()
endTime := now.Add(24 * time.Hour)
startTime := now.Add(24 * time.Hour)
endTime := startTime.Add(24 * time.Hour)

bacc, origCoins := initBaseAccount()
cva := types.NewContinuousVestingAccount(bacc, origCoins, now.Unix(), endTime.Unix())
cva := types.NewContinuousVestingAccount(bacc, origCoins, startTime.Unix(), endTime.Unix())

// require that all original coins are locked at the end of the vesting
// require that all original coins are locked before the beginning of the vesting
// schedule
lockedCoins := cva.LockedCoins(now)
require.Equal(t, origCoins, lockedCoins)

// require that there exist no locked coins in the beginning of the
// require that all original coins are locked at the beginning of the vesting
// schedule
lockedCoins = cva.LockedCoins(startTime)
require.Equal(t, origCoins, lockedCoins)

// require that there exist no locked coins in the end of the vesting schedule
lockedCoins = cva.LockedCoins(endTime)
require.Equal(t, sdk.NewCoins(), lockedCoins)

// require that all vested coins (50%) are spendable
lockedCoins = cva.LockedCoins(now.Add(12 * time.Hour))
lockedCoins = cva.LockedCoins(startTime.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, lockedCoins)

// require 25% of coins vesting after 3/4 of the time between start and end time has passed
lockedCoins = cva.LockedCoins(startTime.Add(18 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}, lockedCoins)
}

func TestTrackDelegationContVestingAcc(t *testing.T) {
Expand Down

0 comments on commit 7babe18

Please sign in to comment.