-
Notifications
You must be signed in to change notification settings - Fork 9
/
caller.go
124 lines (104 loc) · 3.22 KB
/
caller.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package multicall
import (
"context"
"fmt"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/forta-network/go-multicall/contracts/contract_multicall"
)
// DefaultAddress is the same for all chains (Multicall3).
// Taken from https://github.com/mds1/multicall
const DefaultAddress = "0xcA11bde05977b3631167028862bE2a173976CA11"
// Caller makes multicalls.
type Caller struct {
contract contract_multicall.Interface
}
// New creates a new caller.
func New(client bind.ContractCaller, multicallAddr ...string) (*Caller, error) {
addr := DefaultAddress
if multicallAddr != nil {
addr = multicallAddr[0]
}
contract, err := contract_multicall.NewMulticallCaller(common.HexToAddress(addr), client)
if err != nil {
return nil, err
}
return &Caller{
contract: contract,
}, nil
}
// Dial dials and Ethereum JSON-RPC API and uses the client as the
// caller backend.
func Dial(ctx context.Context, rawUrl string, multicallAddr ...string) (*Caller, error) {
client, err := ethclient.DialContext(ctx, rawUrl)
if err != nil {
return nil, err
}
return New(client, multicallAddr...)
}
// Call makes multicalls.
func (caller *Caller) Call(opts *bind.CallOpts, calls ...*Call) ([]*Call, error) {
var multiCalls []contract_multicall.Multicall3Call3
for i, call := range calls {
b, err := call.Pack()
if err != nil {
return calls, fmt.Errorf("failed to pack call inputs at index [%d]: %v", i, err)
}
multiCalls = append(multiCalls, contract_multicall.Multicall3Call3{
Target: call.Contract.Address,
AllowFailure: call.CanFail,
CallData: b,
})
}
results, err := caller.contract.Aggregate3(opts, multiCalls)
if err != nil {
return calls, fmt.Errorf("multicall failed: %v", err)
}
for i, result := range results {
call := calls[i] // index always matches
call.Failed = !result.Success
if err := call.Unpack(result.ReturnData); err != nil {
return calls, fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err)
}
}
return calls, nil
}
// CallChunked makes multiple multicalls by chunking given calls.
// Cooldown is helpful for sleeping between chunks and avoiding rate limits.
func (caller *Caller) CallChunked(opts *bind.CallOpts, chunkSize int, cooldown time.Duration, calls ...*Call) ([]*Call, error) {
var allCalls []*Call
for i, chunk := range chunkInputs(chunkSize, calls) {
if i > 0 && cooldown > 0 {
time.Sleep(cooldown)
}
chunk, err := caller.Call(opts, chunk...)
if err != nil {
return calls, fmt.Errorf("call chunk [%d] failed: %v", i, err)
}
allCalls = append(allCalls, chunk...)
}
return allCalls, nil
}
func chunkInputs[T any](chunkSize int, inputs []T) (chunks [][]T) {
if len(inputs) == 0 {
return
}
if chunkSize <= 0 || len(inputs) < 2 || chunkSize > len(inputs) {
return [][]T{inputs}
}
lastChunkSize := len(inputs) % chunkSize
chunkCount := len(inputs) / chunkSize
for i := 0; i < chunkCount; i++ {
start := i * chunkSize
end := start + chunkSize
chunks = append(chunks, inputs[start:end])
}
if lastChunkSize > 0 {
start := chunkCount * chunkSize
end := start + lastChunkSize
chunks = append(chunks, inputs[start:end])
}
return
}