From 1fbee12081afe5ede37640d71f94b55ec6faf639 Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 15:47:34 +0800 Subject: [PATCH 1/7] Support single request Outputs type supports nil --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ call.go | 10 ++++----- caller.go | 15 +++++++++++++- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a5eb2ea..69bced3 100644 --- a/README.md +++ b/README.md @@ -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,64 @@ 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")) + if err != nil { + return + } + + fmt.Println(single.CallName, ":", single.Outputs, err) +} +``` diff --git a/call.go b/call.go index b89a9e4..a4c6b54 100644 --- a/call.go +++ b/call.go @@ -2,7 +2,6 @@ package multicall import ( "bytes" - "errors" "fmt" "reflect" @@ -80,15 +79,16 @@ func (call *Call) Unpack(b []byte) error { 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) } + if t.Kind() != reflect.Struct { + call.Outputs = out + return nil + } + fieldCount := t.NumField() for i := 0; i < fieldCount; i++ { field := t.Field(i) diff --git a/caller.go b/caller.go index eb2e10d..0d8c078 100644 --- a/caller.go +++ b/caller.go @@ -47,6 +47,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 { @@ -86,7 +99,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) } From 12c2d287e92d9849b9ea5957bebd0336999879b2 Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 16:09:42 +0800 Subject: [PATCH 2/7] Outputs type supports nil --- call.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/call.go b/call.go index a4c6b54..30f0ab7 100644 --- a/call.go +++ b/call.go @@ -2,6 +2,7 @@ package multicall import ( "bytes" + "errors" "fmt" "reflect" @@ -75,20 +76,24 @@ func (call *Call) AllowFailure() *Call { // Unpack unpacks and converts EVM outputs and sets struct fields. func (call *Call) Unpack(b []byte) error { - t := reflect.ValueOf(call.Outputs) - if t.Kind() == reflect.Pointer { - t = t.Elem() - } 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 t.Kind() != reflect.Struct { + 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") + } + fieldCount := t.NumField() for i := 0; i < fieldCount; i++ { field := t.Field(i) From 4ca414886375337dee3953c37c2585458da8f6ad Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 16:26:20 +0800 Subject: [PATCH 3/7] Support extension parameter return as is --- call.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/call.go b/call.go index 30f0ab7..1858891 100644 --- a/call.go +++ b/call.go @@ -42,6 +42,7 @@ type Call struct { CallName string Contract *Contract Method string + Extend any Inputs []any Outputs any CanFail bool @@ -67,6 +68,15 @@ 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{} { + 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 { From 019903a6ac5f1cd4ff7559a652f5816d54ec6fc5 Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 16:28:52 +0800 Subject: [PATCH 4/7] README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 69bced3..1c3d59b 100644 --- a/README.md +++ b/README.md @@ -145,11 +145,14 @@ func main() { nil, "balanceOf", common.HexToAddress("0xcEe284F754E854890e311e3280b767F80797180d"), // Arbitrum One gateway - ).Name("Arbitrum One gateway balance")) + ).Name("Arbitrum One gateway balance").SetExtend(map[string]string{ + "account": "0xcEe284F754E854890e311e3280b767F80797180d", + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + })) if err != nil { return } - fmt.Println(single.CallName, ":", single.Outputs, err) + fmt.Println(single.CallName, ":", single.UnpackResult()[0].(*big.Int), single.Extend) } ``` From 07b7d5b7f36c2c83b5eb5ef0266d010ff5e2832d Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 18:14:19 +0800 Subject: [PATCH 5/7] AllowFailure nil --- call.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/call.go b/call.go index 1858891..8929516 100644 --- a/call.go +++ b/call.go @@ -74,6 +74,9 @@ func (call *Call) SetExtend(ext any) *Call { } func (call *Call) UnpackResult() []interface{} { + if call.Outputs == nil { + return nil + } return call.Outputs.([]interface{}) } From fa1d05a4e7d840aeffdc64ac82a036d0c94f63e2 Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 18:25:30 +0800 Subject: [PATCH 6/7] CanFail unpack error --- caller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caller.go b/caller.go index 0d8c078..f80abd3 100644 --- a/caller.go +++ b/caller.go @@ -82,7 +82,7 @@ func (caller *Caller) calls(opts *bind.CallOpts, calls ...*Call) ([]*Call, error for i, result := range results { call := calls[i] // index always matches call.Failed = !result.Success - if err := call.Unpack(result.ReturnData); err != nil { + if err := call.Unpack(result.ReturnData); err != nil && !call.CanFail { return calls, fmt.Errorf("failed to unpack call outputs at index [%d]: %v", i, err) } } From c1a22458bfefa1a01ae9c69889d7853cae378b43 Mon Sep 17 00:00:00 2001 From: ziyeziye Date: Mon, 11 Dec 2023 18:27:30 +0800 Subject: [PATCH 7/7] CanFail unpack error --- caller.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/caller.go b/caller.go index f80abd3..4c04d2b 100644 --- a/caller.go +++ b/caller.go @@ -3,6 +3,7 @@ package multicall import ( "context" "fmt" + "log" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -82,7 +83,11 @@ func (caller *Caller) calls(opts *bind.CallOpts, calls ...*Call) ([]*Call, error for i, result := range results { call := calls[i] // index always matches call.Failed = !result.Success - if err := call.Unpack(result.ReturnData); err != nil && !call.CanFail { + 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) } }