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 }