-
Notifications
You must be signed in to change notification settings - Fork 115
/
color.go
260 lines (211 loc) · 8.31 KB
/
color.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
package ledgerstate
import (
"bytes"
"sort"
"github.com/cockroachdb/errors"
"github.com/iotaledger/hive.go/cerrors"
"github.com/iotaledger/hive.go/datastructure/orderedmap"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/iotaledger/hive.go/stringify"
"github.com/mr-tron/base58"
)
// region Color ////////////////////////////////////////////////////////////////////////////////////////////////////////
// ColorIOTA is the zero value of the Color and represents uncolored tokens.
var ColorIOTA = Color{}
// ColorMint represents a placeholder Color that indicates that tokens should be "colored" in their Output.
var ColorMint = Color{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
// ColorLength represents the length of a Color (amount of bytes).
const ColorLength = 32
// Color represents a marker that is associated to a token balance and that can give tokens a certain "meaning".
type Color [ColorLength]byte
// ColorFromBytes unmarshals a Color from a sequence of bytes.
func ColorFromBytes(colorBytes []byte) (color Color, consumedBytes int, err error) {
marshalUtil := marshalutil.New(colorBytes)
if color, err = ColorFromMarshalUtil(marshalUtil); err != nil {
err = errors.Errorf("failed to parse Color from MarshalUtil: %w", err)
return
}
consumedBytes = marshalUtil.ReadOffset()
return
}
// ColorFromBase58EncodedString creates a Color from a base58 encoded string.
func ColorFromBase58EncodedString(base58String string) (color Color, err error) {
parsedBytes, err := base58.Decode(base58String)
if err != nil {
err = errors.Errorf("error while decoding base58 encoded Color (%v): %w", err, cerrors.ErrBase58DecodeFailed)
return
}
if color, _, err = ColorFromBytes(parsedBytes); err != nil {
err = errors.Errorf("failed to parse Color from bytes: %w", err)
return
}
return
}
// ColorFromMarshalUtil unmarshals a Color using a MarshalUtil (for easier unmarshaling).
func ColorFromMarshalUtil(marshalUtil *marshalutil.MarshalUtil) (color Color, err error) {
colorBytes, err := marshalUtil.ReadBytes(ColorLength)
if err != nil {
err = errors.Errorf("failed to parse Color (%v): %w", err, cerrors.ErrParseBytesFailed)
return
}
copy(color[:], colorBytes)
return
}
// Bytes marshals the Color into a sequence of bytes.
func (c Color) Bytes() []byte {
return c[:]
}
// Base58 returns a base58 encoded version of the Color.
func (c Color) Base58() string {
return base58.Encode(c.Bytes())
}
// String creates a human readable string of the Color.
func (c Color) String() string {
switch c {
case ColorIOTA:
return "IOTA"
case ColorMint:
return "MINT"
default:
return c.Base58()
}
}
// Compare offers a comparator for Colors which returns -1 if otherColor is bigger, 1 if it is smaller and 0 if they are
// the same.
func (c Color) Compare(otherColor Color) int {
return bytes.Compare(c[:], otherColor[:])
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region ColoredBalances //////////////////////////////////////////////////////////////////////////////////////////////
// ColoredBalances represents a collection of balances associated to their respective Color that maintains a
// deterministic order of the present Colors.
type ColoredBalances struct {
balances *orderedmap.OrderedMap
}
// NewColoredBalances returns a new deterministically ordered collection of ColoredBalances.
func NewColoredBalances(balances map[Color]uint64) (coloredBalances *ColoredBalances) {
coloredBalances = &ColoredBalances{balances: orderedmap.New()}
// deterministically sort colors
sortedColors := make([]Color, 0, len(balances))
for color, balance := range balances {
if balance == 0 {
// drop zero balances
continue
}
sortedColors = append(sortedColors, color)
}
sort.Slice(sortedColors, func(i, j int) bool { return sortedColors[i].Compare(sortedColors[j]) < 0 })
// add sorted colors to the underlying map
for _, color := range sortedColors {
coloredBalances.balances.Set(color, balances[color])
}
return
}
// ColoredBalancesFromBytes unmarshals ColoredBalances from a sequence of bytes.
func ColoredBalancesFromBytes(bytes []byte) (coloredBalances *ColoredBalances, consumedBytes int, err error) {
marshalUtil := marshalutil.New(bytes)
if coloredBalances, err = ColoredBalancesFromMarshalUtil(marshalUtil); err != nil {
err = errors.Errorf("failed to parse ColoredBalances from MarshalUtil: %w", err)
return
}
consumedBytes = marshalUtil.ReadOffset()
return
}
// ColoredBalancesFromMarshalUtil unmarshals ColoredBalances using a MarshalUtil (for easier unmarshaling).
func ColoredBalancesFromMarshalUtil(marshalUtil *marshalutil.MarshalUtil) (coloredBalances *ColoredBalances, err error) {
balancesCount, err := marshalUtil.ReadUint32()
if err != nil {
err = errors.Errorf("failed to parse element count (%v): %w", err, cerrors.ErrParseBytesFailed)
return
}
if balancesCount == 0 {
err = errors.Errorf("empty balances in output")
return
}
var previousColor *Color
coloredBalances = NewColoredBalances(nil)
for i := uint32(0); i < balancesCount; i++ {
color, colorErr := ColorFromMarshalUtil(marshalUtil)
if colorErr != nil {
err = errors.Errorf("failed to parse Color from MarshalUtil: %w", colorErr)
return
}
// check semantic correctness (ensure ordering)
if previousColor != nil && previousColor.Compare(color) != -1 {
err = errors.Errorf("parsed Colors are not in correct order: %w", cerrors.ErrParseBytesFailed)
return
}
balance, balanceErr := marshalUtil.ReadUint64()
if balanceErr != nil {
err = errors.Errorf("failed to parse balance of Color %s (%v): %w", color.String(), balanceErr, cerrors.ErrParseBytesFailed)
return
}
if balance == 0 {
err = errors.Errorf("zero balance found for color %s", color.String())
return
}
coloredBalances.balances.Set(color, balance)
previousColor = &color
}
return
}
// Get returns the balance of the given Color and a boolean value indicating if the requested Color existed.
func (c *ColoredBalances) Get(color Color) (uint64, bool) {
balance, exists := c.balances.Get(color)
ret, ok := balance.(uint64)
if !ok {
return 0, false
}
return ret, exists
}
// ForEach calls the consumer for each element in the collection and aborts the iteration if the consumer returns false.
func (c *ColoredBalances) ForEach(consumer func(color Color, balance uint64) bool) {
c.balances.ForEach(func(key, value interface{}) bool {
return consumer(key.(Color), value.(uint64))
})
}
// Size returns the amount of individual balances in the ColoredBalances.
func (c *ColoredBalances) Size() int {
return c.balances.Size()
}
// Clone returns a copy of the ColoredBalances.
func (c *ColoredBalances) Clone() *ColoredBalances {
copiedBalances := orderedmap.New()
c.balances.ForEach(copiedBalances.Set)
return &ColoredBalances{
balances: copiedBalances,
}
}
// Bytes returns a marshaled version of the ColoredBalances.
func (c *ColoredBalances) Bytes() []byte {
marshalUtil := marshalutil.New()
marshalUtil.WriteUint32(uint32(c.balances.Size()))
c.ForEach(func(color Color, balance uint64) bool {
marshalUtil.WriteBytes(color.Bytes())
marshalUtil.WriteUint64(balance)
return true
})
return marshalUtil.Bytes()
}
// Map returns a vanilla golang map (unordered) containing the existing balances. Since the ColoredBalances are
// immutable to ensure the deterministic ordering, this method can be used to retrieve a copy of the current values
// prior to some modification (like setting the updated colors of a minting transaction) which can then be used to
// create a new ColoredBalances object.
func (c *ColoredBalances) Map() (balances map[Color]uint64) {
balances = make(map[Color]uint64)
c.ForEach(func(color Color, balance uint64) bool {
balances[color] = balance
return true
})
return
}
// String returns a human readable version of the ColoredBalances.
func (c *ColoredBalances) String() string {
structBuilder := stringify.StructBuilder("ColoredBalances")
c.ForEach(func(color Color, balance uint64) bool {
structBuilder.AddField(stringify.StructField(color.String(), balance))
return true
})
return structBuilder.String()
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////