/
marker.go
491 lines (430 loc) · 15.6 KB
/
marker.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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
package types
import (
"errors"
"fmt"
"strings"
proto "github.com/gogo/protobuf/proto"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)
var (
// ensure the MarkerAccount correctly extends the following interfaces
_ authtypes.AccountI = (*MarkerAccount)(nil)
_ authtypes.GenesisAccount = (*MarkerAccount)(nil)
_ MarkerAccountI = (*MarkerAccount)(nil)
)
// MarkerAccountI defines the required method interface for a marker account
type MarkerAccountI interface {
proto.Message
authtypes.AccountI
Clone() *MarkerAccount
Validate() error
GetDenom() string
GetManager() sdk.AccAddress
GetMarkerType() MarkerType
GetStatus() MarkerStatus
SetStatus(MarkerStatus) error
GetSupply() sdk.Coin
SetSupply(sdk.Coin) error
HasFixedSupply() bool
GrantAccess(AccessGrantI) error
RevokeAccess(sdk.AccAddress) error
GetAccessList() []AccessGrant
HasAccess(string, Access) bool
ValidateHasAccess(string, Access) error
AddressHasAccess(sdk.AccAddress, Access) bool
ValidateAddressHasAccess(sdk.AccAddress, Access) error
AddressListForPermission(Access) []sdk.AccAddress
HasGovernanceEnabled() bool
AllowsForcedTransfer() bool
SetAllowForcedTransfer(bool)
GetRequiredAttributes() []string
SetRequiredAttributes([]string)
}
// NewEmptyMarkerAccount creates a new empty marker account in a Proposed state
func NewEmptyMarkerAccount(denom, manager string, grants []AccessGrant) *MarkerAccount {
baseAcc := authtypes.NewBaseAccountWithAddress(MustGetMarkerAddress(denom))
return &MarkerAccount{
BaseAccount: baseAcc,
AccessControl: grants,
Denom: denom,
Manager: manager,
Supply: sdk.ZeroInt(),
Status: StatusProposed,
MarkerType: MarkerType_Coin,
SupplyFixed: true,
AllowGovernanceControl: true,
AllowForcedTransfer: false,
}
}
// NewMarkerAccount creates a marker account initialized over a given base account.
func NewMarkerAccount(
baseAcc *authtypes.BaseAccount,
totalSupply sdk.Coin,
manager sdk.AccAddress,
accessControls []AccessGrant,
status MarkerStatus,
markerType MarkerType,
supplyFixed, allowGovernanceControl, allowForcedTransfer bool,
requiredAttributes []string,
) *MarkerAccount {
// clear marker manager for active or later status accounts.
if status >= StatusActive {
manager = sdk.AccAddress{}
}
return &MarkerAccount{
BaseAccount: baseAcc,
Denom: totalSupply.Denom,
Manager: manager.String(),
Supply: totalSupply.Amount,
AccessControl: accessControls,
Status: status,
MarkerType: markerType,
SupplyFixed: supplyFixed,
AllowGovernanceControl: allowGovernanceControl,
AllowForcedTransfer: allowForcedTransfer,
RequiredAttributes: requiredAttributes,
}
}
// Clone makes a MarkerAccount instance copy
func (ma MarkerAccount) Clone() *MarkerAccount {
return proto.Clone(&ma).(*MarkerAccount)
}
// GetDenom the denomination of the coin associated with this marker
func (ma MarkerAccount) GetDenom() string { return ma.Denom }
// HasFixedSupply return true if the total supply for the marker is considered "fixed" and should be controlled with an
// invariant check
func (ma MarkerAccount) HasFixedSupply() bool { return ma.SupplyFixed }
// HasGovernanceEnabled returns true if this marker allows governance proposals to control this marker
func (ma MarkerAccount) HasGovernanceEnabled() bool { return ma.AllowGovernanceControl }
// AllowsForcedTransfer returns true if force transfer is allowed for this marker.
func (ma MarkerAccount) AllowsForcedTransfer() bool {
return ma.AllowForcedTransfer
}
func (ma *MarkerAccount) SetAllowForcedTransfer(allowForcedTransfer bool) {
ma.AllowForcedTransfer = allowForcedTransfer
}
// HasAccess returns true if the provided address has been assigned the provided
// role within the current MarkerAccount AccessControl
func (ma *MarkerAccount) HasAccess(addr string, role Access) bool {
for _, g := range ma.AccessControl {
if g.Address == addr && g.HasAccess(role) {
return true
}
}
return false
}
// ValidateHasAccess returns an error if the provided address does not have the given role in this marker.
func (ma *MarkerAccount) ValidateHasAccess(addr string, role Access) error {
if !ma.HasAccess(addr, role) {
return fmt.Errorf("%s does not have %s on %s marker (%s)", addr, role, ma.GetDenom(), ma.GetAddress())
}
return nil
}
// AddressHasAccess returns true if the provided address has been assigned the provided
// role within the current MarkerAccount AccessControl
func (ma *MarkerAccount) AddressHasAccess(addr sdk.AccAddress, role Access) bool {
return ma.HasAccess(addr.String(), role)
}
// ValidateAddressHasAccess returns an error if the provided address does not have the given role in this marker.
func (ma *MarkerAccount) ValidateAddressHasAccess(addr sdk.AccAddress, role Access) error {
return ma.ValidateHasAccess(addr.String(), role)
}
// AddressListForPermission returns a list of all addresses with the provided rule within the
// current MarkerAccount AccessControl list
func (ma *MarkerAccount) AddressListForPermission(role Access) []sdk.AccAddress {
var addressList []sdk.AccAddress
for _, g := range ma.AccessControl {
if g.HasAccess(role) {
addressList = append(addressList, g.GetAddress())
}
}
return addressList
}
// Validate performs minimal sanity checking over the current MarkerAccount instance
func (ma MarkerAccount) Validate() error {
if !ValidMarkerStatus(ma.Status) {
return fmt.Errorf("invalid marker status")
}
// unlikely as this is set using Coin which prohibits negative values.
if ma.Supply.IsNegative() {
return fmt.Errorf("total supply must be greater than or equal to zero")
}
if ma.Status < StatusActive && ma.Manager == "" && len(ma.AddressListForPermission(Access_Admin)) == 0 {
return fmt.Errorf("a manager is required if there are no accounts with ACCESS_ADMIN and marker is not ACTIVE")
}
if ma.Status == StatusFinalized && len(ma.AddressListForPermission(Access_Mint)) == 0 && ma.Supply.IsZero() {
return fmt.Errorf("cannot create a marker with zero total supply and no authorization for minting more")
}
// unlikely as this is set using a Coin which prohibits this value.
if strings.TrimSpace(ma.Denom) == "" {
return fmt.Errorf("marker denom cannot be empty")
}
markerAddress, err := MarkerAddress(ma.Denom)
if err != nil {
return fmt.Errorf("marker denom is invalid: %w", err)
}
if err := ValidateIbcDenom(ma); err != nil {
return fmt.Errorf("invalid ibc denom configuration: %w", err)
}
if !ma.BaseAccount.GetAddress().Equals(markerAddress) {
return fmt.Errorf("address %s cannot be derived from the marker denom '%s'", ma.Address, ma.Denom)
}
if err := ValidateGrantsForMarkerType(ma.MarkerType, ma.AccessControl...); err != nil {
return fmt.Errorf("invalid access privileges granted: %w", err)
}
selfGrant := GrantsForAddress(ma.GetAddress(), ma.AccessControl...).GetAccessList()
if len(selfGrant) > 0 {
return fmt.Errorf("permissions cannot be granted to '%s' marker account: %v", ma.Denom, selfGrant)
}
if ma.Manager == ma.GetAddress().String() {
return fmt.Errorf("marker can not be self managed")
}
if ma.AllowForcedTransfer && ma.MarkerType != MarkerType_RestrictedCoin {
return fmt.Errorf("forced transfers can only be allowed on restricted markers")
}
return ma.BaseAccount.Validate()
}
// ValidateIbcDenom if denom is ibc it that validates supply is not fixed and Mint/Burn is not allowed
func ValidateIbcDenom(ma MarkerAccount) error {
if !strings.HasPrefix(ma.Denom, "ibc/") {
return nil
}
if ma.SupplyFixed {
return errors.New("fixed supply is not supported for ibc marker")
}
for _, grant := range ma.AccessControl {
for _, access := range grant.Permissions {
if access.IsOneOf(Access_Burn, Access_Mint) {
return fmt.Errorf("%v is not supported for ibc marker", access)
}
}
}
return nil
}
// ValidateGrantsForMarkerType checks a collection of grants and returns any errors encountered or nil
func ValidateGrantsForMarkerType(markerType MarkerType, grants ...AccessGrant) error {
for _, grant := range grants {
for _, access := range grant.Permissions {
switch markerType {
case MarkerType_Coin:
{
if !access.IsOneOf(Access_Admin, Access_Burn, Access_Delete, Access_Deposit, Access_Mint, Access_Withdraw) {
return fmt.Errorf("%v is not supported for marker type %v", access, markerType)
}
}
// Restricted Coins also support Transfer access
case MarkerType_RestrictedCoin:
{
if !access.IsOneOf(Access_Admin, Access_Burn, Access_Delete, Access_Deposit, Access_Mint, Access_Withdraw, Access_Transfer, Access_ForceTransfer) {
return fmt.Errorf("%v is not supported for marker type %v", access, markerType)
}
}
default:
return fmt.Errorf("cannot validate access grants for unsupported marker type %s", markerType.String())
}
}
}
return ValidateGrants(grants...)
}
// ValidateRequiredAttributes checks that required attributes are of the correct format
func ValidateRequiredAttributes(requiredAttributes []string) error {
for _, attr := range requiredAttributes {
if strings.TrimSpace(attr) == "" {
return fmt.Errorf("invalid name: empty")
}
}
return nil
}
func (ma *MarkerAccount) GetRequiredAttributes() []string {
return ma.RequiredAttributes
}
func (ma *MarkerAccount) SetRequiredAttributes(requiredAttributes []string) {
ma.RequiredAttributes = requiredAttributes
}
// GetPubKey implements authtypes.Account (but there are no public keys associated with the account for signing)
func (ma MarkerAccount) GetPubKey() cryptotypes.PubKey {
return nil
}
// SetPubKey implements authtypes.Account (but there are no public keys associated with the account for signing)
func (ma *MarkerAccount) SetPubKey(_ cryptotypes.PubKey) error {
return fmt.Errorf("not supported for marker accounts")
}
// SetSequence implements authtypes.Account (but you can't set a sequence as you can't sign tx for this account)
func (ma *MarkerAccount) SetSequence(_ uint64) error {
return fmt.Errorf("not supported for marker accounts")
}
// GetStatus returns the status of the marker account.
func (ma MarkerAccount) GetStatus() MarkerStatus {
return ma.Status
}
// SetStatus sets the status of the marker to the provided value.
func (ma *MarkerAccount) SetStatus(status MarkerStatus) error {
if status == StatusUndefined {
return fmt.Errorf("error invalid marker status %s", status)
}
if status == StatusActive {
// When activated the manager property is no longer valid so clear it
ma.Manager = ""
}
ma.Status = status
return nil
}
// GetMarkerType returns the type of the marker account.
func (ma MarkerAccount) GetMarkerType() MarkerType {
return ma.MarkerType
}
// GetAddress returns the address of the marker account.
func (ma MarkerAccount) GetAddress() sdk.AccAddress {
addr, err := sdk.AccAddressFromBech32(ma.Address)
if err != nil {
panic(err)
}
return addr
}
// GetManager returns the address of the account that is responsible for the proposed marker.
func (ma MarkerAccount) GetManager() sdk.AccAddress {
// manage is not required, return an empty address if not set.
if ma.Manager == "" {
return sdk.AccAddress{}
}
// if the manager is set we much return it as an address
addr, err := sdk.AccAddressFromBech32(ma.Manager)
if err != nil {
panic(err)
}
return addr
}
// SetManager sets the manager/owner address for proposed marker accounts
func (ma *MarkerAccount) SetManager(manager sdk.AccAddress) error {
if !manager.Empty() && ma.Status != StatusProposed {
return fmt.Errorf("manager address is only valid for proposed markers, use access grants instead")
}
if err := sdk.VerifyAddressFormat(manager); err != nil {
return err
}
ma.Manager = manager.String()
return nil
}
// SetSupply sets the total supply amount to track
func (ma *MarkerAccount) SetSupply(total sdk.Coin) error {
if total.Denom != ma.Denom {
return fmt.Errorf("supply coin denom must match marker denom")
}
ma.Supply = total.Amount
return nil
}
// GetSupply implements authtypes.Account
func (ma MarkerAccount) GetSupply() sdk.Coin {
return sdk.NewCoin(ma.Denom, ma.Supply)
}
// GrantAccess appends the access grant to the marker account.
func (ma *MarkerAccount) GrantAccess(access AccessGrantI) error {
if err := access.Validate(); err != nil {
return err
}
// Find any existing permissions and append specified permissions
for _, ac := range ma.AccessControl {
if ac.GetAddress().Equals(access.GetAddress()) {
if err := access.MergeAdd(*NewAccessGrant(ac.GetAddress(), ac.GetAccessList())); err != nil {
return err
}
}
}
// Revoke existing (no errors from this as we have validated above)
if err := ma.RevokeAccess(access.GetAddress()); err != nil {
return err
}
// Append the new record
ma.AccessControl = append(ma.AccessControl, *NewAccessGrant(access.GetAddress(), access.GetAccessList()))
return nil
}
// RevokeAccess removes any AccessGrant for the given address on this marker.
func (ma *MarkerAccount) RevokeAccess(addr sdk.AccAddress) error {
if err := sdk.VerifyAddressFormat(addr); err != nil {
return fmt.Errorf("can not revoke access for invalid address")
}
var accessList []AccessGrant
for _, ac := range ma.AccessControl {
if ac.Address != addr.String() {
accessList = append(accessList, ac)
}
}
ma.AccessControl = accessList
return nil
}
// GetAccessList returns the full access list for the marker
func (ma *MarkerAccount) GetAccessList() []AccessGrant {
return ma.AccessControl
}
// MarkerTypeFromString returns a MarkerType from a string. It returns an error
// if the string is invalid.
func MarkerTypeFromString(str string) (MarkerType, error) {
switch strings.ToLower(str) {
case "coin":
return MarkerType_Coin, nil
case "restricted":
fallthrough
case "restrictedcoin":
return MarkerType_RestrictedCoin, nil
default:
if val, ok := MarkerType_value[str]; ok {
return MarkerType(val), nil
}
}
return MarkerType_Unknown, fmt.Errorf("'%s' is not a valid marker status", str)
}
// AddToRequiredAttributes add new attributes to current list, errors if attribute already exists
func AddToRequiredAttributes(currentAttrs []string, addAttrs []string) ([]string, error) {
for _, aa := range addAttrs {
for _, ca := range currentAttrs {
if aa == ca {
return nil, fmt.Errorf("attribute %q is already required", aa)
}
}
currentAttrs = append(currentAttrs, aa)
}
return currentAttrs, nil
}
// RemoveFromRequiredAttributes remove attributes from current list, errors if attribute does not exists
func RemoveFromRequiredAttributes(currentAttrs []string, removeAttrs []string) ([]string, error) {
toRem := make(map[string]bool)
for _, ra := range removeAttrs {
found := false
for _, ca := range currentAttrs {
if ra == ca {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("attribute %q is already not required", ra)
}
toRem[ra] = true
}
reqAttrs := make([]string, 0, len(currentAttrs))
for _, ca := range currentAttrs {
if !toRem[ca] {
reqAttrs = append(reqAttrs, ca)
}
}
return reqAttrs, nil
}
// NewNetAssetValue returns a new instance of NetAssetValue
func NewNetAssetValue(price sdk.Coin, volume uint64) NetAssetValue {
return NetAssetValue{
Price: price,
Volume: volume,
}
}
// Validate returns error if NetAssetValue is not in a valid state
func (mnav *NetAssetValue) Validate() error {
if err := mnav.Price.Validate(); err != nil {
return err
}
if mnav.Price.Amount.GT(sdk.NewInt(0)) && mnav.Volume < 1 {
return fmt.Errorf("marker net asset value volume must be positive value")
}
return nil
}