This commit is contained in:
ziyeziye 2023-12-11 10:27:39 +00:00 committed by GitHub
commit ebcc51bd9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 6 deletions

View file

@ -19,6 +19,7 @@ go get github.com/forta-network/go-multicall
(See other examples under the `examples` directory!)
#### Multicall
```go
package main
@ -91,3 +92,67 @@ func main() {
}
}
```
#### SingleCall
```go
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/forta-network/go-multicall"
)
const (
APIURL = "https://cloudflare-eth.com"
ERC20ABI = `[
{
"constant":true,
"inputs":[
{
"name":"tokenOwner",
"type":"address"
}
],
"name":"balanceOf",
"outputs":[
{
"name":"balance",
"type":"uint256"
}
],
"payable":false,
"stateMutability":"view",
"type":"function"
}
]`
)
func main() {
caller, err := multicall.Dial(context.Background(), APIURL)
if err != nil {
panic(err)
}
contract, err := multicall.NewContract(ERC20ABI, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
if err != nil {
panic(err)
}
single, err := caller.CallSingle(nil,
contract.NewCall(
nil,
"balanceOf",
common.HexToAddress("0xcEe284F754E854890e311e3280b767F80797180d"), // Arbitrum One gateway
).Name("Arbitrum One gateway balance").SetExtend(map[string]string{
"account": "0xcEe284F754E854890e311e3280b767F80797180d",
"token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
}))
if err != nil {
return
}
fmt.Println(single.CallName, ":", single.UnpackResult()[0].(*big.Int), single.Extend)
}
```

28
call.go
View file

@ -42,6 +42,7 @@ type Call struct {
CallName string
Contract *Contract
Method string
Extend any
Inputs []any
Outputs any
CanFail bool
@ -67,6 +68,18 @@ func (call *Call) Name(name string) *Call {
return call
}
func (call *Call) SetExtend(ext any) *Call {
call.Extend = ext
return call
}
func (call *Call) UnpackResult() []interface{} {
if call.Outputs == nil {
return nil
}
return call.Outputs.([]interface{})
}
// AllowFailure sets if the call is allowed to fail. This helps avoiding a revert
// when one of the calls in the array fails.
func (call *Call) AllowFailure() *Call {
@ -76,19 +89,24 @@ func (call *Call) AllowFailure() *Call {
// Unpack unpacks and converts EVM outputs and sets struct fields.
func (call *Call) Unpack(b []byte) error {
out, err := call.Contract.ABI.Unpack(call.Method, b)
if err != nil {
return fmt.Errorf("failed to unpack '%s' outputs: %v", call.Method, err)
}
if call.Outputs == nil {
call.Outputs = out
return nil
}
t := reflect.ValueOf(call.Outputs)
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return errors.New("outputs type is not a struct")
}
out, err := call.Contract.ABI.Unpack(call.Method, b)
if err != nil {
return fmt.Errorf("failed to unpack '%s' outputs: %v", call.Method, err)
}
fieldCount := t.NumField()
for i := 0; i < fieldCount; i++ {
field := t.Field(i)

View file

@ -3,6 +3,7 @@ package multicall
import (
"context"
"fmt"
"log"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@ -47,6 +48,19 @@ func Dial(ctx context.Context, rawUrl string, multicallAddr ...string) (*Caller,
// Call makes multicalls.
func (caller *Caller) Call(opts *bind.CallOpts, calls ...*Call) ([]*Call, error) {
return caller.calls(opts, calls...)
}
func (caller *Caller) CallSingle(opts *bind.CallOpts, call *Call) (*Call, error) {
calls, err := caller.calls(opts, call)
if err != nil {
return call, fmt.Errorf("CallSingle failed: %v", err)
}
return calls[0], nil
}
func (caller *Caller) calls(opts *bind.CallOpts, calls ...*Call) ([]*Call, error) {
var multiCalls []contract_multicall.Multicall3Call3
for i, call := range calls {
@ -70,6 +84,10 @@ func (caller *Caller) Call(opts *bind.CallOpts, calls ...*Call) ([]*Call, error)
call := calls[i] // index always matches
call.Failed = !result.Success
if err := call.Unpack(result.ReturnData); err != nil {
if call.CanFail {
log.Println(fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err))
continue
}
return calls, fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err)
}
}
@ -86,7 +104,7 @@ func (caller *Caller) CallChunked(opts *bind.CallOpts, chunkSize int, cooldown t
time.Sleep(cooldown)
}
chunk, err := caller.Call(opts, chunk...)
chunk, err := caller.calls(opts, chunk...)
if err != nil {
return calls, fmt.Errorf("call chunk [%d] failed: %v", i, err)
}