/
account.go
285 lines (250 loc) · 9.79 KB
/
account.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package keeper
import (
"time"
"github.com/Agoric/agoric-sdk/golang/cosmos/x/lien/types"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/gogo/protobuf/proto"
)
// omniAccount is the full expected interface of non-module accounts.
// In addition to the methods declared in authtypes.AccountI, additional
// expectations are enforced dynamically through casting and reflection:
//
// - non-module accounts are expected to obey the GenesisAccount interface,
// i.e. to have a Validate() method;
//
// - UnpackInterfacesMessage is needed for unpacking accounts embedded
// in an Any message;
//
// - MarshalYAML() is used for String rendering;
//
// - protobuf Messages are expected to implement a number of "XXX"-prefixed
// methods not visible in the Message interface.
//
// Declaring the expected methods here allows them to implicitly fall through
// to an embedded omniAccount.
//
// Note that this interface will have to adapt to updated expectations in
// the cosmos-sdk or protobuf library.
type omniAccount interface {
authtypes.GenesisAccount
codectypes.UnpackInterfacesMessage
MarshalYAML() (interface{}, error)
XXX_DiscardUnknown()
XXX_Marshal([]byte, bool) ([]byte, error)
XXX_Merge(proto.Message)
XXX_Size() int
XXX_Unmarshal([]byte) error
}
// omniVestingAccount is an omniAccount plus vesting methods.
type omniVestingAccount interface {
omniAccount
vestexported.VestingAccount
}
// omniGrantAccount is an omniAccount plus GrantAccount methods.
type omniGrantAccount interface {
omniAccount
vestexported.GrantAccount
}
// omniClawbackAccount is an omniAccount plus Clawback methods.
type omniClawbackAccount interface {
omniAccount
vestexported.ClawbackVestingAccountI
}
// unlockedVestingAccount extends an omniAccount to be a vesting account
// by simulating an original vesting amount of zero. It inherets the marshal
// behavior of the wrapped account. It uses the lien structure to track
// delegation.
type unlockedVestingAccount struct {
omniAccount
lien *types.Lien
}
var _ omniVestingAccount = unlockedVestingAccount{}
// LockedCoins implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) LockedCoins(ctx sdk.Context) sdk.Coins {
return sdk.NewCoins()
}
// TrackDelegation implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) TrackDelegation(blockTime time.Time, balance, amount sdk.Coins) {
if !amount.IsAllLTE(balance) {
panic("insufficient funds")
}
uva.lien.Delegated = uva.lien.Delegated.Add(amount...)
}
// TrackUndelegation implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) TrackUndelegation(amount sdk.Coins) {
// max(delegated - amount, 0) == delegated - min(delegated, amount)
uva.lien.Delegated = uva.lien.Delegated.Sub(uva.lien.Delegated.Min(amount))
}
// GetVestedCoins implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins {
return sdk.NewCoins()
}
// GetVestingCoins implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins {
return sdk.NewCoins()
}
// GetStartTime implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetStartTime() int64 {
return 0
}
// GetEndTime implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetEndTime() int64 {
return 0
}
// GetOriginalVesting implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetOriginalVesting() sdk.Coins {
return sdk.NewCoins()
}
//GetDelegatedFree implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetDelegatedFree() sdk.Coins {
return uva.lien.Delegated
}
// GetDelegatedVesting implements the vestexported.VestingAccount interface.
func (uva unlockedVestingAccount) GetDelegatedVesting() sdk.Coins {
return sdk.NewCoins()
}
// XXX_MessageName implements the method used by the gogoproto extension
// for grpc-gateway compatibility.
func (uva unlockedVestingAccount) XXX_MessageName() string {
return proto.MessageName(uva.omniAccount)
}
// fakeGrantAccount extends an omniVestingAccount to be a GrantAccount.
type fakeGrantAccount struct {
omniVestingAccount
}
var _ omniGrantAccount = fakeGrantAccount{}
// AddGrant implements the vestexported.GrantAccount interface.
func (fga fakeGrantAccount) AddGrant(ctx sdk.Context, grantAction vestexported.AddGrantAction) error {
return grantAction.AddToAccount(ctx, fga.omniVestingAccount) // XXX or just fail here
}
// fakeClawbackAccount extends an omniGrantAccount to be a clawback account.
type fakeClawbackAccount struct {
omniGrantAccount
}
var _ omniClawbackAccount = fakeClawbackAccount{}
// GetUnlockedOnly implements the vestexported.ClawbackVestingAccountI interface.
func (fca fakeClawbackAccount) GetUnlockedOnly(blockTime time.Time) sdk.Coins {
return fca.omniGrantAccount.GetVestedCoins(blockTime)
}
// GetVestedOnly implements the vestexported.ClawbackVestingAccountI interface.
func (fca fakeClawbackAccount) GetVestedOnly(blockTime time.Time) sdk.Coins {
return fca.omniGrantAccount.GetOriginalVesting()
}
// Clawback implements the vestexported.ClawbackVestingAccountI interface.
func (fca fakeClawbackAccount) Clawback(ctx sdk.Context, action vestexported.ClawbackAction) error {
return action.TakeFromAccount(ctx, fca.omniGrantAccount) // XXX or just fail here
}
// PostReward implements the vestexported.ClawbackVestingAccountI interface.
func (fca fakeClawbackAccount) PostReward(ctx sdk.Context, reward sdk.Coins, action vestexported.RewardAction) error {
// perform no action here, but lienAccount can divert lien
return nil
}
// LienAccount wraps an omniClawbackAccount to implement lien encumbrance.
// The LockedCoins() method is the maximum of the coins locked for
// liens, and the coins locked in the underlying VestingAccount.
// It inherits the marshaling behavior of the wrapped account.
// In particular, the Lien account must be passed by pointer because of
// expectations from the proto library.
type LienAccount struct {
omniClawbackAccount
lienKeeper Keeper
lien types.Lien
}
var _ omniClawbackAccount = &LienAccount{}
// LockedCoins implements the method from the VestingAccount interface.
// It takes the maximum of the coins locked for liens and the coins
// locked in the wrapped VestingAccount, but limited to the current
// account balance.
func (la *LienAccount) LockedCoins(ctx sdk.Context) sdk.Coins {
wrappedLocked := la.omniClawbackAccount.LockedCoins(ctx)
lienedLocked := la.LienedLockedCoins(ctx)
encumbered := wrappedLocked.Max(lienedLocked)
balance := la.lienKeeper.GetAllBalances(ctx, la.GetAddress())
return balance.Min(encumbered)
}
// LienedLockedCoins returns the coins which are locked on the lien/vesting dimension,
// which is the raw unvested amount, plus the raw liened amount, less the net amount
// delegated.
func (la *LienAccount) LienedLockedCoins(ctx sdk.Context) sdk.Coins {
delegated := la.GetDelegatedFree().Add(la.GetDelegatedVesting()...)
liened := la.lien.Coins
acc := la.omniClawbackAccount.(authtypes.AccountI)
if clawback, ok := acc.(vestexported.ClawbackVestingAccountI); ok {
liened = liened.Add(clawback.GetOriginalVesting().Sub(clawback.GetVestedOnly(ctx.BlockTime()))...)
}
// Since coins can't go negative, even transiently, use the
// identity A + B = max(A, B) + min(A, B)
// max(0, A - B) = max(B, A) - B = A - min(A, B)
return liened.Sub(liened.Min(delegated))
}
// XXX_MessageName provides the message name for JSON serialization.
// See proto.MessageName().
func (la *LienAccount) XXX_MessageName() string {
// Use the embedded account's message name for JSON serialization.
return proto.MessageName(la.omniClawbackAccount)
}
// NewAccountWrapper returns an AccountWrapper which wraps any account
// to be a LienAccount associated with the given Keeper, then unwraps
// any layers that the Wrap added.
func NewAccountWrapper(lk Keeper) types.AccountWrapper {
return types.AccountWrapper{
Wrap: func(ctx sdk.Context, acc authtypes.AccountI) authtypes.AccountI {
if acc == nil {
return nil
}
omni, ok := acc.(omniAccount)
if !ok {
// don't wrap non-omni accounts, e.g. module accounts
return acc
}
addr := acc.GetAddress()
lien := lk.GetLien(ctx, addr)
if lien.Coins.IsZero() {
// don't wrap unless there is a lien
return acc
}
lienAcc := LienAccount{
lienKeeper: lk,
lien: lien,
}
vestingAcc, ok := omni.(omniVestingAccount)
if !ok {
// Make non-vesting accounts appear to be vesting
// (need to give a pointer to the Lien - this is why the LienAccount
// holding it is created first).
vestingAcc = unlockedVestingAccount{omniAccount: omni, lien: &lienAcc.lien}
}
grantAcc, ok := vestingAcc.(omniGrantAccount)
if !ok {
// Make other vesting accounts appear to be GrantAccounts.
grantAcc = fakeGrantAccount{omniVestingAccount: vestingAcc}
}
clawbackAcc, ok := grantAcc.(omniClawbackAccount)
if !ok {
// Make GrantAccounts appear to be ClawbackAccounts.
clawbackAcc = fakeClawbackAccount{omniGrantAccount: grantAcc}
}
lienAcc.omniClawbackAccount = clawbackAcc
return &lienAcc
},
Unwrap: func(ctx sdk.Context, acc authtypes.AccountI) authtypes.AccountI {
if la, ok := acc.(*LienAccount); ok {
lk.SetLien(ctx, la.GetAddress(), la.lien)
acc = la.omniClawbackAccount
}
if fca, ok := acc.(fakeClawbackAccount); ok {
acc = fca.omniGrantAccount
}
if fga, ok := acc.(fakeGrantAccount); ok {
acc = fga.omniVestingAccount
}
if uva, ok := acc.(unlockedVestingAccount); ok {
acc = uva.omniAccount
}
return acc
},
}
}