Skip to content

Commit 4e88372

Browse files
committed
perf: short-circuit address parsing
1 parent b0bb622 commit 4e88372

2 files changed

Lines changed: 89 additions & 6 deletions

File tree

bchain/coins/eth/contract.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package eth
22

33
import (
44
"context"
5+
"encoding/hex"
56
"math/big"
67
"strings"
78

@@ -30,14 +31,20 @@ const contractDecimalsSignature = "0x313ce567"
3031
const contractBalanceOfSignature = "0x70a08231"
3132

3233
func addressFromPaddedHex(s string) (string, error) {
33-
var t big.Int
34-
var ok bool
34+
// Logs/topics and calldata often include a 0x prefix; strip it so length math and decoding are consistent.
3535
if has0xPrefix(s) {
36-
_, ok = t.SetString(s[2:], 16)
37-
} else {
38-
_, ok = t.SetString(s, 16)
36+
s = s[2:]
3937
}
40-
if !ok {
38+
if len(s) >= EthereumTypeAddressDescriptorLen*2 {
39+
// Fast path: padded topics/data store the address in the last 20 bytes.
40+
s = s[len(s)-EthereumTypeAddressDescriptorLen*2:]
41+
if b, err := hex.DecodeString(s); err == nil && len(b) == EthereumTypeAddressDescriptorLen {
42+
return ethcommon.BytesToAddress(b).String(), nil
43+
}
44+
}
45+
// Fallback: handle odd formats by parsing the full value as a number.
46+
var t big.Int
47+
if _, ok := t.SetString(s, 16); !ok {
4148
return "", errors.New("Data is not a number")
4249
}
4350
a := ethcommon.BigToAddress(&t)

bchain/coins/eth/contract_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
package eth
44

55
import (
6+
"bytes"
7+
"encoding/hex"
68
"fmt"
79
"math/big"
810
"strings"
@@ -12,6 +14,80 @@ import (
1214
"github.com/trezor/blockbook/tests/dbtestdata"
1315
)
1416

17+
func Test_addressFromPaddedHex(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
input string
21+
wantHex string
22+
wantErr bool
23+
}{
24+
{
25+
name: "address_no_padding_with_prefix",
26+
input: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
27+
wantHex: "5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
28+
},
29+
{
30+
name: "uppercase_prefix",
31+
input: "0X0000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
32+
wantHex: "5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
33+
},
34+
{
35+
name: "padded",
36+
input: "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8",
37+
wantHex: "2aacf811ac1a60081ea39f7783c0d26c500871a8",
38+
},
39+
{
40+
name: "all_zero",
41+
input: "0x0000000000000000000000000000000000000000000000000000000000000000",
42+
wantHex: "0000000000000000000000000000000000000000",
43+
},
44+
{
45+
name: "unpadded",
46+
input: "1a2b3c",
47+
wantHex: "00000000000000000000000000000000001a2b3c",
48+
},
49+
{
50+
name: "invalid_fast_path",
51+
input: "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871zz",
52+
wantErr: true,
53+
},
54+
{
55+
name: "invalid",
56+
input: "0xzz",
57+
wantErr: true,
58+
},
59+
}
60+
61+
for _, tt := range tests {
62+
t.Run(tt.name, func(t *testing.T) {
63+
got, err := addressFromPaddedHex(tt.input)
64+
if tt.wantErr {
65+
if err == nil {
66+
t.Fatalf("expected error for %q", tt.input)
67+
}
68+
return
69+
}
70+
if err != nil {
71+
t.Fatalf("addressFromPaddedHex(%q) error %v", tt.input, err)
72+
}
73+
if len(got) < 2 || !strings.HasPrefix(got, "0x") {
74+
t.Fatalf("addressFromPaddedHex(%q) returned %q", tt.input, got)
75+
}
76+
gotBytes, err := hex.DecodeString(got[2:])
77+
if err != nil {
78+
t.Fatalf("decode got %q error %v", got, err)
79+
}
80+
wantBytes, err := hex.DecodeString(tt.wantHex)
81+
if err != nil {
82+
t.Fatalf("decode want %q error %v", tt.wantHex, err)
83+
}
84+
if !bytes.Equal(gotBytes, wantBytes) {
85+
t.Fatalf("addressFromPaddedHex(%q) bytes %x want %x", tt.input, gotBytes, wantBytes)
86+
}
87+
})
88+
}
89+
}
90+
1591
func Test_contractGetTransfersFromLog(t *testing.T) {
1692
tests := []struct {
1793
name string

0 commit comments

Comments
 (0)