diff --git a/call/constants.go b/call/constants.go index a5ab956..e366603 100644 --- a/call/constants.go +++ b/call/constants.go @@ -37,7 +37,7 @@ var DefaultChainConfigs = map[Chain]ChainConfig{ Url: "https://bsc-dataseed1.ninicoin.io", }, Ethereum: { - MultiCallAddress: "0xeefba1e63905ef1d7acba5a8513c70307c1ce441", + MultiCallAddress: "0x5BA1e12693Dc8F9c48aAD8770482f4739bEeD696", Url: "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", }, Fantom: { @@ -49,7 +49,7 @@ var DefaultChainConfigs = map[Chain]ChainConfig{ Url: "https://moonbeam.public.blastapi.io", }, Moonriver: { - MultiCallAddress: "0xaef00a0cf402d9dedd54092d9ca179be6f9e5ce3", + MultiCallAddress: "0xEae947bF407A4a4f1c5a6A73312734A2863e3855", Url: "https://moonriver.public.blastapi.io", }, Celo: { diff --git a/call/contract.go b/call/contract.go index c3c0c44..917732a 100644 --- a/call/contract.go +++ b/call/contract.go @@ -12,7 +12,7 @@ import ( ) type Argument struct { - Name string `json:"name"` + Key string `json:"key"` Type string `json:"type"` InternalType string `json:"internalType"` } @@ -40,7 +40,7 @@ type Contract struct { rawMethods map[string]string methods []Method calls []core.Call - multiCaller *core.Caller + multiCaller *core.MultiCaller } func NewContractBuilder() ContractBuilder { @@ -53,7 +53,7 @@ func NewContractBuilder() ContractBuilder { return contract.WithChainConfig(DefaultChainConfigs[Ethereum]) } -func (c *Contract) WithChainConfig(config ChainConfig) *Contract { +func (ct *Contract) WithChainConfig(config ChainConfig) *Contract { if config.MultiCallAddress == "" || config.Url == "" { panic("Invalid configuration. MultiCallAddress and Url must be set") } @@ -63,75 +63,85 @@ func (c *Contract) WithChainConfig(config ChainConfig) *Contract { panic(err) } - return c.WithClient(client).AtAddress(config.MultiCallAddress).Build() + return ct.WithClient(client).AtAddress(config.MultiCallAddress).Build() } -func (a *Contract) WithClient(ethClient *ethclient.Client) ContractBuilder { - a.ethClient = ethClient - return a +func (ct *Contract) WithClient(ethClient *ethclient.Client) ContractBuilder { + ct.ethClient = ethClient + return ct } -func (a *Contract) Build() *Contract { - return a +func (ct *Contract) Build() *Contract { + return ct } -func (a *Contract) AtAddress(address string) ContractBuilder { - caller, err := core.NewCaller(a.ethClient, common.HexToAddress(address)) +func (ct *Contract) AtAddress(address string) ContractBuilder { + caller, err := core.NewMultiCaller(ct.ethClient, common.HexToAddress(address)) if err != nil { panic(err) } - a.multiCaller = caller - return a + ct.multiCaller = caller + return ct } -func (a *Contract) AddCall(callName string, contractAddress string, method string, args ...interface{}) *Contract { - callData, err := a.contractAbi.Pack(method, args...) +func (ct *Contract) AddCall(callName string, contractAddress string, method string, args ...interface{}) *Contract { + callData, err := ct.contractAbi.Pack(method, args...) if err != nil { panic(err) } - a.calls = append(a.calls, core.Call{ + ct.calls = append(ct.calls, core.Call{ Method: method, Target: common.HexToAddress(contractAddress), - Name: callName, + Key: callName, CallData: callData, }) - return a + return ct } -func (a *Contract) AddMethod(signature string) *Contract { - existCall, ok := a.rawMethods[strings.ToLower(signature)] +func (ct *Contract) AddMethod(signature string) *Contract { + existCall, ok := ct.rawMethods[strings.ToLower(signature)] if ok { - panic("Caller named " + existCall + " is exist on ABI") + panic("MultiCaller named " + existCall + " is exist on ABI") } - a.rawMethods[strings.ToLower(signature)] = signature - a.methods = append(a.methods, parseNewMethod(signature)) - newAbi, err := repackAbi(a.methods) + ct.rawMethods[strings.ToLower(signature)] = signature + ct.methods = append(ct.methods, parseNewMethod(signature)) + newAbi, err := repackAbi(ct.methods) if err != nil { panic(err) } - a.contractAbi = newAbi + ct.contractAbi = newAbi if err != nil { panic(err) } - return a + return ct } -func (a *Contract) Abi() abi.ABI { - return a.contractAbi +func (ct *Contract) Abi() abi.ABI { + return ct.contractAbi } -func (a *Contract) Call(blockNumber *big.Int) (*big.Int, map[string][]interface{}, error) { +func (ct *Contract) Call(blockNumber *big.Int) (*big.Int, map[string][]interface{}, error) { res := make(map[string][]interface{}) - blockNumber, results, err := a.multiCaller.Execute(a.calls, blockNumber) - for _, call := range a.calls { - res[call.Name], _ = a.contractAbi.Unpack(call.Method, results[call.Name].ReturnData) + blockNumber, results, err := ct.multiCaller.StrictlyExecute(ct.calls, blockNumber) + for _, call := range ct.calls { + res[call.Key], _ = ct.contractAbi.Unpack(call.Method, results[call.Key].ReturnData) } - a.ClearCall() + ct.ClearCall() return blockNumber, res, err } -func (a *Contract) ClearCall() { - a.calls = []core.Call{} +func (ct *Contract) FlexibleCall(requireSuccess bool) (map[string][]interface{}, error) { + res := make(map[string][]interface{}) + results, err := ct.multiCaller.Execute(ct.calls, requireSuccess) + for _, call := range ct.calls { + res[call.Key], _ = ct.contractAbi.Unpack(call.Method, results[call.Key].ReturnData) + } + ct.ClearCall() + return res, err +} + +func (ct *Contract) ClearCall() { + ct.calls = []core.Call{} } func parseNewMethod(signature string) Method { @@ -159,7 +169,7 @@ func parseNewMethod(signature string) Method { for _, inParam := range params { if inParam != "" { newMethod.Inputs = append(newMethod.Inputs, Argument{ - Name: "", + Key: "", Type: strings.TrimSpace(inParam), InternalType: strings.TrimSpace(inParam), }) @@ -172,7 +182,7 @@ func parseNewMethod(signature string) Method { for _, outParam := range outputs { newMethod.Outputs = append(newMethod.Outputs, Argument{ - Name: "", + Key: "", Type: strings.TrimSpace(outParam), InternalType: strings.TrimSpace(outParam), }) @@ -186,7 +196,7 @@ func parseNewMethod(signature string) Method { for _, inParam := range params { if inParam != "" { newMethod.Inputs = append(newMethod.Inputs, Argument{ - Name: "", + Key: "", Type: strings.TrimSpace(inParam), InternalType: strings.TrimSpace(inParam), }) @@ -196,7 +206,7 @@ func parseNewMethod(signature string) Method { returnType := strings.TrimSpace(singleReturnPaths[1]) newMethod.Outputs = append(newMethod.Outputs, Argument{ - Name: "", + Key: "", Type: returnType, InternalType: returnType, }) diff --git a/call/contract_test.go b/call/contract_test.go index 37c7188..31e480f 100644 --- a/call/contract_test.go +++ b/call/contract_test.go @@ -19,6 +19,10 @@ var TestAddresses = map[Chain]string{ Moonriver: "0xE3F5a90F9cb311505cd691a46596599aA1A0AD7D", } +var TestAddressesV2 = map[Chain]string{ + Ethereum: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52", +} + func TestContractBuilder_Default(t *testing.T) { caller := NewContractBuilder().AddMethod("function totalSupply()(uint256)") _, result, err := caller. @@ -58,3 +62,17 @@ func TestContractBuilder_Call(t *testing.T) { } } } + +func TestContractBuilder_FlexibleCall(t *testing.T) { + for chain, address := range TestAddressesV2 { + caller := NewContractBuilder(). + WithChainConfig(DefaultChainConfigs[chain]). + AddMethod("totalSupply()(uint256)") + result, err := caller.AddCall("ts", address, "totalSupply").FlexibleCall(false) + if err != nil { + assert.Failf(t, "Error calling %s contract", string(chain)) + } else { + assert.Equal(t, result["ts"][0].(*big.Int).Cmp(big.NewInt(0)), 1) + } + } +} diff --git a/core/multicall_abi.go b/core/multicall_abi.go index 1f8b446..875944e 100644 --- a/core/multicall_abi.go +++ b/core/multicall_abi.go @@ -1,203 +1,15 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - package core import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription ) -// MultiCall is an auto generated low-level Go binding around an user-defined struct. type MultiCall struct { Target common.Address CallData []byte } -// Result is an auto generated low-level Go binding around an user-defined struct. -type Result struct { - Success bool - ReturnData []byte -} - -// MultiMetaData contains all meta data concerning the Multi contract. var MultiMetaData = &bind.MetaData{ ABI: "[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"structDePocketCore.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\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"structDePocketCore.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\":\"structDePocketCore.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\":\"structDePocketCore.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"tryAggregate\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"structDePocketCore.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\":\"structDePocketCore.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\":\"structDePocketCore.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", } - -// Multi is an auto generated Go binding around an Ethereum contract. -type Multi struct { - MultiCaller // Read-only binding to the contract - MultiTransactor // Write-only binding to the contract - MultiFilterer // Log filterer for contract events -} - -// MultiCaller is an auto generated read-only Go binding around an Ethereum contract. -type MultiCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// MultiTransactor is an auto generated write-only Go binding around an Ethereum contract. -type MultiTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// MultiFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type MultiFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -type MultiTransactorSession struct { - Contract *MultiTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -type MultiRaw struct { - Contract *Multi // Generic contract binding to access the raw methods on -} - -type MultiCallerRaw struct { - Contract *MultiCaller // Generic read-only contract binding to access the raw methods on -} - -type MultiTransactorRaw struct { - Contract *MultiTransactor // Generic write-only contract binding to access the raw methods on -} - -func NewMulti(address common.Address, backend bind.ContractBackend) (*Multi, error) { - contract, err := bindMulti(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Multi{MultiCaller: MultiCaller{contract: contract}, MultiTransactor: MultiTransactor{contract: contract}, MultiFilterer: MultiFilterer{contract: contract}}, nil -} - -func NewMultiCaller(address common.Address, caller bind.ContractCaller) (*MultiCaller, error) { - contract, err := bindMulti(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &MultiCaller{contract: contract}, nil -} - -func bindMulti(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(MultiMetaData.ABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -func (_Multi *MultiRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Multi.Contract.MultiCaller.contract.Call(opts, result, method, params...) -} - -func (_Multi *MultiCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Multi.Contract.contract.Call(opts, result, method, params...) -} - -func (_Multi *MultiCaller) Aggregate(opts *bind.CallOpts, calls []MultiCall) (struct { - BlockNumber *big.Int - ReturnData [][]byte -}, error) { - var out []interface{} - err := _Multi.contract.Call(opts, &out, "aggregate", calls) - - outstruct := new(struct { - BlockNumber *big.Int - ReturnData [][]byte - }) - if err != nil { - return *outstruct, err - } - - outstruct.BlockNumber = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - outstruct.ReturnData = *abi.ConvertType(out[1], new([][]byte)).(*[][]byte) - - return *outstruct, err - -} - -func (_Multi *MultiCaller) BlockAndAggregate(opts *bind.CallOpts, calls []MultiCall) (struct { - BlockNumber *big.Int - BlockHash [32]byte - ReturnData []Result -}, error) { - var out []interface{} - err := _Multi.contract.Call(opts, &out, "blockAndAggregate", calls) - - outstruct := new(struct { - BlockNumber *big.Int - BlockHash [32]byte - ReturnData []Result - }) - if err != nil { - return *outstruct, err - } - - outstruct.BlockNumber = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - outstruct.BlockHash = *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) - outstruct.ReturnData = *abi.ConvertType(out[2], new([]Result)).(*[]Result) - - return *outstruct, err - -} - -func (_Multi *MultiCaller) TryAggregate(opts *bind.CallOpts, requireSuccess bool, calls []MultiCall) ([]Result, error) { - var out []interface{} - err := _Multi.contract.Call(opts, &out, "tryAggregate", requireSuccess, calls) - - if err != nil { - return *new([]Result), err - } - - out0 := *abi.ConvertType(out[0], new([]Result)).(*[]Result) - - return out0, err - -} - -func (_Multi *MultiCaller) TryBlockAndAggregate(opts *bind.CallOpts, requireSuccess bool, calls []MultiCall) (struct { - BlockNumber *big.Int - BlockHash [32]byte - ReturnData []Result -}, error) { - var out []interface{} - err := _Multi.contract.Call(opts, &out, "tryBlockAndAggregate", requireSuccess, calls) - - outstruct := new(struct { - BlockNumber *big.Int - BlockHash [32]byte - ReturnData []Result - }) - if err != nil { - return *outstruct, err - } - - outstruct.BlockNumber = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - outstruct.BlockHash = *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) - outstruct.ReturnData = *abi.ConvertType(out[2], new([]Result)).(*[]Result) - - return *outstruct, err - -} \ No newline at end of file diff --git a/core/multicall_wrapper.go b/core/multicall_wrapper.go index 7822e2e..00c14ab 100644 --- a/core/multicall_wrapper.go +++ b/core/multicall_wrapper.go @@ -11,7 +11,7 @@ import ( ) type Call struct { - Name string `json:"name"` + Key string `json:"key"` Method string `json:"method"` Target common.Address `json:"target"` CallData []byte `json:"call_data"` @@ -19,6 +19,7 @@ type Call struct { type CallResponse struct { Method string + Status bool ReturnData []byte `json:"returnData"` } @@ -26,26 +27,26 @@ func (call Call) GetMultiCall() MultiCall { return MultiCall{Target: call.Target, CallData: call.CallData} } -type Caller struct { +type MultiCaller struct { Client *ethclient.Client Abi abi.ABI ContractAddress common.Address } -func NewCaller(client *ethclient.Client, contractAddress common.Address) (*Caller, error) { +func NewMultiCaller(client *ethclient.Client, contractAddress common.Address) (*MultiCaller, error) { mcAbi, err := abi.JSON(strings.NewReader(MultiMetaData.ABI)) if err != nil { return nil, err } - return &Caller{ + return &MultiCaller{ Client: client, Abi: mcAbi, ContractAddress: contractAddress, }, nil } -func (caller *Caller) Execute(calls []Call, blockNumber *big.Int) (*big.Int, map[string]CallResponse, error) { +func (caller *MultiCaller) StrictlyExecute(calls []Call, blockNumber *big.Int) (*big.Int, map[string]CallResponse, error) { var multiCalls = make([]MultiCall, 0, len(calls)) for _, call := range calls { multiCalls = append(multiCalls, call.GetMultiCall()) @@ -67,10 +68,45 @@ func (caller *Caller) Execute(calls []Call, blockNumber *big.Int) (*big.Int, map results := make(map[string]CallResponse) for i, response := range responses[1].([][]byte) { - results[calls[i].Name] = CallResponse{ + results[calls[i].Key] = CallResponse{ Method: calls[i].Method, + Status: true, ReturnData: response, } } return responses[0].(*big.Int), results, nil } + +func (caller *MultiCaller) Execute(calls []Call, requireSuccess bool) (map[string]CallResponse, error) { + var multiCalls = make([]MultiCall, 0, len(calls)) + for _, call := range calls { + multiCalls = append(multiCalls, call.GetMultiCall()) + } + callData, err := caller.Abi.Pack("tryAggregate", requireSuccess, multiCalls) + if err != nil { + return nil, err + } + resp, err := caller.Client.CallContract(context.Background(), ethereum.CallMsg{To: &caller.ContractAddress, Data: callData}, nil) + if err != nil { + return nil, err + } + + responses, err := caller.Abi.Unpack("tryAggregate", resp) + + if err != nil { + return nil, err + } + + results := make(map[string]CallResponse) + for i, response := range responses[0].([]struct { + Success bool `json:"success"` + ReturnData []byte `json:"returnData"` + }) { + results[calls[i].Key] = CallResponse{ + Method: calls[i].Method, + ReturnData: response.ReturnData, + Status: response.Success, + } + } + return results, nil +}