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  }