@@ -101,6 +101,59 @@ TEST(DebugLogSimulationTest, MaxMemoryReadsExceeded)
101101 EXPECT_THAT (debug_logger.dump_logs (), SizeIs (0 ));
102102}
103103
104+ TEST (DebugLogSimulationTest, CumulativeMaxMemoryReadsExceeded)
105+ {
106+ StrictMock<MockMemory> memory;
107+ std::vector<std::string> log_messages;
108+ // Each DEBUGLOG below consumes 5 reads (1 level + 1 fields_size + 2 message + 1 field). A budget of 9 admits the
109+ // first call but must reject the second, since the limit is cumulative across the whole simulation.
110+ DebugLogger debug_logger (
111+ DebugLogLevel::INFO , 9 , [&log_messages](const std::string& message) { log_messages.push_back (message); });
112+
113+ AztecAddress contract_address = 42 ;
114+ MemoryAddress level_offset = 50 ;
115+ MemoryAddress message_offset = 100 ;
116+ MemoryAddress fields_offset = 200 ;
117+ MemoryAddress fields_size_offset = 300 ;
118+
119+ std::array<MemoryValue, 2 > message_data = {
120+ MemoryValue::from<FF >(' H' ), // 'H'
121+ MemoryValue::from<FF >(' i' ), // 'i'
122+ };
123+ uint16_t message_size = message_data.size ();
124+
125+ std::array<MemoryValue, 1 > fields_data = { MemoryValue::from<FF >(42 ) };
126+ uint32_t fields_size = fields_data.size ();
127+
128+ MemoryValue level = MemoryValue::from<uint8_t >(static_cast <uint8_t >(DebugLogLevel::FATAL ));
129+ MemoryValue fields_size_value = MemoryValue::from<uint32_t >(fields_size);
130+
131+ // Both calls read the level and fields_size; only the first call gets far enough to read the message and fields
132+ // (the second throws on the cumulative bounds check before any further reads).
133+ EXPECT_CALL (memory, get (level_offset)).Times (2 ).WillRepeatedly (ReturnRef (level));
134+ EXPECT_CALL (memory, get (fields_size_offset)).Times (2 ).WillRepeatedly (ReturnRef (fields_size_value));
135+ for (uint32_t i = 0 ; i < message_size; ++i) {
136+ EXPECT_CALL (memory, get (message_offset + i)).WillOnce (ReturnRef (message_data[i]));
137+ }
138+ for (uint32_t i = 0 ; i < fields_size; ++i) {
139+ EXPECT_CALL (memory, get (fields_offset + i)).WillOnce (ReturnRef (fields_data[i]));
140+ }
141+
142+ // First call is within budget (5 <= 9) and succeeds.
143+ debug_logger.debug_log (
144+ memory, contract_address, level_offset, message_offset, message_size, fields_offset, fields_size_offset);
145+
146+ // Second call would push the cumulative total to 10 > 9 and must throw.
147+ EXPECT_THROW (
148+ debug_logger.debug_log (
149+ memory, contract_address, level_offset, message_offset, message_size, fields_offset, fields_size_offset),
150+ std::runtime_error);
151+
152+ // Only the first log was recorded.
153+ EXPECT_THAT (log_messages, ElementsAre (" DEBUGLOG(fatal): Hi: [0x2a]" ));
154+ EXPECT_THAT (debug_logger.dump_logs (), SizeIs (1 ));
155+ }
156+
104157TEST (DebugLogSimulationTest, InvalidLevel)
105158{
106159 StrictMock<MockMemory> memory;
0 commit comments