mirror of
https://github.com/forta-network/go-multicall.git
synced 2026-05-28 18:54:01 +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.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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
|
|
@ -102,6 +103,15 @@ func (ms *multicallStub) Aggregate3(opts *bind.CallOpts, calls []contract_multic
|
|||
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) {
|
||||
r := require.New(t)
|
||||
|
||||
|
|
@ -326,6 +336,136 @@ func TestDial(t *testing.T) {
|
|||
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) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Reference in a new issue