Skip to content

Commit

Permalink
Merge pull request #193 from agoric-labs/4424-clawback-interface
Browse files Browse the repository at this point in the history
refactor: external interfaces for extended vesting account types
  • Loading branch information
mergify[bot] authored Mar 29, 2022
2 parents 8e6b3cf + d792b56 commit 2807843
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 88 deletions.
File renamed without changes.
48 changes: 48 additions & 0 deletions x/auth/vesting/exported/exported.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,51 @@ type VestingAccount interface {
GetDelegatedFree() sdk.Coins
GetDelegatedVesting() sdk.Coins
}

// AddGrantAction encapsulates the data needed to add a grant to an account.
type AddGrantAction interface {
// AddToAccount adds the grant to the specified account.
// The rawAccount should bypass any account wrappers.
AddToAccount(ctx sdk.Context, rawAccount VestingAccount) error
}

// ClabackAction encapsulates the data needed to perform clawback.
type ClawbackAction interface {
// TakeFromAccount removes unvested tokens from the specified account.
// The rawAccount should bypass any account wrappers.
TakeFromAccount(ctx sdk.Context, rawAccount VestingAccount) error
}

// RewardAction encapsulates the data needed to process rewards distributed to an account.
type RewardAction interface {
// ProcessReward processes the given reward which as been added to the account.
// Returns an error if the account is of the wrong type.
// The rawAccount should bypass any account wrappers.
ProcessReward(ctx sdk.Context, reward sdk.Coins, rawAccount VestingAccount) error
}

// GrantAccount is a VestingAccount which can accept new grants.
type GrantAccount interface {
VestingAccount
// AddGrant adds the specified grant to the account.
AddGrant(ctx sdk.Context, action AddGrantAction) error
}

// ClawbackVestingAccountI is an interface for the methods of a clawback account.
type ClawbackVestingAccountI interface {
GrantAccount

// GetUnlockedOnly returns the sum of all unlocking events up to and including
// the blockTime.
GetUnlockedOnly(blockTime time.Time) sdk.Coins

// GetVestedOnly returns the sum of all vesting events up to and including
// the blockTime.
GetVestedOnly(blockTime time.Time) sdk.Coins

// Clawback performs the clawback described by action.
Clawback(ctx sdk.Context, action ClawbackAction) error

// PostReward preforms post-reward processing described by action.
PostReward(ctx sdk.Context, reward sdk.Coins, action RewardAction) error
}
30 changes: 17 additions & 13 deletions x/auth/vesting/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
)

Expand Down Expand Up @@ -132,7 +133,7 @@ func (s msgServer) CreatePeriodicVestingAccount(goCtx context.Context, msg *type
acc := ak.GetAccount(ctx, to)

if acc != nil {
pva, isPeriodic := acc.(*types.PeriodicVestingAccount)
pva, isPeriodic := acc.(exported.GrantAccount)
switch {
case !msg.Merge && isPeriodic:
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s already exists; consider using --merge", msg.ToAddress)
Expand All @@ -141,7 +142,11 @@ func (s msgServer) CreatePeriodicVestingAccount(goCtx context.Context, msg *type
case msg.Merge && !isPeriodic:
return nil, sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "account %s must be a periodic vesting account", msg.ToAddress)
}
pva.AddGrant(ctx, s.StakingKeeper, msg.GetStartTime(), msg.GetVestingPeriods(), totalCoins)
grantAction := types.NewPeriodicGrantAction(s.StakingKeeper, msg.GetStartTime(), msg.GetVestingPeriods(), totalCoins)
err := pva.AddGrant(ctx, grantAction)
if err != nil {
return nil, err
}
} else {
baseAccount := ak.NewAccountWithAddress(ctx, to)
acc = types.NewPeriodicVestingAccount(baseAccount.(*authtypes.BaseAccount), totalCoins, msg.StartTime, msg.VestingPeriods)
Expand Down Expand Up @@ -233,22 +238,24 @@ func (s msgServer) CreateClawbackVestingAccount(goCtx context.Context, msg *type

madeNewAcc := false
acc := ak.GetAccount(ctx, to)
var va *types.ClawbackVestingAccount
var va exported.ClawbackVestingAccountI

if acc != nil {
var isClawback bool
va, isClawback = acc.(*types.ClawbackVestingAccount)
va, isClawback = acc.(exported.ClawbackVestingAccountI)
switch {
case !msg.Merge && isClawback:
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s already exists; consider using --merge", msg.ToAddress)
case !msg.Merge && !isClawback:
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s already exists", msg.ToAddress)
case msg.Merge && !isClawback:
return nil, sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "account %s must be a clawback vesting account", msg.ToAddress)
case msg.FromAddress != va.FunderAddress:
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account %s can only accept grants from account %s", msg.ToAddress, va.FunderAddress)
}
va.AddGrant(ctx, s.StakingKeeper, msg.GetStartTime(), msg.GetLockupPeriods(), msg.GetVestingPeriods(), vestingCoins)
grantAction := types.NewClawbackGrantAction(msg.FromAddress, s.StakingKeeper, msg.GetStartTime(), msg.GetLockupPeriods(), msg.GetVestingPeriods(), vestingCoins)
err := va.AddGrant(ctx, grantAction)
if err != nil {
return nil, err
}
} else {
baseAccount := ak.NewAccountWithAddress(ctx, to)
va = types.NewClawbackVestingAccount(baseAccount.(*authtypes.BaseAccount), from, vestingCoins, msg.StartTime, msg.LockupPeriods, msg.VestingPeriods)
Expand Down Expand Up @@ -320,16 +327,13 @@ func (s msgServer) Clawback(goCtx context.Context, msg *types.MsgClawback) (*typ
if acc == nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "account %s does not exist", msg.Address)
}
va, ok := acc.(*types.ClawbackVestingAccount)
va, ok := acc.(exported.ClawbackVestingAccountI)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "account not subject to clawback: %s", msg.Address)
}

if va.FunderAddress != msg.GetFunderAddress() {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "clawback can only be requested by original funder %s", va.FunderAddress)
}

err = va.Clawback(ctx, dest, ak, bk, s.StakingKeeper)
clawbackAction := types.NewClawbackAction(funder, dest, ak, bk, s.StakingKeeper)
err = va.Clawback(ctx, clawbackAction)
if err != nil {
return nil, err
}
Expand Down
16 changes: 9 additions & 7 deletions x/auth/vesting/types/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,36 @@ package types

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
)

type distributionHooks struct {
accountKeeper AccountKeeper
bankKeeper BankKeeper
stakingKeeper StakingKeeper
rewardAction exported.RewardAction
}

var _ DistributionHooks = distributionHooks{}

func NewDistributionHooks(ak AccountKeeper, bk BankKeeper, sk StakingKeeper) DistributionHooks {
return distributionHooks{
accountKeeper: ak,
bankKeeper: bk,
stakingKeeper: sk,
rewardAction: NewClawbackRewardAction(ak, bk, sk),
}
}

func (dh distributionHooks) AllowWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) bool {
acc := dh.accountKeeper.GetAccount(ctx, delAddr)
_, isClawback := acc.(*ClawbackVestingAccount)
_, isClawback := acc.(exported.ClawbackVestingAccountI)
return !isClawback
}

func (dh distributionHooks) AfterDelegationReward(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress, reward sdk.Coins) {
acc := dh.accountKeeper.GetAccount(ctx, delAddr)
cva, isClawback := acc.(*ClawbackVestingAccount)
cva, isClawback := acc.(exported.ClawbackVestingAccountI)
if isClawback {
cva.PostReward(ctx, reward, dh.accountKeeper, dh.bankKeeper, dh.stakingKeeper)
err := cva.PostReward(ctx, reward, dh.rewardAction)
if err != nil {
panic(err)
}
}
}
Loading

0 comments on commit 2807843

Please sign in to comment.