github.com/cerberus-wallet/blockbook@v0.3.2/bchain/coins/eth/erc20.go (about) 1 package eth 2 3 import ( 4 "blockbook/bchain" 5 "bytes" 6 "context" 7 "encoding/hex" 8 "math/big" 9 "strings" 10 "sync" 11 "unicode/utf8" 12 13 ethcommon "github.com/ethereum/go-ethereum/common" 14 "github.com/golang/glog" 15 "github.com/juju/errors" 16 ) 17 18 var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"}, 19 {"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"}, 20 {"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"}, 21 {"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"}, 22 {"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"}, 23 {"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"}, 24 {"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"}, 25 {"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"}, 26 {"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"}, 27 {"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}, 28 {"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}, 29 {"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"}, 30 {"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"}, 31 {"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` 32 33 // doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event 34 const erc20TransferMethodSignature = "0xa9059cbb" 35 const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" 36 const erc20NameSignature = "0x06fdde03" 37 const erc20SymbolSignature = "0x95d89b41" 38 const erc20DecimalsSignature = "0x313ce567" 39 const erc20BalanceOf = "0x70a08231" 40 41 var cachedContracts = make(map[string]*bchain.Erc20Contract) 42 var cachedContractsMux sync.Mutex 43 44 func addressFromPaddedHex(s string) (string, error) { 45 var t big.Int 46 var ok bool 47 if has0xPrefix(s) { 48 _, ok = t.SetString(s[2:], 16) 49 } else { 50 _, ok = t.SetString(s, 16) 51 } 52 if !ok { 53 return "", errors.New("Data is not a number") 54 } 55 a := ethcommon.BigToAddress(&t) 56 return a.String(), nil 57 } 58 59 func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) { 60 var r []bchain.Erc20Transfer 61 for _, l := range logs { 62 if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature { 63 var t big.Int 64 _, ok := t.SetString(l.Data, 0) 65 if !ok { 66 return nil, errors.New("Data is not a number") 67 } 68 from, err := addressFromPaddedHex(l.Topics[1]) 69 if err != nil { 70 return nil, err 71 } 72 to, err := addressFromPaddedHex(l.Topics[2]) 73 if err != nil { 74 return nil, err 75 } 76 r = append(r, bchain.Erc20Transfer{ 77 Contract: EIP55AddressFromAddress(l.Address), 78 From: EIP55AddressFromAddress(from), 79 To: EIP55AddressFromAddress(to), 80 Tokens: t, 81 }) 82 } 83 } 84 return r, nil 85 } 86 87 func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) { 88 var r []bchain.Erc20Transfer 89 if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) { 90 to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)]) 91 if err != nil { 92 return nil, err 93 } 94 var t big.Int 95 _, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16) 96 if !ok { 97 return nil, errors.New("Data is not a number") 98 } 99 r = append(r, bchain.Erc20Transfer{ 100 Contract: EIP55AddressFromAddress(tx.To), 101 From: EIP55AddressFromAddress(tx.From), 102 To: EIP55AddressFromAddress(to), 103 Tokens: t, 104 }) 105 } 106 return r, nil 107 } 108 109 func (b *EthereumRPC) ethCall(data, to string) (string, error) { 110 ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 111 defer cancel() 112 var r string 113 err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{ 114 "data": data, 115 "to": to, 116 }, "latest") 117 if err != nil { 118 return "", err 119 } 120 return r, nil 121 } 122 123 func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { 124 if has0xPrefix(data) { 125 data = data[2:] 126 } 127 if len(data) == 64 { 128 var n big.Int 129 _, ok := n.SetString(data, 16) 130 if ok { 131 return &n 132 } 133 } 134 if glog.V(1) { 135 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 136 } 137 return nil 138 } 139 140 func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { 141 if has0xPrefix(data) { 142 data = data[2:] 143 } 144 if len(data) > 128 { 145 n := parseErc20NumericProperty(contractDesc, data[64:128]) 146 if n != nil { 147 l := n.Uint64() 148 if 2*int(l) <= len(data)-128 { 149 b, err := hex.DecodeString(data[128 : 128+2*l]) 150 if err == nil { 151 return string(b) 152 } 153 } 154 } 155 } else if len(data) == 64 { 156 // allow string properties as 32 bytes of UTF-8 data 157 b, err := hex.DecodeString(data) 158 if err == nil { 159 i := bytes.Index(b, []byte{0}) 160 if i > 0 { 161 b = b[:i] 162 } 163 if utf8.Valid(b) { 164 return string(b) 165 } 166 } 167 } 168 if glog.V(1) { 169 glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) 170 } 171 return "" 172 } 173 174 // EthereumTypeGetErc20ContractInfo returns information about ERC20 contract 175 func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { 176 cds := string(contractDesc) 177 cachedContractsMux.Lock() 178 contract, found := cachedContracts[cds] 179 cachedContractsMux.Unlock() 180 if !found { 181 address := EIP55Address(contractDesc) 182 data, err := b.ethCall(erc20NameSignature, address) 183 if err != nil { 184 return nil, err 185 } 186 name := parseErc20StringProperty(contractDesc, data) 187 if name != "" { 188 data, err = b.ethCall(erc20SymbolSignature, address) 189 if err != nil { 190 return nil, err 191 } 192 symbol := parseErc20StringProperty(contractDesc, data) 193 data, err = b.ethCall(erc20DecimalsSignature, address) 194 if err != nil { 195 return nil, err 196 } 197 contract = &bchain.Erc20Contract{ 198 Contract: address, 199 Name: name, 200 Symbol: symbol, 201 } 202 d := parseErc20NumericProperty(contractDesc, data) 203 if d != nil { 204 contract.Decimals = int(uint8(d.Uint64())) 205 } else { 206 contract.Decimals = EtherAmountDecimalPoint 207 } 208 } else { 209 contract = nil 210 } 211 cachedContractsMux.Lock() 212 cachedContracts[cds] = contract 213 cachedContractsMux.Unlock() 214 } 215 return contract, nil 216 } 217 218 // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address 219 func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { 220 addr := EIP55Address(addrDesc) 221 contract := EIP55Address(contractDesc) 222 req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] 223 data, err := b.ethCall(req, contract) 224 if err != nil { 225 return nil, err 226 } 227 r := parseErc20NumericProperty(contractDesc, data) 228 if r == nil { 229 return nil, errors.New("Invalid balance") 230 } 231 return r, nil 232 }