github.com/Finschia/finschia-sdk@v0.48.1/x/collection/collection.go (about) 1 package collection 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 proto "github.com/gogo/protobuf/proto" 9 10 codectypes "github.com/Finschia/finschia-sdk/codec/types" 11 sdk "github.com/Finschia/finschia-sdk/types" 12 ) 13 14 const ( 15 prefixLegacyPermission = "LEGACY_PERMISSION_" 16 ) 17 18 // Deprecated: use Permission. 19 func LegacyPermissionFromString(name string) LegacyPermission { 20 legacyPermissionName := prefixLegacyPermission + strings.ToUpper(name) 21 return LegacyPermission(LegacyPermission_value[legacyPermissionName]) 22 } 23 24 func (x LegacyPermission) String() string { 25 lenPrefix := len(prefixLegacyPermission) 26 return strings.ToLower(LegacyPermission_name[int32(x)][lenPrefix:]) 27 } 28 29 func DefaultNextClassIDs(contractID string) NextClassIDs { 30 return NextClassIDs{ 31 ContractId: contractID, 32 Fungible: sdk.NewUint(1), 33 NonFungible: sdk.NewUint(1 << 28).Incr(), // "10000000 + 1" 34 } 35 } 36 37 func validateParams(params Params) error { 38 // limits are uint32, so no need to validate them. 39 return nil 40 } 41 42 type TokenClass interface { 43 proto.Message 44 45 GetId() string 46 SetId(ids *NextClassIDs) 47 48 SetName(name string) 49 50 SetMeta(meta string) 51 52 ValidateBasic() error 53 } 54 55 func TokenClassToAny(class TokenClass) *codectypes.Any { 56 msg := class.(proto.Message) 57 58 any, err := codectypes.NewAnyWithValue(msg) 59 if err != nil { 60 panic(err) 61 } 62 63 return any 64 } 65 66 func TokenClassFromAny(any *codectypes.Any) TokenClass { 67 class := any.GetCachedValue().(TokenClass) 68 return class 69 } 70 71 func TokenClassUnpackInterfaces(any *codectypes.Any, unpacker codectypes.AnyUnpacker) error { 72 var class TokenClass 73 return unpacker.UnpackAny(any, &class) 74 } 75 76 // ---------------------------------------------------------------------------- 77 // FTClass 78 var _ TokenClass = (*FTClass)(nil) 79 80 //nolint:golint 81 func (c *FTClass) SetId(ids *NextClassIDs) { 82 id := ids.Fungible 83 ids.Fungible = id.Incr() 84 c.Id = fmt.Sprintf("%08x", id.Uint64()) 85 } 86 87 func (c *FTClass) SetName(name string) { 88 c.Name = name 89 } 90 91 func (c *FTClass) SetMeta(meta string) { 92 c.Meta = meta 93 } 94 95 func (c FTClass) ValidateBasic() error { 96 if err := ValidateClassID(c.Id); err != nil { 97 return err 98 } 99 100 if err := validateName(c.Name); err != nil { 101 return err 102 } 103 if err := validateMeta(c.Meta); err != nil { 104 return err 105 } 106 if err := validateDecimals(c.Decimals); err != nil { 107 return err 108 } 109 110 return nil 111 } 112 113 // ---------------------------------------------------------------------------- 114 // NFTClass 115 var _ TokenClass = (*NFTClass)(nil) 116 117 //nolint:golint 118 func (c *NFTClass) SetId(ids *NextClassIDs) { 119 id := ids.NonFungible 120 ids.NonFungible = id.Incr() 121 c.Id = fmt.Sprintf("%08x", id.Uint64()) 122 } 123 124 func (c *NFTClass) SetName(name string) { 125 c.Name = name 126 } 127 128 func (c *NFTClass) SetMeta(meta string) { 129 c.Meta = meta 130 } 131 132 func (c NFTClass) ValidateBasic() error { 133 if err := ValidateClassID(c.Id); err != nil { 134 return err 135 } 136 137 if err := validateName(c.Name); err != nil { 138 return err 139 } 140 if err := validateMeta(c.Meta); err != nil { 141 return err 142 } 143 144 return nil 145 } 146 147 // ---------------------------------------------------------------------------- 148 // Coin 149 func NewFTCoin(classID string, amount sdk.Int) Coin { 150 return NewCoin(NewFTID(classID), amount) 151 } 152 153 func NewNFTCoin(classID string, number int) Coin { 154 return NewCoin(NewNFTID(classID, number), sdk.OneInt()) 155 } 156 157 func NewCoin(id string, amount sdk.Int) Coin { 158 coin := Coin{ 159 TokenId: id, 160 Amount: amount, 161 } 162 163 if err := coin.ValidateBasic(); err != nil { 164 panic(err) 165 } 166 167 return coin 168 } 169 170 func (c Coin) String() string { 171 return fmt.Sprintf("%s:%s", c.TokenId, c.Amount) 172 } 173 174 func (c Coin) ValidateBasic() error { 175 if err := ValidateTokenID(c.TokenId); err != nil { 176 return err 177 } 178 179 if c.isNil() || !c.isPositive() { 180 return fmt.Errorf("invalid amount: %v", c.Amount) 181 } 182 183 if err := ValidateNFTID(c.TokenId); err == nil { 184 if !c.Amount.Equal(sdk.OneInt()) { 185 return fmt.Errorf("duplicate non fungible tokens") 186 } 187 } 188 189 return nil 190 } 191 192 func (c Coin) isPositive() bool { 193 return c.Amount.IsPositive() 194 } 195 196 func (c Coin) isNil() bool { 197 return c.Amount.IsNil() 198 } 199 200 var reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s%s):([[:digit:]]+)$`, patternClassID, patternAll)) 201 202 func ParseCoin(coinStr string) (*Coin, error) { 203 coinStr = strings.TrimSpace(coinStr) 204 205 matches := reDecCoin.FindStringSubmatch(coinStr) 206 if matches == nil { 207 return nil, fmt.Errorf("invalid coin expression: %s", coinStr) 208 } 209 210 id, amountStr := matches[1], matches[2] 211 212 amount, ok := sdk.NewIntFromString(amountStr) 213 if !ok { 214 return nil, fmt.Errorf("failed to parse coin amount: %s", amountStr) 215 } 216 217 coin := NewCoin(id, amount) 218 return &coin, nil 219 } 220 221 // ---------------------------------------------------------------------------- 222 // Coins 223 type Coins []Coin 224 225 func NewCoins(coins ...Coin) Coins { 226 newCoins := Coins(coins) 227 if err := newCoins.ValidateBasic(); err != nil { 228 panic(fmt.Errorf("invalid coin %s: %w", newCoins, err)) 229 } 230 231 return newCoins 232 } 233 234 func (coins Coins) String() string { 235 if len(coins) == 0 { 236 return "" 237 } else if len(coins) == 1 { 238 return coins[0].String() 239 } 240 241 var out strings.Builder 242 for _, coin := range coins[:len(coins)-1] { 243 out.WriteString(coin.String()) 244 out.WriteByte(',') 245 } 246 out.WriteString(coins[len(coins)-1].String()) 247 return out.String() 248 } 249 250 func (coins Coins) ValidateBasic() error { 251 if len(coins) == 0 { 252 return fmt.Errorf("empty coins") 253 } 254 255 seenIDs := map[string]bool{} 256 for _, coin := range coins { 257 if seenIDs[coin.TokenId] { 258 return fmt.Errorf("duplicate id %s", coin.TokenId) 259 } 260 seenIDs[coin.TokenId] = true 261 262 if err := coin.ValidateBasic(); err != nil { 263 return fmt.Errorf("invalid coin %s: %w", coin.TokenId, err) 264 } 265 } 266 267 return nil 268 } 269 270 func ParseCoins(coinsStr string) (Coins, error) { 271 coinsStr = strings.TrimSpace(coinsStr) 272 if len(coinsStr) == 0 { 273 return nil, fmt.Errorf("invalid string for coins") 274 } 275 276 coinStrs := strings.Split(coinsStr, ",") 277 coins := make(Coins, len(coinStrs)) 278 for i, coinStr := range coinStrs { 279 coin, err := ParseCoin(coinStr) 280 if err != nil { 281 return nil, err 282 } 283 284 coins[i] = *coin 285 } 286 287 return NewCoins(coins...), nil 288 } 289 290 type Token interface { 291 proto.Message 292 } 293 294 func TokenFromAny(any *codectypes.Any) Token { 295 class := any.GetCachedValue().(Token) 296 return class 297 } 298 299 func TokenUnpackInterfaces(any *codectypes.Any, unpacker codectypes.AnyUnpacker) error { 300 var token Token 301 return unpacker.UnpackAny(any, &token) 302 }