github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/bchain/coins/xcb/contract.go (about) 1 package xcb 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "math/big" 8 "strings" 9 "sync" 10 "unicode/utf8" 11 12 "golang.org/x/text/language" 13 "golang.org/x/text/message" 14 15 "github.com/core-coin/go-core/v2/common" 16 "github.com/core-coin/go-core/v2/common/hexutil" 17 "github.com/golang/glog" 18 "github.com/juju/errors" 19 20 "github.com/cryptohub-digital/blockbook/bchain" 21 ) 22 23 const tokenTransferEventSignature = "0xc17a9d92b89f27cb79cc390f23a1a5d302fefab8c7911075ede952ac2b5607a1" 24 25 // doing the parsing/processing without using go-core/accounts/abi library, it is simple to get data from Transfer event 26 const cbc20TransferMethodSignature = "0x4b40e901" 27 28 const nameSignature = "0x07ba2a17" 29 const symbolSignature = "0x231782d8" 30 const decimalsSignature = "0x5d1fb5f9" 31 const balanceOfSignature = "0x1d7976f3" 32 33 const cbc721TransferFromMethodSignature = "0x31f2e679" // transferFrom(address,address,uint256) 34 const cbc721SafeTransferFromMethodSignature = "0x3453ba4a" // safeTransferFrom(address,address,uint256) 35 const cbc721SafeTransferFromWithDataMethodSignature = "0xf3d63809" // safeTransferFrom(address,address,uint256,bytes) 36 37 var cachedContracts = make(map[string]*bchain.ContractInfo) 38 var cachedContractsMux sync.Mutex 39 40 func addressFromPaddedHex(s string) (string, error) { 41 var t big.Int 42 var ok bool 43 if has0xPrefix(s) { 44 _, ok = t.SetString(s[2:], 16) 45 } else { 46 _, ok = t.SetString(s, 16) 47 } 48 if !ok { 49 return "", errors.New("Data is not a number") 50 } 51 a := common.BigToAddress(&t) 52 return a.String(), nil 53 } 54 55 func getTokenTransfersFromLog(logs []*RpcLog) (bchain.TokenTransfers, error) { 56 var r bchain.TokenTransfers 57 var tt *bchain.TokenTransfer 58 var err error 59 for _, l := range logs { 60 tl := len(l.Topics) 61 if tl > 0 { 62 signature := l.Topics[0] 63 if signature == tokenTransferEventSignature { 64 tt, err = processtokenTransferEventFromLogs(l) 65 } else { 66 continue 67 } 68 if err != nil { 69 return nil, err 70 } 71 if tt != nil { 72 r = append(r, tt) 73 } 74 } 75 } 76 return r, nil 77 } 78 79 func processtokenTransferEventFromLogs(log *RpcLog) (*bchain.TokenTransfer, error) { 80 tl := len(log.Topics) 81 var ttt bchain.TokenType 82 var value big.Int 83 if tl == 3 { 84 ttt = bchain.FungibleToken 85 _, ok := value.SetString(log.Data, 0) 86 if !ok { 87 return nil, errors.New("CBC20 log Data is not a number") 88 } 89 } else if tl == 4 { 90 ttt = bchain.NonFungibleToken 91 _, ok := value.SetString(log.Topics[3], 0) 92 if !ok { 93 return nil, errors.New("CBC721 log Topics[3] is not a number") 94 } 95 } else { 96 return nil, nil 97 } 98 99 from, err := addressFromPaddedHex(log.Topics[1]) 100 if err != nil { 101 return nil, err 102 } 103 to, err := addressFromPaddedHex(log.Topics[2]) 104 if err != nil { 105 return nil, err 106 } 107 return &bchain.TokenTransfer{ 108 Type: ttt, 109 Contract: log.Address, 110 From: from, 111 To: to, 112 Value: value, 113 }, nil 114 } 115 116 func getTokenTransfersFromTx(tx *RpcTransaction) (bchain.TokenTransfers, error) { 117 var r bchain.TokenTransfers 118 if len(tx.Payload)%(128+len(cbc20TransferMethodSignature)) == 0 && strings.HasPrefix(tx.Payload, cbc20TransferMethodSignature) { 119 to, err := addressFromPaddedHex(tx.Payload[len(cbc20TransferMethodSignature) : 64+len(cbc20TransferMethodSignature)]) 120 if err != nil { 121 return nil, err 122 } 123 var t big.Int 124 _, ok := t.SetString(tx.Payload[len(cbc20TransferMethodSignature)+64:], 16) 125 if !ok { 126 return nil, errors.New("Data is not a number") 127 } 128 r = append(r, &bchain.TokenTransfer{ 129 Contract: tx.To, 130 From: tx.From, 131 To: to, 132 Value: t, 133 Type: bchain.FungibleToken, 134 }) 135 } else if len(tx.Payload) >= 10+192 && 136 (strings.HasPrefix(tx.Payload, cbc721TransferFromMethodSignature) || 137 strings.HasPrefix(tx.Payload, cbc721SafeTransferFromMethodSignature) || 138 strings.HasPrefix(tx.Payload, cbc721SafeTransferFromWithDataMethodSignature)) { 139 from, err := addressFromPaddedHex(tx.Payload[10 : 10+64]) 140 if err != nil { 141 return nil, err 142 } 143 to, err := addressFromPaddedHex(tx.Payload[10+64 : 10+128]) 144 if err != nil { 145 return nil, err 146 } 147 var t big.Int 148 _, ok := t.SetString(tx.Payload[10+128:10+192], 16) 149 if !ok { 150 return nil, errors.New("Data is not a number") 151 } 152 r = append(r, &bchain.TokenTransfer{ 153 Type: bchain.NonFungibleToken, 154 Contract: tx.To, 155 From: from, 156 To: to, 157 Value: t, 158 }) 159 } 160 return r, nil 161 } 162 163 func (b *CoreblockchainRPC) xcbCall(data, to string) (string, error) { 164 ctx, cancel := context.WithTimeout(context.Background(), b.Timeout) 165 defer cancel() 166 var r string 167 err := b.RPC.CallContext(ctx, &r, "xcb_call", map[string]interface{}{ 168 "data": data, 169 "to": to, 170 }, "latest") 171 if err != nil { 172 return "", err 173 } 174 return r, nil 175 } 176 177 func parseCBC20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { 178 if has0xPrefix(data) { 179 data = data[2:] 180 } 181 if len(data) > 64 { 182 data = data[:64] 183 } 184 if len(data) == 64 { 185 var n big.Int 186 _, ok := n.SetString(data, 16) 187 if ok { 188 return &n 189 } 190 } 191 if glog.V(1) { 192 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 193 } 194 return nil 195 } 196 197 func parseCBC20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { 198 if has0xPrefix(data) { 199 data = data[2:] 200 } 201 if len(data) > 128 { 202 n := parseCBC20NumericProperty(contractDesc, data[64:128]) 203 if n != nil { 204 l := n.Uint64() 205 if l > 0 && 2*int(l) <= len(data)-128 { 206 b, err := hex.DecodeString(data[128 : 128+2*l]) 207 if err == nil { 208 return string(b) 209 } 210 } 211 } 212 } 213 // allow string properties as UTF-8 data 214 b, err := hex.DecodeString(data) 215 if err == nil { 216 i := bytes.Index(b, []byte{0}) 217 if i > 32 { 218 i = 32 219 } 220 if i > 0 { 221 b = b[:i] 222 } 223 if utf8.Valid(b) { 224 return string(b) 225 } 226 } 227 if glog.V(1) { 228 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 229 } 230 return "" 231 } 232 233 func (b *CoreblockchainRPC) AddVerifiedSCData(contract *bchain.ContractInfo) *bchain.ContractInfo { 234 if contract != nil { 235 // if smart contract ticker is verified but address is wrong -> do not show SC symbol (ticker) 236 if !b.smartContractVerifier.IsValidVerifiedSC(contract.Contract, contract.Symbol) { 237 contract.Symbol = "" 238 return contract 239 } 240 // if smart contract address is verified -> add verifying data 241 if sc := b.smartContractVerifier.GetVerified(contract.Contract); sc != nil { 242 contract.Icon = sc.Icon 243 contract.VerifierWebAddress = sc.Web 244 245 p := message.NewPrinter(language.English) 246 contract.TotalSupply = p.Sprintf("%d\n", sc.TotalSupply) 247 } 248 } 249 return contract 250 } 251 252 func (b *CoreblockchainRPC) FindVerifiedByName(query string) *bchain.AddressDescriptor { 253 contains := func(s []string, e string) bool { 254 for _, a := range s { 255 if strings.ToLower(a) == strings.ToLower(e) { 256 return true 257 } 258 } 259 return false 260 } 261 for _, sc := range b.smartContractVerifier.GetAllSmartContracts() { 262 if contains(sc.Aliases, query) { 263 ad, _ := bchain.AddressDescriptorFromString("ad:" + sc.Address) 264 return &ad 265 } 266 } 267 for _, sc := range b.addressVerifier.GetAllAddresses() { 268 if contains(sc.Aliases, query) { 269 ad, _ := bchain.AddressDescriptorFromString("ad:" + sc.Address) 270 return &ad 271 } 272 } 273 274 return nil 275 } 276 277 func (b *CoreblockchainRPC) AddVerifiedAddressData(address bchain.AddressDescriptor) *bchain.VerifiedAddress { 278 return b.addressVerifier.GetVerified(common.Bytes2Hex(address)) 279 } 280 281 // GetContractInfo returns information about smart contract 282 func (b *CoreblockchainRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) { 283 cds, err := b.Parser.GetAddrDescFromAddress(common.Bytes2Hex(contractDesc[:])) 284 if err != nil { 285 return nil, err 286 } 287 cachedContractsMux.Lock() 288 contract, found := cachedContracts[common.Bytes2Hex(cds)] 289 cachedContractsMux.Unlock() 290 291 if !found { 292 address, err := common.HexToAddress(common.Bytes2Hex(cds)) 293 if err != nil { 294 return nil, err 295 } 296 297 contractInfo := &bchain.ContractInfo{} 298 if sc := b.smartContractVerifier.GetVerified(common.Bytes2Hex(contractDesc[:])); sc != nil { 299 contractInfo.Icon = sc.Icon 300 contractInfo.VerifierWebAddress = sc.Web 301 302 p := message.NewPrinter(language.English) 303 contractInfo.TotalSupply = p.Sprintf("%d", sc.TotalSupply) 304 } 305 data, err := b.xcbCall(nameSignature, address.Hex()) 306 if err != nil { 307 if strings.Contains(err.Error(), "execution reverted") { 308 // if execution reverted -> it is not cbc20 smart contract 309 contractInfo.Contract = address.Hex() 310 contractInfo.Type = CBC721TokenType 311 return contractInfo, nil 312 } 313 return nil, nil 314 } 315 name := parseCBC20StringProperty(contractDesc, data) 316 if name != "" { 317 data, err = b.xcbCall(symbolSignature, address.Hex()) 318 if err != nil { 319 glog.Warning(errors.Annotatef(err, "cbc20SymbolSignature %v", address)) 320 return nil, nil 321 // return nil, errors.Annotatef(err, "cbc20SymbolSignature %v", address) 322 } 323 symbol := parseCBC20StringProperty(contractDesc, data) 324 data, err = b.xcbCall(decimalsSignature, address.Hex()) 325 if err != nil { 326 glog.Warning(errors.Annotatef(err, "cbc20DecimalsSignature %v", address)) 327 // return nil, errors.Annotatef(err, "cbc20DecimalsSignature %v", address) 328 } 329 contractInfo.Contract = address.Hex() 330 contractInfo.Name = name 331 contractInfo.Symbol = symbol 332 contractInfo.Type = CBC20TokenType 333 334 // if smart contract ticker is verified but address is wrong -> do not show SC symbol (ticker) 335 if !b.smartContractVerifier.IsValidVerifiedSC(contractInfo.Contract, contractInfo.Symbol) { 336 contractInfo.Symbol = "" 337 } 338 d := parseCBC20NumericProperty(contractDesc, data) 339 if d != nil { 340 contractInfo.Decimals = int(uint8(d.Uint64())) 341 } else { 342 contractInfo.Decimals = CoreAmountDecimalPoint 343 } 344 } else { 345 contractInfo = nil 346 } 347 cachedContractsMux.Lock() 348 cachedContracts[common.Bytes2Hex(cds)] = contractInfo 349 cachedContractsMux.Unlock() 350 return contractInfo, nil 351 } 352 return contract, nil 353 } 354 355 // CoreCoinTypeGetCbc20ContractBalance returns balance of cbc20 contract for given address 356 func (b *CoreblockchainRPC) CoreCoinTypeGetCbc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { 357 addr := cutAddress(addrDesc) 358 contract := "0x" + cutAddress(contractDesc) 359 360 req := balanceOfSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr):] + addr 361 data, err := b.xcbCall(req, contract) 362 if err != nil { 363 return nil, err 364 } 365 r := parseCBC20NumericProperty(contractDesc, data) 366 if r == nil { 367 return nil, errors.New("Invalid balance") 368 } 369 return r, nil 370 } 371 372 func cutAddress(addrDesc bchain.AddressDescriptor) string { 373 raw := hexutil.Encode(addrDesc) 374 375 if len(raw) > 2 { 376 raw = raw[2:] 377 } 378 379 return raw 380 }