From 6d059ec73f578dd2583d3cf9a0b0f0bd920593f3 Mon Sep 17 00:00:00 2001 From: cary Date: Sat, 18 Apr 2026 16:57:49 +0800 Subject: [PATCH] testcases --- .idea/.gitignore | 10 ++ .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/go-multicall.iml | 9 ++ .idea/go.imports.xml | 10 ++ .idea/golinter.xml | 7 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + call_test.go | 16 +++ caller_test.go | 140 +++++++++++++++++++++ 9 files changed, 212 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/go-multicall.iml create mode 100644 .idea/go.imports.xml create mode 100644 .idea/golinter.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..30cf57e --- /dev/null +++ b/.idea/.gitignore @@ -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 diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/go-multicall.iml b/.idea/go-multicall.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/go-multicall.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..644cdf0 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/golinter.xml b/.idea/golinter.xml new file mode 100644 index 0000000..1ccf3ec --- /dev/null +++ b/.idea/golinter.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c4b021f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/call_test.go b/call_test.go index c490245..a83900b 100644 --- a/call_test.go +++ b/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()) +} diff --git a/caller_test.go b/caller_test.go index 5a7ef33..0c084e3 100644 --- a/caller_test.go +++ b/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