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  }