-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrt.go
More file actions
326 lines (278 loc) · 8.46 KB
/
grt.go
File metadata and controls
326 lines (278 loc) · 8.46 KB
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
package sds
import (
"fmt"
"math/big"
"strings"
"github.com/holiman/uint256"
)
const (
// GRTDecimals is the number of decimal places for GRT (18 decimals, like ETH).
GRTDecimals = 18
)
var (
// grtBase is 10^18 for converting between GRT and base units.
grtBase = func() uint256.Int {
var v uint256.Int
v.Exp(uint256.NewInt(10), uint256.NewInt(GRTDecimals))
return v
}()
// grtBaseBig is the big.Int equivalent for interop.
grtBaseBig = new(big.Int).Exp(big.NewInt(10), big.NewInt(GRTDecimals), nil)
)
// GRT represents a GRT token amount stored in base units (18 decimals).
// It wraps uint256.Int as a value type for efficient stack-based arithmetic,
// suitable for blockchain token amounts.
//
// Parsing supports:
// - "1.5 GRT" or "1.5GRT" - explicit GRT suffix
// - "1.5" - plain decimal (assumed to be GRT)
//
// For serialization, the canonical format is "<decimal> GRT" (e.g., "1.5 GRT").
//
// Zero value is valid and represents 0 GRT.
type GRT struct {
raw uint256.Int
}
// NewGRT creates a GRT from a uint64 value in base units.
// This is more efficient than NewGRTFromBigInt for small values.
func NewGRT(raw uint64) GRT {
return GRT{raw: *uint256.NewInt(raw)}
}
// NewGRTFromUint256Ptr creates a GRT from a *uint256.Int value in base units.
// If raw is nil, returns zero GRT.
func NewGRTFromUint256(raw *uint256.Int) GRT {
if raw == nil {
return GRT{}
}
return GRT{raw: *raw}
}
// NewGRTFromBigInt creates a GRT from a big.Int value in base units.
func NewGRTFromBigInt(raw *big.Int) GRT {
if raw == nil {
return GRT{}
}
v, overflow := uint256.FromBig(raw)
if overflow {
// If overflow, clamp to max value
v = uint256.MustFromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
}
return GRT{raw: *v}
}
// NewGRTFromUint64 creates a GRT from a uint64 value in base units.
// This is more efficient than NewGRTFromBigInt for small values.
func NewGRTFromUint64(raw uint64) GRT {
return GRT{raw: *uint256.NewInt(raw)}
}
// ZeroGRT returns a zero-valued GRT.
func ZeroGRT() GRT {
return GRT{}
}
// ParseGRT parses a GRT amount from a string.
//
// Supported formats:
// - "1.5 GRT" or "1.5GRT" - explicit GRT suffix, value is in GRT
// - "1.5" - plain decimal without suffix, assumed to be GRT
// - Empty string returns zero GRT
//
// All formats interpret the numeric value as GRT (not base units).
func ParseGRT(s string) (GRT, error) {
s = strings.TrimSpace(s)
if s == "" {
return GRT{}, nil
}
// Check for GRT suffix (case-insensitive)
if strings.HasSuffix(strings.ToUpper(s), "GRT") {
s = strings.TrimSpace(s[:len(s)-3])
}
return parseDecimalToGRT(s)
}
// parseDecimalToGRT parses a decimal string as GRT and converts to base units.
func parseDecimalToGRT(decimal string) (GRT, error) {
decimal = strings.TrimSpace(decimal)
if decimal == "" {
return GRT{}, nil
}
// Handle negative numbers
negative := false
if strings.HasPrefix(decimal, "-") {
negative = true
decimal = decimal[1:]
}
// Split by decimal point
parts := strings.Split(decimal, ".")
if len(parts) > 2 {
return GRT{}, fmt.Errorf("invalid decimal format: %s", decimal)
}
// Parse integer part
intPart := parts[0]
if intPart == "" {
intPart = "0"
}
intValue, ok := new(big.Int).SetString(intPart, 10)
if !ok {
return GRT{}, fmt.Errorf("invalid integer part: %s", intPart)
}
// Convert integer part to base units (multiply by 10^18)
raw := new(big.Int).Mul(intValue, grtBaseBig)
// Handle fractional part
if len(parts) == 2 {
fracPart := parts[1]
// Pad or truncate to 18 decimals
if len(fracPart) > GRTDecimals {
fracPart = fracPart[:GRTDecimals]
} else {
fracPart = fracPart + strings.Repeat("0", GRTDecimals-len(fracPart))
}
fracValue, ok := new(big.Int).SetString(fracPart, 10)
if !ok {
return GRT{}, fmt.Errorf("invalid fractional part: %s", fracPart)
}
raw.Add(raw, fracValue)
}
if negative {
raw.Neg(raw)
}
// Convert to uint256
v, overflow := uint256.FromBig(raw)
if overflow {
return GRT{}, fmt.Errorf("value overflow: %s", decimal)
}
return GRT{raw: *v}, nil
}
// Raw returns a pointer to the underlying uint256.Int value in base units.
// The returned pointer references the internal value - do not mutate.
func (g *GRT) Raw() *uint256.Int {
return &g.raw
}
// BigInt returns the value as a big.Int in base units.
func (g GRT) BigInt() *big.Int {
return g.raw.ToBig()
}
// IsZero returns true if the GRT value is zero.
func (g GRT) IsZero() bool {
return g.raw.IsZero()
}
// Cmp compares g and other and returns:
//
// -1 if g < other
// 0 if g == other
// +1 if g > other
func (g *GRT) Cmp(other *GRT) int {
return g.raw.Cmp(&other.raw)
}
// Add returns g + other.
func (g *GRT) Add(other *GRT) GRT {
var result uint256.Int
result.Add(&g.raw, &other.raw)
return GRT{raw: result}
}
// Sub returns g - other. Returns zero if result would be negative.
func (g *GRT) Sub(other *GRT) GRT {
if g.raw.Cmp(&other.raw) < 0 {
return GRT{}
}
var result uint256.Int
result.Sub(&g.raw, &other.raw)
return GRT{raw: result}
}
// Mul returns g * n where n is a multiplier (e.g., quantity).
func (g *GRT) Mul(n uint64) GRT {
var result uint256.Int
result.Mul(&g.raw, uint256.NewInt(n))
return GRT{raw: result}
}
// String returns the GRT value as a decimal string with " GRT" suffix.
// This is the canonical serialization format.
func (g *GRT) String() string {
return g.ToDecimalString() + " GRT"
}
// ToDecimalString returns the GRT value as a decimal string without suffix.
// Examples: "1.5", "0.000001", "1000"
func (g *GRT) ToDecimalString() string {
if g.raw.IsZero() {
return "0"
}
// Convert to big.Int for division
raw := g.raw.ToBig()
// Divide by 10^18 to get GRT value
grt := new(big.Int).Div(raw, grtBaseBig)
remainder := new(big.Int).Mod(raw, grtBaseBig)
if remainder.Sign() == 0 {
return grt.String()
}
// Format with fractional part (pad to 18 digits, then trim trailing zeros)
// Note: big.Int implements fmt.Formatter, so %018d works correctly
fracStr := fmt.Sprintf("%018d", remainder)
fracStr = strings.TrimRight(fracStr, "0")
return fmt.Sprintf("%s.%s", grt.String(), fracStr)
}
// MarshalText implements encoding.TextMarshaler.
// Format: "<decimal> GRT" (e.g., "1.5 GRT")
func (g GRT) MarshalText() ([]byte, error) {
return []byte(g.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
// Accepts formats: "1.5 GRT", "1.5GRT", "1.5" (assumed GRT)
func (g *GRT) UnmarshalText(text []byte) error {
parsed, err := ParseGRT(string(text))
if err != nil {
return err
}
*g = parsed
return nil
}
// MarshalJSON implements json.Marshaler.
// Serializes as a JSON string: "1.5 GRT"
func (g GRT) MarshalJSON() ([]byte, error) {
return []byte(`"` + g.String() + `"`), nil
}
// UnmarshalJSON implements json.Unmarshaler.
// Accepts JSON string formats: "1.5 GRT", "1.5GRT", "1.5"
func (g *GRT) UnmarshalJSON(data []byte) error {
// Remove quotes
s := string(data)
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
s = s[1 : len(s)-1]
}
return g.UnmarshalText([]byte(s))
}
// MarshalYAML implements yaml.Marshaler.
// Serializes as: "1.5 GRT"
func (g GRT) MarshalYAML() (any, error) {
return g.String(), nil
}
// UnmarshalYAML implements yaml.Unmarshaler.
// Accepts formats: "1.5 GRT", "1.5GRT", "1.5"
func (g *GRT) UnmarshalYAML(unmarshal func(any) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
return g.UnmarshalText([]byte(s))
}
// PricingConfig holds the pricing configuration for a data service.
// Prices are specified in GRT.
type PricingConfig struct {
// PricePerBlock is the price per processed block in GRT
PricePerBlock GRT `yaml:"price_per_block" json:"price_per_block"`
// PricePerByte is the price per byte transferred in GRT
PricePerByte GRT `yaml:"price_per_byte" json:"price_per_byte"`
}
// CalculateUsageCost calculates the total cost for given usage.
func (c *PricingConfig) CalculateUsageCost(blocksProcessed, bytesTransferred uint64) GRT {
blockCost := c.PricePerBlock.Mul(blocksProcessed)
byteCost := c.PricePerByte.Mul(bytesTransferred)
return blockCost.Add(&byteCost)
}
// DefaultPricingConfig returns a default pricing configuration.
// Based on: $3/million blocks, $175/TiB, at $0.02602/GRT
// Default: 0.000115 GRT per block (~$3 per million blocks)
// Default: 0.0000000061 GRT per byte (~$175 per TiB)
func DefaultPricingConfig() *PricingConfig {
blockPrice, _ := ParseGRT("0.000115 GRT")
bytePrice, _ := ParseGRT("0.0000000061 GRT")
return &PricingConfig{
PricePerBlock: blockPrice,
PricePerByte: bytePrice,
}
}