github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/eth/contract.go (about)

     1  package eth
     2  
     3  import (
     4  	"context"
     5  	"math/big"
     6  	"strings"
     7  
     8  	ethcommon "github.com/ethereum/go-ethereum/common"
     9  	"github.com/ethereum/go-ethereum/common/hexutil"
    10  	"github.com/juju/errors"
    11  	"github.com/trezor/blockbook/bchain"
    12  )
    13  
    14  const erc20TransferMethodSignature = "0xa9059cbb"                  // transfer(address,uint256)
    15  const erc721TransferFromMethodSignature = "0x23b872dd"             // transferFrom(address,address,uint256)
    16  const erc721SafeTransferFromMethodSignature = "0x42842e0e"         // safeTransferFrom(address,address,uint256)
    17  const erc721SafeTransferFromWithDataMethodSignature = "0xb88d4fde" // safeTransferFrom(address,address,uint256,bytes)
    18  const erc721TokenURIMethodSignature = "0xc87b56dd"                 // tokenURI(uint256)
    19  const erc1155URIMethodSignature = "0x0e89341c"                     // uri(uint256)
    20  
    21  const tokenTransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
    22  const tokenERC1155TransferSingleEventSignature = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
    23  const tokenERC1155TransferBatchEventSignature = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
    24  
    25  const nameRegisteredEventSignature = "0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f"
    26  
    27  const contractNameSignature = "0x06fdde03"
    28  const contractSymbolSignature = "0x95d89b41"
    29  const contractDecimalsSignature = "0x313ce567"
    30  const contractBalanceOfSignature = "0x70a08231"
    31  
    32  func addressFromPaddedHex(s string) (string, error) {
    33  	var t big.Int
    34  	var ok bool
    35  	if has0xPrefix(s) {
    36  		_, ok = t.SetString(s[2:], 16)
    37  	} else {
    38  		_, ok = t.SetString(s, 16)
    39  	}
    40  	if !ok {
    41  		return "", errors.New("Data is not a number")
    42  	}
    43  	a := ethcommon.BigToAddress(&t)
    44  	return a.String(), nil
    45  }
    46  
    47  func processTransferEvent(l *bchain.RpcLog) (transfer *bchain.TokenTransfer, err error) {
    48  	defer func() {
    49  		if r := recover(); r != nil {
    50  			err = errors.Errorf("processTransferEvent recovered from panic %v", r)
    51  		}
    52  	}()
    53  	tl := len(l.Topics)
    54  	var ttt bchain.TokenType
    55  	var value big.Int
    56  	if tl == 3 {
    57  		ttt = bchain.FungibleToken
    58  		_, ok := value.SetString(l.Data, 0)
    59  		if !ok {
    60  			return nil, errors.New("ERC20 log Data is not a number")
    61  		}
    62  	} else if tl == 4 {
    63  		ttt = bchain.NonFungibleToken
    64  		_, ok := value.SetString(l.Topics[3], 0)
    65  		if !ok {
    66  			return nil, errors.New("ERC721 log Topics[3] is not a number")
    67  		}
    68  	} else {
    69  		return nil, nil
    70  	}
    71  	var from, to string
    72  	from, err = addressFromPaddedHex(l.Topics[1])
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	to, err = addressFromPaddedHex(l.Topics[2])
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return &bchain.TokenTransfer{
    81  		Type:     ttt,
    82  		Contract: EIP55AddressFromAddress(l.Address),
    83  		From:     EIP55AddressFromAddress(from),
    84  		To:       EIP55AddressFromAddress(to),
    85  		Value:    value,
    86  	}, nil
    87  }
    88  
    89  func processERC1155TransferSingleEvent(l *bchain.RpcLog) (transfer *bchain.TokenTransfer, err error) {
    90  	defer func() {
    91  		if r := recover(); r != nil {
    92  			err = errors.Errorf("processERC1155TransferSingleEvent recovered from panic %v", r)
    93  		}
    94  	}()
    95  	tl := len(l.Topics)
    96  	if tl != 4 {
    97  		return nil, nil
    98  	}
    99  	var from, to string
   100  	from, err = addressFromPaddedHex(l.Topics[2])
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	to, err = addressFromPaddedHex(l.Topics[3])
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	var id, value big.Int
   109  	data := l.Data
   110  	if has0xPrefix(l.Data) {
   111  		data = data[2:]
   112  	}
   113  	_, ok := id.SetString(data[:64], 16)
   114  	if !ok {
   115  		return nil, errors.New("ERC1155 log Data id is not a number")
   116  	}
   117  	_, ok = value.SetString(data[64:128], 16)
   118  	if !ok {
   119  		return nil, errors.New("ERC1155 log Data value is not a number")
   120  	}
   121  	return &bchain.TokenTransfer{
   122  		Type:             bchain.MultiToken,
   123  		Contract:         EIP55AddressFromAddress(l.Address),
   124  		From:             EIP55AddressFromAddress(from),
   125  		To:               EIP55AddressFromAddress(to),
   126  		MultiTokenValues: []bchain.MultiTokenValue{{Id: id, Value: value}},
   127  	}, nil
   128  }
   129  
   130  func processERC1155TransferBatchEvent(l *bchain.RpcLog) (transfer *bchain.TokenTransfer, err error) {
   131  	defer func() {
   132  		if r := recover(); r != nil {
   133  			err = errors.Errorf("processERC1155TransferBatchEvent recovered from panic %v", r)
   134  		}
   135  	}()
   136  	tl := len(l.Topics)
   137  	if tl < 4 {
   138  		return nil, nil
   139  	}
   140  	var from, to string
   141  	from, err = addressFromPaddedHex(l.Topics[2])
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  	to, err = addressFromPaddedHex(l.Topics[3])
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	data := l.Data
   150  	if has0xPrefix(l.Data) {
   151  		data = data[2:]
   152  	}
   153  	var b big.Int
   154  	_, ok := b.SetString(data[:64], 16)
   155  	if !ok || !b.IsInt64() {
   156  		return nil, errors.New("ERC1155 TransferBatch, not a number")
   157  	}
   158  	offsetIds := int(b.Int64()) * 2
   159  	_, ok = b.SetString(data[64:128], 16)
   160  	if !ok || !b.IsInt64() {
   161  		return nil, errors.New("ERC1155 TransferBatch, not a number")
   162  	}
   163  	offsetValues := int(b.Int64()) * 2
   164  	_, ok = b.SetString(data[offsetIds:offsetIds+64], 16)
   165  	if !ok || !b.IsInt64() {
   166  		return nil, errors.New("ERC1155 TransferBatch, not a number")
   167  	}
   168  	countIds := int(b.Int64())
   169  	_, ok = b.SetString(data[offsetValues:offsetValues+64], 16)
   170  	if !ok || !b.IsInt64() {
   171  		return nil, errors.New("ERC1155 TransferBatch, not a number")
   172  	}
   173  	countValues := int(b.Int64())
   174  	if countIds != countValues {
   175  		return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match")
   176  	}
   177  	idValues := make([]bchain.MultiTokenValue, countValues)
   178  	for i := 0; i < countValues; i++ {
   179  		var id, value big.Int
   180  		o := offsetIds + 64 + 64*i
   181  		_, ok := id.SetString(data[o:o+64], 16)
   182  		if !ok {
   183  			return nil, errors.New("ERC1155 log Data id is not a number")
   184  		}
   185  		o = offsetValues + 64 + 64*i
   186  		_, ok = value.SetString(data[o:o+64], 16)
   187  		if !ok {
   188  			return nil, errors.New("ERC1155 log Data value is not a number")
   189  		}
   190  		idValues[i] = bchain.MultiTokenValue{Id: id, Value: value}
   191  	}
   192  	return &bchain.TokenTransfer{
   193  		Type:             bchain.MultiToken,
   194  		Contract:         EIP55AddressFromAddress(l.Address),
   195  		From:             EIP55AddressFromAddress(from),
   196  		To:               EIP55AddressFromAddress(to),
   197  		MultiTokenValues: idValues,
   198  	}, nil
   199  }
   200  
   201  func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) {
   202  	var r bchain.TokenTransfers
   203  	var tt *bchain.TokenTransfer
   204  	var err error
   205  	for _, l := range logs {
   206  		tl := len(l.Topics)
   207  		if tl > 0 {
   208  			signature := l.Topics[0]
   209  			if signature == tokenTransferEventSignature {
   210  				tt, err = processTransferEvent(l)
   211  			} else if signature == tokenERC1155TransferSingleEventSignature {
   212  				tt, err = processERC1155TransferSingleEvent(l)
   213  			} else if signature == tokenERC1155TransferBatchEventSignature {
   214  				tt, err = processERC1155TransferBatchEvent(l)
   215  			} else {
   216  				continue
   217  			}
   218  			if err != nil {
   219  				return nil, err
   220  			}
   221  			if tt != nil {
   222  				r = append(r, tt)
   223  			}
   224  		}
   225  	}
   226  	return r, nil
   227  }
   228  
   229  func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfers, error) {
   230  	var r bchain.TokenTransfers
   231  	if len(tx.Payload) == 10+128 && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) {
   232  		to, err := addressFromPaddedHex(tx.Payload[10 : 10+64])
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		var t big.Int
   237  		_, ok := t.SetString(tx.Payload[10+64:], 16)
   238  		if !ok {
   239  			return nil, errors.New("Data is not a number")
   240  		}
   241  		r = append(r, &bchain.TokenTransfer{
   242  			Type:     bchain.FungibleToken,
   243  			Contract: EIP55AddressFromAddress(tx.To),
   244  			From:     EIP55AddressFromAddress(tx.From),
   245  			To:       EIP55AddressFromAddress(to),
   246  			Value:    t,
   247  		})
   248  	} else if len(tx.Payload) >= 10+192 &&
   249  		(strings.HasPrefix(tx.Payload, erc721TransferFromMethodSignature) ||
   250  			strings.HasPrefix(tx.Payload, erc721SafeTransferFromMethodSignature) ||
   251  			strings.HasPrefix(tx.Payload, erc721SafeTransferFromWithDataMethodSignature)) {
   252  		from, err := addressFromPaddedHex(tx.Payload[10 : 10+64])
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  		to, err := addressFromPaddedHex(tx.Payload[10+64 : 10+128])
   257  		if err != nil {
   258  			return nil, err
   259  		}
   260  		var t big.Int
   261  		_, ok := t.SetString(tx.Payload[10+128:10+192], 16)
   262  		if !ok {
   263  			return nil, errors.New("Data is not a number")
   264  		}
   265  		r = append(r, &bchain.TokenTransfer{
   266  			Type:     bchain.NonFungibleToken,
   267  			Contract: EIP55AddressFromAddress(tx.To),
   268  			From:     EIP55AddressFromAddress(from),
   269  			To:       EIP55AddressFromAddress(to),
   270  			Value:    t,
   271  		})
   272  	}
   273  	return r, nil
   274  }
   275  
   276  func (b *EthereumRPC) ethCall(data, to string) (string, error) {
   277  	ctx, cancel := context.WithTimeout(context.Background(), b.Timeout)
   278  	defer cancel()
   279  	var r string
   280  	err := b.RPC.CallContext(ctx, &r, "eth_call", map[string]interface{}{
   281  		"data": data,
   282  		"to":   to,
   283  	}, "latest")
   284  	if err != nil {
   285  		return "", err
   286  	}
   287  	return r, nil
   288  }
   289  
   290  func (b *EthereumRPC) fetchContractInfo(address string) (*bchain.ContractInfo, error) {
   291  	var contract bchain.ContractInfo
   292  	data, err := b.ethCall(contractNameSignature, address)
   293  	if err != nil {
   294  		// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
   295  		// and returning error "execution reverted" for some non contract addresses
   296  		// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
   297  		// glog.Warning(errors.Annotatef(err, "Contract NameSignature %v", address))
   298  		return nil, nil
   299  		// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
   300  	}
   301  	name := strings.TrimSpace(parseSimpleStringProperty(data))
   302  	if name != "" {
   303  		data, err = b.ethCall(contractSymbolSignature, address)
   304  		if err != nil {
   305  			// glog.Warning(errors.Annotatef(err, "Contract SymbolSignature %v", address))
   306  			return nil, nil
   307  			// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
   308  		}
   309  		symbol := strings.TrimSpace(parseSimpleStringProperty(data))
   310  		data, _ = b.ethCall(contractDecimalsSignature, address)
   311  		// if err != nil {
   312  		// 	glog.Warning(errors.Annotatef(err, "Contract DecimalsSignature %v", address))
   313  		// 	// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
   314  		// }
   315  		contract = bchain.ContractInfo{
   316  			Contract: address,
   317  			Name:     name,
   318  			Symbol:   symbol,
   319  		}
   320  		d := parseSimpleNumericProperty(data)
   321  		if d != nil {
   322  			contract.Decimals = int(uint8(d.Uint64()))
   323  		} else {
   324  			contract.Decimals = EtherAmountDecimalPoint
   325  		}
   326  	} else {
   327  		return nil, nil
   328  	}
   329  	return &contract, nil
   330  }
   331  
   332  // GetContractInfo returns information about a contract
   333  func (b *EthereumRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) {
   334  	address := EIP55Address(contractDesc)
   335  	return b.fetchContractInfo(address)
   336  }
   337  
   338  // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
   339  func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
   340  	addr := hexutil.Encode(addrDesc)[2:]
   341  	contract := hexutil.Encode(contractDesc)
   342  	req := contractBalanceOfSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr):] + addr
   343  	data, err := b.ethCall(req, contract)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	r := parseSimpleNumericProperty(data)
   348  	if r == nil {
   349  		return nil, errors.New("Invalid balance")
   350  	}
   351  	return r, nil
   352  }
   353  
   354  // GetContractInfo returns URI of non fungible or multi token defined by token id
   355  func (b *EthereumRPC) GetTokenURI(contractDesc bchain.AddressDescriptor, tokenID *big.Int) (string, error) {
   356  	address := hexutil.Encode(contractDesc)
   357  	// CryptoKitties do not fully support ERC721 standard, do not have tokenURI method
   358  	if address == "0x06012c8cf97bead5deae237070f9587f8e7a266d" {
   359  		return "https://api.cryptokitties.co/kitties/" + tokenID.Text(10), nil
   360  	}
   361  	id := tokenID.Text(16)
   362  	if len(id) < 64 {
   363  		id = "0000000000000000000000000000000000000000000000000000000000000000"[len(id):] + id
   364  	}
   365  	// try ERC721 tokenURI method and  ERC1155 uri method
   366  	for _, method := range []string{erc721TokenURIMethodSignature, erc1155URIMethodSignature} {
   367  		data, err := b.ethCall(method+id, address)
   368  		if err == nil && data != "" {
   369  			uri := parseSimpleStringProperty(data)
   370  			// try to sanitize the URI returned from the contract
   371  			i := strings.LastIndex(uri, "ipfs://")
   372  			if i >= 0 {
   373  				uri = strings.Replace(uri[i:], "ipfs://", "https://ipfs.io/ipfs/", 1)
   374  				// some contracts return uri ipfs://ifps/abcdef instead of ipfs://abcdef
   375  				uri = strings.Replace(uri, "https://ipfs.io/ipfs/ipfs/", "https://ipfs.io/ipfs/", 1)
   376  			}
   377  			i = strings.LastIndex(uri, "https://")
   378  			// allow only https:// URIs
   379  			if i >= 0 {
   380  				uri = strings.ReplaceAll(uri[i:], "{id}", id)
   381  				return uri, nil
   382  			}
   383  		}
   384  	}
   385  	return "", nil
   386  }