@@ -6,6 +6,7 @@ package evmreader
66import (
77 "context"
88 "errors"
9+ "math"
910 "math/big"
1011 "time"
1112
@@ -58,7 +59,8 @@ func (s *EvmReaderSuite) TestFetchApplicationInputsDuplicateAndOutOfOrderEvents(
5859 makeInputEvent (addr , 1 , 100 ), // duplicate
5960 }, nil )
6061
61- inputs , err := s .evmReader .fetchApplicationInputs (s .ctx , app , 10 , 200 )
62+ prevValue := new (big.Int ).SetUint64 (0 ) // repo returns 0 inputs
63+ inputs , err := s .evmReader .fetchInputs (s .ctx , app , 10 , 200 , prevValue , 0 , math .MaxUint64 )
6264 s .Require ().NoError (err )
6365
6466 // 3 unique inputs, sorted by index
@@ -68,6 +70,130 @@ func (s *EvmReaderSuite) TestFetchApplicationInputsDuplicateAndOutOfOrderEvents(
6870 s .Require ().Equal (uint64 (2 ), inputs [2 ].Index )
6971}
7072
73+ // --- Bounds filtering: inputs outside [lowerBound, upperBound) are excluded ---
74+ // When RetrieveInputs returns events with indices outside the requested bounds,
75+ // fetchInputs must exclude them. This exercises the bounds filtering added in the
76+ // unified fetchInputs (C2 consolidation).
77+ func (s * EvmReaderSuite ) TestFetchInputsBoundsFiltering () {
78+ addr := common .HexToAddress ("0x3333333333333333333333333333333333333333" )
79+
80+ inputSrc := & MockInputBox {}
81+ app := appContracts {
82+ application : & Application {
83+ Name : "test-app" ,
84+ IApplicationAddress : addr ,
85+ IInputBoxAddress : inputBoxAddr ,
86+ IInputBoxBlock : 10 ,
87+ },
88+ inputSource : inputSrc ,
89+ }
90+
91+ // On-chain: 0 inputs before block 100, 5 from block 100
92+ inputSrc .On ("GetNumberOfInputs" , blockRange (0 , 100 ), mock .Anything ).
93+ Return (new (big.Int ).SetUint64 (0 ), nil )
94+ inputSrc .On ("GetNumberOfInputs" , blockFrom (100 ), mock .Anything ).
95+ Return (new (big.Int ).SetUint64 (5 ), nil )
96+
97+ // RetrieveInputs returns indices 0..4, but we only want [2, 4)
98+ inputSrc .On ("RetrieveInputs" ,
99+ mock .MatchedBy (func (opts * bind.FilterOpts ) bool { return opts .Start == 100 }),
100+ mock .Anything , mock .Anything ,
101+ ).Return ([]iinputbox.IInputBoxInputAdded {
102+ makeInputEvent (addr , 0 , 100 ), // below lowerBound → excluded
103+ makeInputEvent (addr , 1 , 100 ), // below lowerBound → excluded
104+ makeInputEvent (addr , 2 , 100 ), // in bounds → included
105+ makeInputEvent (addr , 3 , 100 ), // in bounds → included
106+ makeInputEvent (addr , 4 , 100 ), // >= upperBound → excluded
107+ }, nil )
108+
109+ prevValue := new (big.Int ).SetUint64 (0 )
110+ inputs , err := s .evmReader .fetchInputs (s .ctx , app , 10 , 200 , prevValue , 2 , 4 )
111+ s .Require ().NoError (err )
112+
113+ // Only indices 2 and 3 should be included
114+ s .Require ().Len (inputs , 2 )
115+ s .Require ().Equal (uint64 (2 ), inputs [0 ].Index )
116+ s .Require ().Equal (uint64 (3 ), inputs [1 ].Index )
117+ }
118+
119+ // --- Bounds filtering: upperBound == lowerBound yields zero inputs ---
120+ // When lowerBound == upperBound, the half-open range [lb, ub) is empty,
121+ // so no inputs should be returned even if the chain has matching events.
122+ func (s * EvmReaderSuite ) TestFetchInputsEmptyBoundsRange () {
123+ addr := common .HexToAddress ("0x3333333333333333333333333333333333333333" )
124+
125+ inputSrc := & MockInputBox {}
126+ app := appContracts {
127+ application : & Application {
128+ Name : "test-app" ,
129+ IApplicationAddress : addr ,
130+ IInputBoxAddress : inputBoxAddr ,
131+ IInputBoxBlock : 10 ,
132+ },
133+ inputSource : inputSrc ,
134+ }
135+
136+ // On-chain: 0 inputs before block 100, 2 from block 100
137+ inputSrc .On ("GetNumberOfInputs" , blockRange (0 , 100 ), mock .Anything ).
138+ Return (new (big.Int ).SetUint64 (0 ), nil )
139+ inputSrc .On ("GetNumberOfInputs" , blockFrom (100 ), mock .Anything ).
140+ Return (new (big.Int ).SetUint64 (2 ), nil )
141+
142+ inputSrc .On ("RetrieveInputs" ,
143+ mock .MatchedBy (func (opts * bind.FilterOpts ) bool { return opts .Start == 100 }),
144+ mock .Anything , mock .Anything ,
145+ ).Return ([]iinputbox.IInputBoxInputAdded {
146+ makeInputEvent (addr , 0 , 100 ),
147+ makeInputEvent (addr , 1 , 100 ),
148+ }, nil )
149+
150+ // lowerBound == upperBound == 5 → empty range
151+ prevValue := new (big.Int ).SetUint64 (0 )
152+ inputs , err := s .evmReader .fetchInputs (s .ctx , app , 10 , 200 , prevValue , 5 , 5 )
153+ s .Require ().NoError (err )
154+ s .Require ().Empty (inputs )
155+ }
156+
157+ // --- Bounds filtering: boundary-exact inclusion/exclusion ---
158+ // Verifies the half-open [lowerBound, upperBound) semantics precisely:
159+ // lowerBound is inclusive, upperBound is exclusive.
160+ func (s * EvmReaderSuite ) TestFetchInputsBoundaryExactness () {
161+ addr := common .HexToAddress ("0x3333333333333333333333333333333333333333" )
162+
163+ inputSrc := & MockInputBox {}
164+ app := appContracts {
165+ application : & Application {
166+ Name : "test-app" ,
167+ IApplicationAddress : addr ,
168+ IInputBoxAddress : inputBoxAddr ,
169+ IInputBoxBlock : 10 ,
170+ },
171+ inputSource : inputSrc ,
172+ }
173+
174+ // On-chain: 0 inputs before block 100, 3 from block 100
175+ inputSrc .On ("GetNumberOfInputs" , blockRange (0 , 100 ), mock .Anything ).
176+ Return (new (big.Int ).SetUint64 (0 ), nil )
177+ inputSrc .On ("GetNumberOfInputs" , blockFrom (100 ), mock .Anything ).
178+ Return (new (big.Int ).SetUint64 (3 ), nil )
179+
180+ inputSrc .On ("RetrieveInputs" ,
181+ mock .MatchedBy (func (opts * bind.FilterOpts ) bool { return opts .Start == 100 }),
182+ mock .Anything , mock .Anything ,
183+ ).Return ([]iinputbox.IInputBoxInputAdded {
184+ makeInputEvent (addr , 0 , 100 ),
185+ makeInputEvent (addr , 1 , 100 ),
186+ makeInputEvent (addr , 2 , 100 ),
187+ }, nil )
188+
189+ // Range [1, 2): only index 1 should be included
190+ prevValue := new (big.Int ).SetUint64 (0 )
191+ inputs , err := s .evmReader .fetchInputs (s .ctx , app , 10 , 200 , prevValue , 1 , 2 )
192+ s .Require ().NoError (err )
193+ s .Require ().Len (inputs , 1 )
194+ s .Require ().Equal (uint64 (1 ), inputs [0 ].Index )
195+ }
196+
71197// --- #18: Adversarial EpochSealed data (LowerBound > UpperBound) ---
72198// When a sealed event has InputIndexLowerBound > InputIndexUpperBound,
73199// the code skips input fetching and stores the epoch without crashing.
@@ -139,7 +265,7 @@ func (s *SealedEpochsSuite) TestSealedEpochLowerBoundGreaterThanUpperBound() {
139265}
140266
141267// --- #10: RetrieveInputs failure at a specific block ---
142- // When RetrieveInputs fails during fetchSealedEpochInputs , the error must
268+ // When RetrieveInputs fails during fetchInputs , the error must
143269// propagate through FindTransitions back to the caller.
144270func (s * SealedEpochsSuite ) TestFetchSealedEpochInputsRetrieveFailure () {
145271 app := appContracts {
@@ -173,7 +299,11 @@ func (s *SealedEpochsSuite) TestFetchSealedEpochInputsRetrieveFailure() {
173299 mock .Anything , mock .Anything ,
174300 ).Return (([]iinputbox.IInputBoxInputAdded )(nil ), errors .New ("RPC timeout" ))
175301
176- _ , err := s .evmReader .fetchSealedEpochInputs (s .ctx , app , epoch )
302+ prevValue := new (big.Int ).SetUint64 (epoch .InputIndexLowerBound )
303+ _ , err := s .evmReader .fetchInputs (s .ctx , app ,
304+ epoch .FirstBlock , epoch .LastBlock ,
305+ prevValue ,
306+ epoch .InputIndexLowerBound , epoch .InputIndexUpperBound )
177307 s .Require ().Error (err )
178308 s .Require ().ErrorContains (err , "RPC timeout" )
179309 s .Require ().ErrorContains (err , "failed to walk input transitions" )
0 commit comments