mirror of
https://github.com/forta-network/go-multicall.git
synced 2026-07-15 17:30:46 +00:00
testcases
This commit is contained in:
parent
c6cbb984a3
commit
6d059ec73f
9 changed files with 212 additions and 0 deletions
10
.idea/.gitignore
vendored
Normal file
10
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
6
.idea/copilot.data.migration.ask2agent.xml
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/go-multicall.iml
Normal file
9
.idea/go-multicall.iml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
10
.idea/go.imports.xml
Normal file
10
.idea/go.imports.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GoImports">
|
||||||
|
<option name="excludedPackages">
|
||||||
|
<array>
|
||||||
|
<option value="golang.org/x/net/context" />
|
||||||
|
</array>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/golinter.xml
Normal file
7
.idea/golinter.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GoLinterSettings">
|
||||||
|
<option name="customConfigFile" value="$PROJECT_DIR$/.golangci.yml" />
|
||||||
|
<option name="useCustomConfigFile" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/go-multicall.iml" filepath="$PROJECT_DIR$/.idea/go-multicall.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
16
call_test.go
16
call_test.go
|
|
@ -35,3 +35,19 @@ func TestCall_BadABI(t *testing.T) {
|
||||||
r.Error(err)
|
r.Error(err)
|
||||||
r.ErrorContains(err, "unexpected EOF")
|
r.ErrorContains(err, "unexpected EOF")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUnpackResult_SafeTypeAssertion verifies UnpackResult never panics regardless of
|
||||||
|
// the concrete type stored in Outputs.
|
||||||
|
func TestUnpackResult_SafeTypeAssertion(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
// nil Outputs → nil
|
||||||
|
r.Nil((&Call{}).UnpackResult())
|
||||||
|
|
||||||
|
// struct pointer (common case) → nil, no panic
|
||||||
|
r.Nil((&Call{Outputs: new(struct{ Val bool })}).UnpackResult())
|
||||||
|
|
||||||
|
// []interface{} → returns the slice as-is
|
||||||
|
out := []interface{}{true, "hello"}
|
||||||
|
r.Equal(out, (&Call{Outputs: out}).UnpackResult())
|
||||||
|
}
|
||||||
|
|
|
||||||
140
caller_test.go
140
caller_test.go
|
|
@ -2,6 +2,7 @@ package multicall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -102,6 +103,15 @@ func (ms *multicallStub) Aggregate3(opts *bind.CallOpts, calls []contract_multic
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// multicallResultStub allows full control over per-call Success flags and errors.
|
||||||
|
type multicallResultStub struct {
|
||||||
|
results func(calls []contract_multicall.Multicall3Call3) ([]contract_multicall.Multicall3Result, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *multicallResultStub) Aggregate3(opts *bind.CallOpts, calls []contract_multicall.Multicall3Call3) ([]contract_multicall.Multicall3Result, error) {
|
||||||
|
return ms.results(calls)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCaller_TwoCalls(t *testing.T) {
|
func TestCaller_TwoCalls(t *testing.T) {
|
||||||
r := require.New(t)
|
r := require.New(t)
|
||||||
|
|
||||||
|
|
@ -326,6 +336,136 @@ func TestDial(t *testing.T) {
|
||||||
r.NotNil(caller)
|
r.NotNil(caller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boolOut is a helper output struct used across CanFail tests.
|
||||||
|
type boolOut struct {
|
||||||
|
Val1 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// validBoolData packs and strips the 4-byte selector, returning raw ABI-encoded output for true.
|
||||||
|
func validBoolData(t *testing.T, c *Contract) []byte {
|
||||||
|
t.Helper()
|
||||||
|
packed, err := c.ABI.Pack("testFunc", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Pack: %v", err)
|
||||||
|
}
|
||||||
|
return packed[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCaller_CanFail_BadReturnData verifies that when a CanFail call receives malformed
|
||||||
|
// return data, only that call is marked Failed; other calls are unaffected and no error
|
||||||
|
// is returned to the caller.
|
||||||
|
func TestCaller_CanFail_BadReturnData(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
c1, err := NewContract(oneValueABI, testAddr1)
|
||||||
|
r.NoError(err)
|
||||||
|
c2, err := NewContract(oneValueABI, testAddr2)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
valid := validBoolData(t, c1)
|
||||||
|
|
||||||
|
call1 := c1.NewCall(new(boolOut), "testFunc", true)
|
||||||
|
call2 := c2.NewCall(new(boolOut), "testFunc", true).AllowFailure() // bad data below
|
||||||
|
call3 := c1.NewCall(new(boolOut), "testFunc", true)
|
||||||
|
|
||||||
|
caller := &Caller{
|
||||||
|
contract: &multicallResultStub{
|
||||||
|
results: func(calls []contract_multicall.Multicall3Call3) ([]contract_multicall.Multicall3Result, error) {
|
||||||
|
return []contract_multicall.Multicall3Result{
|
||||||
|
{Success: true, ReturnData: valid},
|
||||||
|
{Success: true, ReturnData: []byte{0xde, 0xad}}, // garbage — ABI decode fails
|
||||||
|
{Success: true, ReturnData: valid},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
calls, err := caller.Call(nil, call1, call2, call3)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Len(calls, 3)
|
||||||
|
|
||||||
|
r.False(calls[0].Failed)
|
||||||
|
r.Equal(true, calls[0].Outputs.(*boolOut).Val1)
|
||||||
|
|
||||||
|
r.True(calls[1].Failed) // ABI decode failed, CanFail=true → marked Failed, no error
|
||||||
|
|
||||||
|
r.False(calls[2].Failed)
|
||||||
|
r.Equal(true, calls[2].Outputs.(*boolOut).Val1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCaller_CanFail_OnChainFailure verifies that when a CanFail call reverts on-chain
|
||||||
|
// (Success=false), it is marked Failed and does not affect other calls.
|
||||||
|
func TestCaller_CanFail_OnChainFailure(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
c, err := NewContract(oneValueABI, testAddr1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
valid := validBoolData(t, c)
|
||||||
|
|
||||||
|
call1 := c.NewCall(new(boolOut), "testFunc", true)
|
||||||
|
call2 := c.NewCall(new(boolOut), "testFunc", true).AllowFailure()
|
||||||
|
|
||||||
|
caller := &Caller{
|
||||||
|
contract: &multicallResultStub{
|
||||||
|
results: func(calls []contract_multicall.Multicall3Call3) ([]contract_multicall.Multicall3Result, error) {
|
||||||
|
return []contract_multicall.Multicall3Result{
|
||||||
|
{Success: true, ReturnData: valid},
|
||||||
|
{Success: false, ReturnData: []byte{}}, // on-chain revert
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
calls, err := caller.Call(nil, call1, call2)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Len(calls, 2)
|
||||||
|
|
||||||
|
r.False(calls[0].Failed)
|
||||||
|
r.Equal(true, calls[0].Outputs.(*boolOut).Val1)
|
||||||
|
|
||||||
|
r.True(calls[1].Failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCallChunked_ReturnsPartialOnError verifies that when a chunk fails, the results
|
||||||
|
// from previously successful chunks are returned alongside the error.
|
||||||
|
func TestCallChunked_ReturnsPartialOnError(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
c, err := NewContract(oneValueABI, testAddr1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
valid := validBoolData(t, c)
|
||||||
|
|
||||||
|
call1 := c.NewCall(new(boolOut), "testFunc", true)
|
||||||
|
call2 := c.NewCall(new(boolOut), "testFunc", true)
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
caller := &Caller{
|
||||||
|
contract: &multicallResultStub{
|
||||||
|
results: func(calls []contract_multicall.Multicall3Call3) ([]contract_multicall.Multicall3Result, error) {
|
||||||
|
callCount++
|
||||||
|
if callCount == 2 {
|
||||||
|
return nil, fmt.Errorf("rpc error on second chunk")
|
||||||
|
}
|
||||||
|
return []contract_multicall.Multicall3Result{
|
||||||
|
{Success: true, ReturnData: valid},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// chunkSize=1: call1 → chunk 0 (succeeds), call2 → chunk 1 (fails)
|
||||||
|
result, err := caller.CallChunked(nil, 1, 0, call1, call2)
|
||||||
|
r.Error(err)
|
||||||
|
r.ErrorContains(err, "chunk [1]")
|
||||||
|
|
||||||
|
// partial results: only the successful first chunk is returned
|
||||||
|
r.Len(result, 1)
|
||||||
|
r.False(result[0].Failed)
|
||||||
|
r.Equal(true, result[0].Outputs.(*boolOut).Val1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestChunkInputs(t *testing.T) {
|
func TestChunkInputs(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue