github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/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 "github.com/core-coin/go-core/v2/common" 13 "github.com/core-coin/go-core/v2/common/hexutil" 14 "github.com/golang/glog" 15 "github.com/juju/errors" 16 17 "github.com/cryptohub-digital/blockbook-fork/bchain" 18 ) 19 20 const tokenTransferEventSignature = "0xc17a9d92b89f27cb79cc390f23a1a5d302fefab8c7911075ede952ac2b5607a1" 21 22 // doing the parsing/processing without using go-core/accounts/abi library, it is simple to get data from Transfer event 23 const crc20TransferMethodSignature = "0x4b40e901" 24 25 const nameSignature = "0x07ba2a17" 26 const symbolSignature = "0x231782d8" 27 const decimalsSignature = "0x5d1fb5f9" 28 const balanceOfSignature = "0x1d7976f3" 29 30 const crc721TransferFromMethodSignature = "0x31f2e679" // transferFrom(address,address,uint256) 31 const crc721SafeTransferFromMethodSignature = "0x3453ba4a" // safeTransferFrom(address,address,uint256) 32 const crc721SafeTransferFromWithDataMethodSignature = "0xf3d63809" // safeTransferFrom(address,address,uint256,bytes) 33 34 var cachedContracts = make(map[string]*bchain.ContractInfo) 35 var cachedContractsMux sync.Mutex 36 37 func addressFromPaddedHex(s string) (string, error) { 38 var t big.Int 39 var ok bool 40 if has0xPrefix(s) { 41 _, ok = t.SetString(s[2:], 16) 42 } else { 43 _, ok = t.SetString(s, 16) 44 } 45 if !ok { 46 return "", errors.New("Data is not a number") 47 } 48 a := common.BigToAddress(&t) 49 return a.String(), nil 50 } 51 52 func getTokenTransfersFromLog(logs []*RpcLog) (bchain.TokenTransfers, error) { 53 var r bchain.TokenTransfers 54 var tt *bchain.TokenTransfer 55 var err error 56 for _, l := range logs { 57 tl := len(l.Topics) 58 if tl > 0 { 59 signature := l.Topics[0] 60 if signature == tokenTransferEventSignature { 61 tt, err = processtokenTransferEventFromLogs(l) 62 } else { 63 continue 64 } 65 if err != nil { 66 return nil, err 67 } 68 if tt != nil { 69 r = append(r, tt) 70 } 71 } 72 } 73 return r, nil 74 } 75 76 func processtokenTransferEventFromLogs(log *RpcLog) (*bchain.TokenTransfer, error) { 77 tl := len(log.Topics) 78 var ttt bchain.TokenType 79 var value big.Int 80 if tl == 3 { 81 ttt = bchain.FungibleToken 82 _, ok := value.SetString(log.Data, 0) 83 if !ok { 84 return nil, errors.New("CRC20 log Data is not a number") 85 } 86 } else if tl == 4 { 87 ttt = bchain.NonFungibleToken 88 _, ok := value.SetString(log.Topics[3], 0) 89 if !ok { 90 return nil, errors.New("CRC721 log Topics[3] is not a number") 91 } 92 } else { 93 return nil, nil 94 } 95 96 from, err := addressFromPaddedHex(log.Topics[1]) 97 if err != nil { 98 return nil, err 99 } 100 to, err := addressFromPaddedHex(log.Topics[2]) 101 if err != nil { 102 return nil, err 103 } 104 return &bchain.TokenTransfer{ 105 Type: ttt, 106 Contract: log.Address, 107 From: from, 108 To: to, 109 Value: value, 110 }, nil 111 } 112 113 func getTokenTransfersFromTx(tx *RpcTransaction) (bchain.TokenTransfers, error) { 114 var r bchain.TokenTransfers 115 if len(tx.Payload)%(128+len(crc20TransferMethodSignature)) == 0 && strings.HasPrefix(tx.Payload, crc20TransferMethodSignature) { 116 to, err := addressFromPaddedHex(tx.Payload[len(crc20TransferMethodSignature) : 64+len(crc20TransferMethodSignature)]) 117 if err != nil { 118 return nil, err 119 } 120 var t big.Int 121 _, ok := t.SetString(tx.Payload[len(crc20TransferMethodSignature)+64:], 16) 122 if !ok { 123 return nil, errors.New("Data is not a number") 124 } 125 r = append(r, &bchain.TokenTransfer{ 126 Contract: tx.To, 127 From: tx.From, 128 To: to, 129 Value: t, 130 Type: bchain.FungibleToken, 131 }) 132 } else if len(tx.Payload) >= 10+192 && 133 (strings.HasPrefix(tx.Payload, crc721TransferFromMethodSignature) || 134 strings.HasPrefix(tx.Payload, crc721SafeTransferFromMethodSignature) || 135 strings.HasPrefix(tx.Payload, crc721SafeTransferFromWithDataMethodSignature)) { 136 from, err := addressFromPaddedHex(tx.Payload[10 : 10+64]) 137 if err != nil { 138 return nil, err 139 } 140 to, err := addressFromPaddedHex(tx.Payload[10+64 : 10+128]) 141 if err != nil { 142 return nil, err 143 } 144 var t big.Int 145 _, ok := t.SetString(tx.Payload[10+128:10+192], 16) 146 if !ok { 147 return nil, errors.New("Data is not a number") 148 } 149 r = append(r, &bchain.TokenTransfer{ 150 Type: bchain.NonFungibleToken, 151 Contract: tx.To, 152 From: from, 153 To: to, 154 Value: t, 155 }) 156 } 157 return r, nil 158 } 159 160 func (b *CoreblockchainRPC) xcbCall(data, to string) (string, error) { 161 ctx, cancel := context.WithTimeout(context.Background(), b.Timeout) 162 defer cancel() 163 var r string 164 err := b.RPC.CallContext(ctx, &r, "xcb_call", map[string]interface{}{ 165 "data": data, 166 "to": to, 167 }, "latest") 168 if err != nil { 169 return "", err 170 } 171 return r, nil 172 } 173 174 func parseCRC20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { 175 if has0xPrefix(data) { 176 data = data[2:] 177 } 178 if len(data) > 64 { 179 data = data[:64] 180 } 181 if len(data) == 64 { 182 var n big.Int 183 _, ok := n.SetString(data, 16) 184 if ok { 185 return &n 186 } 187 } 188 if glog.V(1) { 189 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 190 } 191 return nil 192 } 193 194 func parseCRC20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { 195 if has0xPrefix(data) { 196 data = data[2:] 197 } 198 if len(data) > 128 { 199 n := parseCRC20NumericProperty(contractDesc, data[64:128]) 200 if n != nil { 201 l := n.Uint64() 202 if l > 0 && 2*int(l) <= len(data)-128 { 203 b, err := hex.DecodeString(data[128 : 128+2*l]) 204 if err == nil { 205 return string(b) 206 } 207 } 208 } 209 } 210 // allow string properties as UTF-8 data 211 b, err := hex.DecodeString(data) 212 if err == nil { 213 i := bytes.Index(b, []byte{0}) 214 if i > 32 { 215 i = 32 216 } 217 if i > 0 { 218 b = b[:i] 219 } 220 if utf8.Valid(b) { 221 return string(b) 222 } 223 } 224 if glog.V(1) { 225 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 226 } 227 return "" 228 } 229 230 // GetContractInfo returns information about smart contract 231 func (b *CoreblockchainRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) { 232 cds, err := b.Parser.GetAddrDescFromAddress(common.Bytes2Hex(contractDesc[:])) 233 if err != nil { 234 return nil, err 235 } 236 cachedContractsMux.Lock() 237 contract, found := cachedContracts[common.Bytes2Hex(cds)] 238 cachedContractsMux.Unlock() 239 240 if !found { 241 address, err := common.HexToAddress(common.Bytes2Hex(cds)) 242 if err != nil { 243 return nil, err 244 } 245 data, err := b.xcbCall(nameSignature, address.Hex()) 246 if err != nil { 247 if strings.Contains(err.Error(), "execution reverted") { 248 // if execution reverted -> it is not crc20 smart contract 249 return &bchain.ContractInfo{ 250 Contract: address.Hex(), 251 Type: CRC721TokenType, 252 }, nil 253 } 254 return nil, nil 255 } 256 name := parseCRC20StringProperty(contractDesc, data) 257 if name != "" { 258 data, err = b.xcbCall(symbolSignature, address.Hex()) 259 if err != nil { 260 glog.Warning(errors.Annotatef(err, "crc20SymbolSignature %v", address)) 261 return nil, nil 262 // return nil, errors.Annotatef(err, "crc20SymbolSignature %v", address) 263 } 264 symbol := parseCRC20StringProperty(contractDesc, data) 265 data, err = b.xcbCall(decimalsSignature, address.Hex()) 266 if err != nil { 267 glog.Warning(errors.Annotatef(err, "crc20DecimalsSignature %v", address)) 268 // return nil, errors.Annotatef(err, "crc20DecimalsSignature %v", address) 269 } 270 contract = &bchain.ContractInfo{ 271 Contract: address.Hex(), 272 Name: name, 273 Symbol: symbol, 274 Type: CRC20TokenType, 275 } 276 d := parseCRC20NumericProperty(contractDesc, data) 277 if d != nil { 278 contract.Decimals = int(uint8(d.Uint64())) 279 } else { 280 contract.Decimals = CoreAmountDecimalPoint 281 } 282 } else { 283 contract = nil 284 } 285 cachedContractsMux.Lock() 286 cachedContracts[common.Bytes2Hex(cds)] = contract 287 cachedContractsMux.Unlock() 288 } 289 return contract, nil 290 } 291 292 // CoreCoinTypeGetCrc20ContractBalance returns balance of crc20 contract for given address 293 func (b *CoreblockchainRPC) CoreCoinTypeGetCrc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { 294 addr := cutAddress(addrDesc) 295 contract := "0x" + cutAddress(contractDesc) 296 297 req := balanceOfSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr):] + addr 298 data, err := b.xcbCall(req, contract) 299 if err != nil { 300 return nil, err 301 } 302 r := parseCRC20NumericProperty(contractDesc, data) 303 if r == nil { 304 return nil, errors.New("Invalid balance") 305 } 306 return r, nil 307 } 308 309 func cutAddress(addrDesc bchain.AddressDescriptor) string { 310 raw := hexutil.Encode(addrDesc) 311 312 if len(raw) > 2 { 313 raw = raw[2:] 314 } 315 316 return raw 317 }