Skip to content

Commit b7d1d69

Browse files
committed
WIP Allow undelegation of more coins than delegated; Add more validity checks
1 parent da12165 commit b7d1d69

File tree

5 files changed

+74
-21
lines changed

5 files changed

+74
-21
lines changed

docs/spec/auth/05_vesting.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,12 @@ func DelegateCoins(t Time, from Account, amount Coins) {
251251
### Undelegating
252252

253253
For a vesting account attempting to undelegate `D` coins, the following is performed:
254+
NOTE: `DV < D` and `(DV + DF) < D` may be possible due to quirks in the rounding of
255+
delegation/undelegation logic.
254256

255-
1. Verify `(DV + DF) >= D > 0` (this is simply a sanity check)
257+
1. Verify `D > 0`
256258
2. Compute `X := min(DF, D)` (portion of `D` that should become free, prioritizing free coins)
257-
3. Compute `Y := D - X` (portion of `D` that should remain vesting)
259+
3. Compute `Y := min(DV, D - X)` (portion of `D` that should remain vesting)
258260
4. Set `DF -= X`
259261
5. Set `DV -= Y`
260262
6. Set `BC += D`

types/coin.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,6 @@ func (coins Coins) AmountOf(denom string) Int {
389389

390390
// IsAllPositive returns true if there is at least one coin and all currencies
391391
// have a positive value.
392-
//
393-
// TODO: Remove once unsigned integers are used.
394392
func (coins Coins) IsAllPositive() bool {
395393
if len(coins) == 0 {
396394
return false

x/auth/account.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,13 @@ func (bva *BaseVestingAccount) TrackUndelegation(amount sdk.Coins) {
295295
panic("undelegation attempt with zero coins")
296296
}
297297
delegatedFree := bva.DelegatedFree.AmountOf(coin.Denom)
298+
delegatedVesting := bva.DelegatedVesting.AmountOf(coin.Denom)
298299

299300
// compute x and y per the specification, where:
300301
// X := min(DF, D)
301-
// Y := D - X
302+
// Y := min(D - X, DV)
302303
x := sdk.MinInt(delegatedFree, coin.Amount)
303-
y := coin.Amount.Sub(x)
304+
y := sdk.MinInt(delegatedVesting, coin.Amount.Sub(x))
304305

305306
if !x.IsZero() {
306307
xCoin := sdk.NewCoin(coin.Denom, x)

x/bank/keeper.go

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ func NewBaseKeeper(ak auth.AccountKeeper,
4747
}
4848

4949
// SetCoins sets the coins at the addr.
50-
func (keeper BaseKeeper) SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error {
50+
func (keeper BaseKeeper) SetCoins(
51+
ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins,
52+
) sdk.Error {
53+
54+
if !amt.IsValid() {
55+
return sdk.ErrInvalidCoins(amt.String())
56+
}
5157
return setCoins(ctx, keeper.ak, addr, amt)
5258
}
5359

@@ -56,6 +62,9 @@ func (keeper BaseKeeper) SubtractCoins(
5662
ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins,
5763
) (sdk.Coins, sdk.Tags, sdk.Error) {
5864

65+
if !amt.IsValid() {
66+
return nil, nil, sdk.ErrInvalidCoins(amt.String())
67+
}
5968
return subtractCoins(ctx, keeper.ak, addr, amt)
6069
}
6170

@@ -64,6 +73,9 @@ func (keeper BaseKeeper) AddCoins(
6473
ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins,
6574
) (sdk.Coins, sdk.Tags, sdk.Error) {
6675

76+
if !amt.IsValid() {
77+
return nil, nil, sdk.ErrInvalidCoins(amt.String())
78+
}
6779
return addCoins(ctx, keeper.ak, addr, amt)
6880
}
6981

@@ -78,14 +90,27 @@ func (keeper BaseKeeper) InputOutputCoins(
7890
// DelegateCoins performs delegation by deducting amt coins from an account with
7991
// address addr. For vesting accounts, delegations amounts are tracked for both
8092
// vesting and vested coins.
81-
func (keeper BaseKeeper) DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
93+
func (keeper BaseKeeper) DelegateCoins(
94+
ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins,
95+
) (sdk.Tags, sdk.Error) {
96+
97+
if !amt.IsValid() {
98+
return nil, sdk.ErrInvalidCoins(amt.String())
99+
}
82100
return delegateCoins(ctx, keeper.ak, addr, amt)
83101
}
84102

85103
// UndelegateCoins performs undelegation by crediting amt coins to an account with
86104
// address addr. For vesting accounts, undelegation amounts are tracked for both
87105
// vesting and vested coins.
88-
func (keeper BaseKeeper) UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
106+
// If any of the undelegation amounts are negative, an error is returned.
107+
func (keeper BaseKeeper) UndelegateCoins(
108+
ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins,
109+
) (sdk.Tags, sdk.Error) {
110+
111+
if !amt.IsValid() {
112+
return nil, sdk.ErrInvalidCoins(amt.String())
113+
}
89114
return undelegateCoins(ctx, keeper.ak, addr, amt)
90115
}
91116

@@ -126,6 +151,10 @@ func NewBaseSendKeeper(ak auth.AccountKeeper,
126151
func (keeper BaseSendKeeper) SendCoins(
127152
ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins,
128153
) (sdk.Tags, sdk.Error) {
154+
155+
if !amt.IsValid() {
156+
return nil, sdk.ErrInvalidCoins(amt.String())
157+
}
129158
return sendCoins(ctx, keeper.ak, fromAddr, toAddr, amt)
130159
}
131160

@@ -188,6 +217,9 @@ func getCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress) sdk.C
188217
}
189218

190219
func setCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) sdk.Error {
220+
if !amt.IsValid() {
221+
return sdk.ErrInvalidCoins(amt.String())
222+
}
191223
acc := am.GetAccount(ctx, addr)
192224
if acc == nil {
193225
acc = am.NewAccountWithAddress(ctx, addr)
@@ -218,6 +250,11 @@ func setAccount(ctx sdk.Context, ak auth.AccountKeeper, acc auth.Account) {
218250
//
219251
// CONTRACT: If the account is a vesting account, the amount has to be spendable.
220252
func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
253+
254+
if !amt.IsValid() {
255+
return nil, nil, sdk.ErrInvalidCoins(amt.String())
256+
}
257+
221258
oldCoins, spendableCoins := sdk.Coins{}, sdk.Coins{}
222259

223260
acc := getAccount(ctx, ak, addr)
@@ -244,6 +281,11 @@ func subtractCoins(ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress,
244281

245282
// AddCoins adds amt to the coins at the addr.
246283
func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) {
284+
285+
if !amt.IsValid() {
286+
return nil, nil, sdk.ErrInvalidCoins(amt.String())
287+
}
288+
247289
oldCoins := getCoins(ctx, am, addr)
248290
newCoins := oldCoins.Add(amt)
249291

@@ -260,9 +302,9 @@ func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s
260302
}
261303

262304
// SendCoins moves coins from one account to another
305+
// Returns ErrInvalidCoins if amt is invalid.
263306
func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
264307
// Safety check ensuring that when sending coins the keeper must maintain the
265-
// supply invariant.
266308
if !amt.IsValid() {
267309
return nil, sdk.ErrInvalidCoins(amt.String())
268310
}
@@ -284,7 +326,7 @@ func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress,
284326
// NOTE: Make sure to revert state changes from tx on error
285327
func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) {
286328
// Safety check ensuring that when sending coins the keeper must maintain the
287-
// supply invariant.
329+
// Check supply invariant and validity of Coins.
288330
if err := ValidateInputsOutputs(inputs, outputs); err != nil {
289331
return nil, err
290332
}
@@ -314,6 +356,10 @@ func delegateCoins(
314356
ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins,
315357
) (sdk.Tags, sdk.Error) {
316358

359+
if !amt.IsValid() {
360+
return nil, sdk.ErrInvalidCoins(amt.String())
361+
}
362+
317363
acc := getAccount(ctx, ak, addr)
318364
if acc == nil {
319365
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr))
@@ -344,6 +390,10 @@ func undelegateCoins(
344390
ctx sdk.Context, ak auth.AccountKeeper, addr sdk.AccAddress, amt sdk.Coins,
345391
) (sdk.Tags, sdk.Error) {
346392

393+
if !amt.IsValid() {
394+
return nil, sdk.ErrInvalidCoins(amt.String())
395+
}
396+
347397
acc := getAccount(ctx, ak, addr)
348398
if acc == nil {
349399
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", addr))
@@ -361,22 +411,24 @@ func undelegateCoins(
361411
), nil
362412
}
363413

364-
func trackDelegation(acc auth.Account, blockTime time.Time, amount sdk.Coins) error {
414+
// CONTRACT: assumes that amt is valid.
415+
func trackDelegation(acc auth.Account, blockTime time.Time, amt sdk.Coins) error {
365416
vacc, ok := acc.(auth.VestingAccount)
366417
if ok {
367-
vacc.TrackDelegation(blockTime, amount)
418+
vacc.TrackDelegation(blockTime, amt)
368419
return nil
369420
}
370421

371-
return acc.SetCoins(acc.GetCoins().Sub(amount))
422+
return acc.SetCoins(acc.GetCoins().Sub(amt))
372423
}
373424

374-
func trackUndelegation(acc auth.Account, amount sdk.Coins) error {
425+
// CONTRACT: assumes that amt is valid.
426+
func trackUndelegation(acc auth.Account, amt sdk.Coins) error {
375427
vacc, ok := acc.(auth.VestingAccount)
376428
if ok {
377-
vacc.TrackUndelegation(amount)
429+
vacc.TrackUndelegation(amt)
378430
return nil
379431
}
380432

381-
return acc.SetCoins(acc.GetCoins().Add(amount))
433+
return acc.SetCoins(acc.GetCoins().Add(amt))
382434
}

x/bank/msgs.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ func (msg MsgSend) ValidateBasic() sdk.Error {
3535
if msg.ToAddress.Empty() {
3636
return sdk.ErrInvalidAddress("missing recipient address")
3737
}
38-
if !msg.Amount.IsAllPositive() {
39-
return sdk.ErrInsufficientCoins("send amount must be positive")
38+
if !msg.Amount.IsValid() {
39+
return sdk.ErrInvalidCoins(msg.Amount.String())
4040
}
4141
return nil
4242
}
@@ -112,7 +112,7 @@ func (in Input) ValidateBasic() sdk.Error {
112112
if !in.Coins.IsValid() {
113113
return sdk.ErrInvalidCoins(in.Coins.String())
114114
}
115-
if !in.Coins.IsAllPositive() {
115+
if !in.Coins.IsValid() {
116116
return sdk.ErrInvalidCoins(in.Coins.String())
117117
}
118118
return nil
@@ -140,7 +140,7 @@ func (out Output) ValidateBasic() sdk.Error {
140140
if !out.Coins.IsValid() {
141141
return sdk.ErrInvalidCoins(out.Coins.String())
142142
}
143-
if !out.Coins.IsAllPositive() {
143+
if !out.Coins.IsValid() {
144144
return sdk.ErrInvalidCoins(out.Coins.String())
145145
}
146146
return nil

0 commit comments

Comments
 (0)