forked from forks/go-multicall
first commit
This commit is contained in:
commit
b7f5bea328
19 changed files with 2360 additions and 0 deletions
27
.github/workflows/build.yml
vendored
Normal file
27
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
go:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19'
|
||||
cache: false
|
||||
- name: Test
|
||||
run: make test
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
skip-go-installation: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
version: v1.52.2
|
||||
45
.github/workflows/coverage.yml
vendored
Normal file
45
.github/workflows/coverage.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: coverage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: Update coverage badge
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19'
|
||||
cache: false
|
||||
- name: Run Test
|
||||
run: make cover
|
||||
- name: Go Coverage Badge
|
||||
uses: tj-actions/coverage-badge-go@v2
|
||||
with:
|
||||
text: coverage
|
||||
filename: coverage.out
|
||||
- name: Verify Changed files
|
||||
uses: tj-actions/verify-changed-files@v12
|
||||
id: verify-changed-files
|
||||
with:
|
||||
files: README.md
|
||||
- name: Commit changes
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add README.md
|
||||
git commit -m "chore: update coverage badge"
|
||||
- name: Push changes
|
||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
branch: ${{ github.head_ref }}
|
||||
25
.github/workflows/pull_request.yml
vendored
Normal file
25
.github/workflows/pull_request.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
go:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.19'
|
||||
cache: false
|
||||
- name: Test
|
||||
run: make test
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
skip-go-installation: true
|
||||
skip-pkg-cache: true
|
||||
skip-build-cache: true
|
||||
version: v1.52.2
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
toolbin
|
||||
coverage.out
|
||||
12
.golangci.yml
Normal file
12
.golangci.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
linters-settings:
|
||||
gosimple:
|
||||
go: "1.19"
|
||||
|
||||
staticcheck:
|
||||
go: "1.19"
|
||||
|
||||
stylecheck:
|
||||
go: "1.19"
|
||||
|
||||
unused:
|
||||
go: "1.19"
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2023 Forta Foundation and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
43
Makefile
Normal file
43
Makefile
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
export GOBIN = $(shell pwd)/toolbin
|
||||
|
||||
LINT = $(GOBIN)/golangci-lint
|
||||
FORMAT = $(GOBIN)/goimports
|
||||
|
||||
ABIGEN = $(GOBIN)/abigen
|
||||
|
||||
.PHONY: tools
|
||||
tools:
|
||||
@echo 'Installing tools...'
|
||||
@rm -rf toolbin
|
||||
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2
|
||||
@go install golang.org/x/tools/cmd/goimports@v0.1.11
|
||||
|
||||
@go install github.com/ethereum/go-ethereum/cmd/abigen@v1.11.5
|
||||
|
||||
.PHONY: require-tools
|
||||
require-tools: tools
|
||||
@echo 'Checking installed tools...'
|
||||
@file $(LINT) > /dev/null
|
||||
@file $(FORMAT) > /dev/null
|
||||
|
||||
@file $(ABIGEN) > /dev/null
|
||||
|
||||
@echo "All tools found in $(GOBIN)!"
|
||||
|
||||
.PHONY: generate
|
||||
generate: require-tools
|
||||
@$(ABIGEN) --out contracts/contract_multicall/multicall.go \
|
||||
--abi contracts/contract_multicall/abi.json --pkg contract_multicall \
|
||||
--type Multicall
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v -count=1 -covermode=count -coverprofile=coverage.out
|
||||
|
||||
.PHONY: cover
|
||||
cover: test
|
||||
go tool cover -func=coverage.out -o=coverage.out
|
||||
|
||||
.PHONY: coverage
|
||||
coverage: test
|
||||
go tool cover -func=coverage.out | grep total | awk '{print substr($$3, 1, length($$3)-1)}'
|
||||
92
README.md
Normal file
92
README.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# go-multicall
|
||||

|
||||
|
||||
A thin Go client for making multiple function calls in single `eth_call` request
|
||||
|
||||
- Uses the go-ethereum tools and libraries
|
||||
- Interfaces with the [MakerDAO `Multicall3` contract](https://github.com/mds1/multicall)
|
||||
|
||||
_**Warning:** MakerDAO Multicall contracts are different than the [forta-network Multicall contract](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Multicall.sol). Please see [this thread](https://forum.openzeppelin.com/t/multicall-by-oz-and-makerdao-has-a-difference/9350) in the forta-network forum if you are looking for an explanation._
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get github.com/forta-network/go-multicall
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
(See other examples under the `examples` directory!)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/forta-network/go-multicall"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
]`
|
||||
)
|
||||
|
||||
type balanceOutput struct {
|
||||
Balance *big.Int
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil,
|
||||
contract.NewCall(
|
||||
new(balanceOutput),
|
||||
"balanceOf",
|
||||
common.HexToAddress("0xcEe284F754E854890e311e3280b767F80797180d"), // Arbitrum One gateway
|
||||
).Name("Arbitrum One gateway balance"),
|
||||
contract.NewCall(
|
||||
new(balanceOutput),
|
||||
"balanceOf",
|
||||
common.HexToAddress("0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf"), // Polygon ERC20 bridge
|
||||
).Name("Polygon ERC20 bridge balance"),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, call := range calls {
|
||||
fmt.Println(call.CallName, ":", call.Outputs.(*balanceOutput).Balance)
|
||||
}
|
||||
}
|
||||
```
|
||||
112
call.go
Normal file
112
call.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package multicall
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// Contract wraps the parsed ABI and acts as a call factory.
|
||||
type Contract struct {
|
||||
ABI *abi.ABI
|
||||
Address common.Address
|
||||
}
|
||||
|
||||
// NewContract creates a new call factory.
|
||||
func NewContract(rawJson, address string) (*Contract, error) {
|
||||
parsedABI, err := ParseABI(rawJson)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Contract{
|
||||
ABI: parsedABI,
|
||||
Address: common.HexToAddress(address),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseABI parses raw ABI JSON.
|
||||
func ParseABI(rawJson string) (*abi.ABI, error) {
|
||||
parsed, err := abi.JSON(bytes.NewBufferString(rawJson))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse abi: %v", err)
|
||||
}
|
||||
return &parsed, nil
|
||||
}
|
||||
|
||||
// Call wraps a multicall call.
|
||||
type Call struct {
|
||||
CallName string
|
||||
Contract *Contract
|
||||
Method string
|
||||
Inputs []any
|
||||
Outputs any
|
||||
CanFail bool
|
||||
Failed bool
|
||||
}
|
||||
|
||||
// NewCall creates a new call using given inputs.
|
||||
// Outputs type is the expected output struct to unpack and set values in.
|
||||
func (contract *Contract) NewCall(
|
||||
outputs any, methodName string, inputs ...any,
|
||||
) *Call {
|
||||
return &Call{
|
||||
Contract: contract,
|
||||
Method: methodName,
|
||||
Inputs: inputs,
|
||||
Outputs: outputs,
|
||||
}
|
||||
}
|
||||
|
||||
// Name sets a name for the call.
|
||||
func (call *Call) Name(name string) *Call {
|
||||
call.CallName = name
|
||||
return call
|
||||
}
|
||||
|
||||
// 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 {
|
||||
call.CanFail = true
|
||||
return 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()
|
||||
}
|
||||
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)
|
||||
converted := abi.ConvertType(out[i], field.Interface())
|
||||
field.Set(reflect.ValueOf(converted))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pack converts and packs EVM inputs.
|
||||
func (call *Call) Pack() ([]byte, error) {
|
||||
if len(call.Inputs) == 0 {
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
b, err := call.Contract.ABI.Pack(call.Method, call.Inputs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to pack '%s' inputs: %v", call.Method, err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
37
call_test.go
Normal file
37
call_test.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package multicall
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCall_BadABI(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
const oneValueABI = `[
|
||||
{
|
||||
"constant":true,
|
||||
"inputs": [
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"name":"testFunc",
|
||||
"outputs": [
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
` // missing closing ] at the end
|
||||
|
||||
_, err := NewContract(oneValueABI, "0x")
|
||||
r.Error(err)
|
||||
r.ErrorContains(err, "unexpected EOF")
|
||||
}
|
||||
77
caller.go
Normal file
77
caller.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package multicall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
}
|
||||
328
caller_test.go
Normal file
328
caller_test.go
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
package multicall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/forta-network/go-multicall/contracts/contract_multicall"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testType struct {
|
||||
Val1 bool
|
||||
Val2 string
|
||||
Val3 []string
|
||||
Val4 []*big.Int
|
||||
Val5 *big.Int
|
||||
Val6 common.Address
|
||||
}
|
||||
|
||||
const (
|
||||
testAddr1 = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
testAddr2 = "0x64d5192F03bD98dB1De2AA8B4abAC5419eaC32CE"
|
||||
)
|
||||
|
||||
const testABI = `[
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"name":"val2",
|
||||
"type":"string"
|
||||
},
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"string[]"
|
||||
},
|
||||
{
|
||||
"name":"val4",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"val5",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"name":"val6",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"name":"testFunc",
|
||||
"outputs":[
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"name":"val2",
|
||||
"type":"string"
|
||||
},
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"string[]"
|
||||
},
|
||||
{
|
||||
"name":"val4",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"name":"val5",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"name":"val6",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]`
|
||||
|
||||
type multicallStub struct {
|
||||
returnData func(calls []contract_multicall.Multicall3Call3) [][]byte
|
||||
}
|
||||
|
||||
func (ms *multicallStub) Aggregate3(opts *bind.CallOpts, calls []contract_multicall.Multicall3Call3) (results []contract_multicall.Multicall3Result, err error) {
|
||||
allReturnData := ms.returnData(calls)
|
||||
for _, returnData := range allReturnData {
|
||||
results = append(results, contract_multicall.Multicall3Result{
|
||||
Success: true,
|
||||
ReturnData: returnData,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestCaller_TwoCalls(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testContract1, err := NewContract(testABI, testAddr1)
|
||||
r.NoError(err)
|
||||
|
||||
testContract2, err := NewContract(testABI, testAddr2)
|
||||
r.NoError(err)
|
||||
|
||||
values1 := testType{
|
||||
Val1: true,
|
||||
Val2: "val2",
|
||||
Val3: []string{"val3_1", "val3_2"},
|
||||
Val4: []*big.Int{big.NewInt(123), big.NewInt(456)},
|
||||
Val5: big.NewInt(678),
|
||||
Val6: common.HexToAddress(testAddr1),
|
||||
}
|
||||
|
||||
call1 := testContract1.NewCall(
|
||||
new(testType), "testFunc",
|
||||
values1.Val1, values1.Val2, values1.Val3,
|
||||
values1.Val4, values1.Val5, values1.Val6,
|
||||
)
|
||||
|
||||
values2 := testType{
|
||||
Val1: false,
|
||||
Val2: "val2_alt",
|
||||
Val3: []string{"val3_1_alt", "val3_2_alt"},
|
||||
Val4: []*big.Int{big.NewInt(1239), big.NewInt(4569)},
|
||||
Val5: big.NewInt(6789),
|
||||
Val6: common.HexToAddress(testAddr2),
|
||||
}
|
||||
|
||||
call2 := testContract2.NewCall(
|
||||
new(testType), "testFunc",
|
||||
values2.Val1, values2.Val2, values2.Val3,
|
||||
values2.Val4, values2.Val5, values2.Val6,
|
||||
)
|
||||
|
||||
caller := &Caller{
|
||||
contract: &multicallStub{
|
||||
returnData: func(calls []contract_multicall.Multicall3Call3) [][]byte {
|
||||
return [][]byte{
|
||||
// return inputs as outputs by stripping the method prefix
|
||||
calls[0].CallData[4:],
|
||||
calls[1].CallData[4:],
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil, call1, call2)
|
||||
r.NoError(err)
|
||||
|
||||
call1Out := calls[0].Outputs.(*testType)
|
||||
r.Equal(values1.Val1, call1Out.Val1)
|
||||
r.Equal(values1.Val2, call1Out.Val2)
|
||||
r.Equal(values1.Val3, call1Out.Val3)
|
||||
r.Equal(values1.Val4, call1Out.Val4)
|
||||
r.Equal(values1.Val5, call1Out.Val5)
|
||||
r.Equal(values1.Val6, call1Out.Val6)
|
||||
|
||||
call2Out := calls[1].Outputs.(*testType)
|
||||
r.Equal(values2.Val1, call2Out.Val1)
|
||||
r.Equal(values2.Val2, call2Out.Val2)
|
||||
r.Equal(values2.Val3, call2Out.Val3)
|
||||
r.Equal(values2.Val4, call2Out.Val4)
|
||||
r.Equal(values2.Val5, call2Out.Val5)
|
||||
r.Equal(values2.Val6, call2Out.Val6)
|
||||
}
|
||||
|
||||
const emptyABI = `[
|
||||
{
|
||||
"constant":true,
|
||||
"inputs": [],
|
||||
"name":"testFunc",
|
||||
"outputs": [],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]`
|
||||
|
||||
func TestCaller_EmptyCall(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testContract, err := NewContract(emptyABI, testAddr1)
|
||||
r.NoError(err)
|
||||
|
||||
call := testContract.NewCall(
|
||||
new(struct{}), "testFunc",
|
||||
// no inputs
|
||||
)
|
||||
|
||||
caller := &Caller{
|
||||
contract: &multicallStub{
|
||||
returnData: func(calls []contract_multicall.Multicall3Call3) [][]byte {
|
||||
return [][]byte{
|
||||
// return empty output
|
||||
make([]byte, 0),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil, call)
|
||||
r.NoError(err)
|
||||
r.Len(calls, 1)
|
||||
}
|
||||
|
||||
const oneValueABI = `[
|
||||
{
|
||||
"constant":true,
|
||||
"inputs": [
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"name":"testFunc",
|
||||
"outputs": [
|
||||
{
|
||||
"name":"val1",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]`
|
||||
|
||||
func TestCaller_BadInput(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testContract, err := NewContract(oneValueABI, testAddr1)
|
||||
r.NoError(err)
|
||||
|
||||
call := testContract.NewCall(
|
||||
new(struct{}), "testFunc",
|
||||
'a',
|
||||
)
|
||||
|
||||
caller := &Caller{
|
||||
contract: &multicallStub{
|
||||
returnData: func(calls []contract_multicall.Multicall3Call3) [][]byte {
|
||||
return [][]byte{
|
||||
// return bad output
|
||||
{},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil, call)
|
||||
r.Error(err)
|
||||
r.ErrorContains(err, "cannot use")
|
||||
r.Len(calls, 1)
|
||||
}
|
||||
|
||||
func TestCaller_BadOutput(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testContract, err := NewContract(emptyABI, testAddr1)
|
||||
r.NoError(err)
|
||||
|
||||
call := testContract.NewCall(
|
||||
new(struct{}), "testFunc",
|
||||
// no inputs
|
||||
)
|
||||
|
||||
caller := &Caller{
|
||||
contract: &multicallStub{
|
||||
returnData: func(calls []contract_multicall.Multicall3Call3) [][]byte {
|
||||
return [][]byte{
|
||||
// return bad output
|
||||
{'a'},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil, call)
|
||||
r.Error(err)
|
||||
r.Len(calls, 1)
|
||||
}
|
||||
|
||||
func TestCaller_WrongOutputsType(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
testContract, err := NewContract(oneValueABI, testAddr1)
|
||||
r.NoError(err)
|
||||
|
||||
call := testContract.NewCall(
|
||||
new([]struct{}), "testFunc",
|
||||
true,
|
||||
)
|
||||
|
||||
packedOutput, err := testContract.ABI.Pack("testFunc", true)
|
||||
r.NoError(err)
|
||||
|
||||
caller := &Caller{
|
||||
contract: &multicallStub{
|
||||
returnData: func(calls []contract_multicall.Multicall3Call3) [][]byte {
|
||||
return [][]byte{
|
||||
packedOutput,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil, call)
|
||||
r.Error(err)
|
||||
r.ErrorContains(err, "not a struct")
|
||||
r.Len(calls, 1)
|
||||
}
|
||||
|
||||
func TestDial(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
caller, err := Dial(context.Background(), "https://polygon-rpc.com")
|
||||
r.NoError(err)
|
||||
r.NotNil(caller)
|
||||
}
|
||||
456
contracts/contract_multicall/abi.json
Normal file
456
contracts/contract_multicall/abi.json
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
[
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"aggregate",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"blockNumber",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes[]",
|
||||
"name":"returnData",
|
||||
"type":"bytes[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"allowFailure",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call3[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"aggregate3",
|
||||
"outputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"success",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"returnData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Result[]",
|
||||
"name":"returnData",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"allowFailure",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"value",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call3Value[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"aggregate3Value",
|
||||
"outputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"success",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"returnData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Result[]",
|
||||
"name":"returnData",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"blockAndAggregate",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"blockNumber",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes32",
|
||||
"name":"blockHash",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"success",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"returnData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Result[]",
|
||||
"name":"returnData",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getBasefee",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"basefee",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"blockNumber",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"getBlockHash",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"bytes32",
|
||||
"name":"blockHash",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getBlockNumber",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"blockNumber",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getChainId",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"chainid",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getCurrentBlockCoinbase",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"coinbase",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getCurrentBlockDifficulty",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"difficulty",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getCurrentBlockGasLimit",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"gaslimit",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getCurrentBlockTimestamp",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"timestamp",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"addr",
|
||||
"type":"address"
|
||||
}
|
||||
],
|
||||
"name":"getEthBalance",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"balance",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
|
||||
],
|
||||
"name":"getLastBlockHash",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"bytes32",
|
||||
"name":"blockHash",
|
||||
"type":"bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"requireSuccess",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"tryAggregate",
|
||||
"outputs":[
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"success",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"returnData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Result[]",
|
||||
"name":"returnData",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"requireSuccess",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"target",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"callData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Call[]",
|
||||
"name":"calls",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"name":"tryBlockAndAggregate",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"blockNumber",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes32",
|
||||
"name":"blockHash",
|
||||
"type":"bytes32"
|
||||
},
|
||||
{
|
||||
"components":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"success",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"bytes",
|
||||
"name":"returnData",
|
||||
"type":"bytes"
|
||||
}
|
||||
],
|
||||
"internalType":"struct Multicall3.Result[]",
|
||||
"name":"returnData",
|
||||
"type":"tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]
|
||||
10
contracts/contract_multicall/interface.go
Normal file
10
contracts/contract_multicall/interface.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package contract_multicall
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
)
|
||||
|
||||
// Interface is an abstraction of the contract.
|
||||
type Interface interface {
|
||||
Aggregate3(opts *bind.CallOpts, calls []Multicall3Call3) ([]Multicall3Result, error)
|
||||
}
|
||||
756
contracts/contract_multicall/multicall.go
Normal file
756
contracts/contract_multicall/multicall.go
Normal file
File diff suppressed because one or more lines are too long
108
examples/agent_registry/main.go
Normal file
108
examples/agent_registry/main.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/forta-network/go-multicall"
|
||||
)
|
||||
|
||||
const (
|
||||
APIURL = "https://polygon-rpc.com"
|
||||
AgentRegistryABI = `[
|
||||
{
|
||||
"inputs":[
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"agentId",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"name":"getAgentState",
|
||||
"outputs":[
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"registered",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"address",
|
||||
"name":"owner",
|
||||
"type":"address"
|
||||
},
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"agentVersion",
|
||||
"type":"uint256"
|
||||
},
|
||||
{
|
||||
"internalType":"string",
|
||||
"name":"metadata",
|
||||
"type":"string"
|
||||
},
|
||||
{
|
||||
"internalType":"uint256[]",
|
||||
"name":"chainIds",
|
||||
"type":"uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType":"bool",
|
||||
"name":"enabled",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"internalType":"uint256",
|
||||
"name":"disabledFlags",
|
||||
"type":"uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
}
|
||||
]`
|
||||
)
|
||||
|
||||
type agentState struct {
|
||||
Registered bool
|
||||
Owner common.Address
|
||||
AgentVersion *big.Int
|
||||
Metadata string
|
||||
ChainIds []*big.Int
|
||||
Enabled bool
|
||||
DisabledFlags *big.Int
|
||||
}
|
||||
|
||||
func main() {
|
||||
caller, err := multicall.Dial(context.Background(), APIURL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Forta AgentRegistry
|
||||
agentReg, err := multicall.NewContract(AgentRegistryABI, "0x61447385B019187daa48e91c55c02AF1F1f3F863")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil,
|
||||
agentReg.NewCall(
|
||||
new(agentState),
|
||||
"getAgentState",
|
||||
botHexToBigInt("0x80ed808b586aeebe9cdd4088ea4dea0a8e322909c0e4493c993e060e89c09ed1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("owner:", calls[0].Outputs.(*agentState).Owner.String())
|
||||
|
||||
b, _ := json.MarshalIndent(calls[0].Outputs.(*agentState), "", " ")
|
||||
fmt.Println(string(b))
|
||||
}
|
||||
|
||||
func botHexToBigInt(hex string) *big.Int {
|
||||
return common.HexToHash(hex).Big()
|
||||
}
|
||||
70
examples/balance/main.go
Normal file
70
examples/balance/main.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"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"
|
||||
}
|
||||
]`
|
||||
)
|
||||
|
||||
type balanceOutput struct {
|
||||
Balance *big.Int
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
calls, err := caller.Call(nil,
|
||||
contract.NewCall(
|
||||
new(balanceOutput),
|
||||
"balanceOf",
|
||||
common.HexToAddress("0xcEe284F754E854890e311e3280b767F80797180d"), // Arbitrum One gateway
|
||||
).Name("Arbitrum One gateway balance"),
|
||||
contract.NewCall(
|
||||
new(balanceOutput),
|
||||
"balanceOf",
|
||||
common.HexToAddress("0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf"), // Polygon ERC20 bridge
|
||||
).Name("Polygon ERC20 bridge balance"),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, call := range calls {
|
||||
fmt.Println(call.CallName, ":", call.Outputs.(*balanceOutput).Balance)
|
||||
}
|
||||
}
|
||||
30
go.mod
Normal file
30
go.mod
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
module github.com/forta-network/go-multicall
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ethereum/go-ethereum v1.11.5
|
||||
github.com/stretchr/testify v1.8.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.5 // indirect
|
||||
github.com/tklauser/numcpus v0.2.2 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
108
go.sum
Normal file
108
go.sum
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk=
|
||||
github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/ethereum/go-ethereum v1.11.5 h1:3M1uan+LAUvdn+7wCEFrcMM4LJTeuxDrPTg/f31a5QQ=
|
||||
github.com/ethereum/go-ethereum v1.11.5/go.mod h1:it7x0DWnTDMfVFdXcU6Ti4KEFQynLHVRarcSlPr0HBo=
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
|
||||
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
|
||||
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
|
||||
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
|
||||
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
||||
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
|
||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
|
||||
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
Loading…
Reference in a new issue