1
0
Fork 0
forked from forks/go-multicall

first commit

This commit is contained in:
Caner Çıdam 2023-04-24 15:27:26 +03:00
commit b7f5bea328
19 changed files with 2360 additions and 0 deletions

27
.github/workflows/build.yml vendored Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
toolbin
coverage.out

12
.golangci.yml Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,92 @@
# go-multicall
![build](https://github.com/forta-network/go-multicall/actions/workflows/build.yml/badge.svg)
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
View 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
View 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
View 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
View 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)
}

View 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"
}
]

View 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)
}

File diff suppressed because one or more lines are too long

View 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
View 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
View 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
View 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=