1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
30using simulation::EmitPublicLogWriteEvent;
31using simulation::TrackedSideEffects;
32using testing::PublicInputsBuilder;
33using tracegen::EmitPublicLogTraceBuilder;
34using tracegen::MemoryTraceBuilder;
35using tracegen::PrecomputedTraceBuilder;
36using tracegen::PublicInputsTraceBuilder;
37using tracegen::TestTraceContainer;
45 memory_values.reserve(fields.size());
46 for (
const FF&
field : fields) {
47 memory_values.push_back(MemoryValue::from<FF>(
field));
52TEST(EmitPublicLogConstrainingTest, EmptyTrace)
57TEST(EmitPublicLogConstrainingTest, Positive)
61 const std::vector<FF> log_fields = { 4, 5 };
62 uint32_t log_size =
static_cast<uint32_t
>(log_fields.size());
63 TrackedSideEffects side_effect_states = { .public_logs = {} };
64 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields,
address } } } };
66 EmitPublicLogWriteEvent
event = {
70 .log_address = log_address,
72 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
73 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
75 .values = to_memory_values(log_fields),
76 .error_memory_out_of_bounds =
false,
77 .error_too_many_log_fields =
false,
78 .error_tag_mismatch =
false,
81 TestTraceContainer
trace({
82 { { C::precomputed_first_row, 1 } },
85 EmitPublicLogTraceBuilder trace_builder;
86 trace_builder.process({
event },
trace);
88 check_relation<emit_public_log>(trace);
91TEST(EmitPublicLogConstrainingTest, PositiveEmptyLog)
96 const std::vector<FF> log_fields = {};
97 uint32_t log_size =
static_cast<uint32_t
>(log_fields.size());
98 TrackedSideEffects side_effect_states = { .public_logs = {} };
99 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields,
address } } } };
101 EmitPublicLogWriteEvent
event = {
105 .log_address = log_address,
106 .log_size = log_size,
107 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
108 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
110 .values = to_memory_values(log_fields),
111 .error_memory_out_of_bounds =
false,
112 .error_too_many_log_fields =
false,
113 .error_tag_mismatch =
false,
117 uint64_t end_log_address_upper_bound =
static_cast<uint64_t
>(log_address) +
static_cast<uint64_t
>(log_size);
119 simulation::GreaterThanEvent gt_event = {
120 .a = end_log_address_upper_bound,
125 TestTraceContainer
trace({
126 { { C::precomputed_first_row, 1 } },
129 EmitPublicLogTraceBuilder trace_builder;
132 trace_builder.process({
event },
trace);
135 FF end_log_address_upper_bound_log_trace =
trace.
get(C::emit_public_log_end_log_address_upper_bound, 1);
136 FF end_log_address_upper_bound_gt_trace =
trace.
get(C::gt_input_a, 0);
137 EXPECT_EQ(end_log_address_upper_bound_log_trace, end_log_address_upper_bound_gt_trace);
139 check_relation<emit_public_log>(trace);
140 check_interaction<EmitPublicLogTraceBuilder, lookup_emit_public_log_check_memory_out_of_bounds_settings>(trace);
143TEST(EmitPublicLogConstrainingTest, ErrorMemoryOutOfBounds)
147 uint32_t log_size = 2;
148 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 },
address } } } };
149 const TrackedSideEffects& side_effect_states_after = side_effect_states;
151 EmitPublicLogWriteEvent
event = {
155 .log_address = log_address,
156 .log_size = log_size,
157 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
158 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
161 .error_memory_out_of_bounds =
true,
162 .error_too_many_log_fields =
false,
163 .error_tag_mismatch =
false,
166 TestTraceContainer
trace({
167 { { C::precomputed_first_row, 1 } },
170 EmitPublicLogTraceBuilder trace_builder;
171 trace_builder.process({
event },
trace);
173 check_relation<emit_public_log>(trace);
176TEST(EmitPublicLogConstrainingTest, ErrorTooManyLogFields)
180 const std::vector<FF> log_fields = { 4, 5 };
181 uint32_t log_size =
static_cast<uint32_t
>(log_fields.size());
183 TrackedSideEffects side_effect_states = {
186 const TrackedSideEffects& side_effect_states_after = side_effect_states;
188 EmitPublicLogWriteEvent
event = {
192 .log_address = log_address,
193 .log_size = log_size,
194 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
195 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
197 .values = to_memory_values(log_fields),
198 .error_memory_out_of_bounds =
false,
199 .error_too_many_log_fields =
true,
200 .error_tag_mismatch =
false,
203 TestTraceContainer
trace({
204 { { C::precomputed_first_row, 1 } },
207 EmitPublicLogTraceBuilder trace_builder;
208 trace_builder.process({
event },
trace);
210 check_relation<emit_public_log>(trace);
213TEST(EmitPublicLogConstrainingTest, ErrorTagMismatch)
218 uint32_t log_size =
static_cast<uint32_t
>(log_values.size());
219 TrackedSideEffects side_effect_states = { .public_logs = {} };
221 const TrackedSideEffects& side_effect_states_after = side_effect_states;
223 EmitPublicLogWriteEvent
event = {
227 .log_address = log_address,
228 .log_size = log_size,
229 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
230 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
232 .values = log_values,
233 .error_memory_out_of_bounds =
false,
234 .error_too_many_log_fields =
false,
235 .error_tag_mismatch =
true,
238 TestTraceContainer
trace({
239 { { C::precomputed_first_row, 1 } },
242 EmitPublicLogTraceBuilder trace_builder;
243 trace_builder.process({
event },
trace);
245 check_relation<emit_public_log>(trace);
248TEST(EmitPublicLogConstrainingTest, ErrorStatic)
252 const std::vector<FF> log_fields = { 4, 5 };
253 uint32_t log_size =
static_cast<uint32_t
>(log_fields.size());
254 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 },
address } } } };
255 const TrackedSideEffects& side_effect_states_after = side_effect_states;
257 EmitPublicLogWriteEvent
event = {
261 .log_address = log_address,
262 .log_size = log_size,
263 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
264 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
266 .values = to_memory_values(log_fields),
267 .error_memory_out_of_bounds =
false,
268 .error_too_many_log_fields =
false,
269 .error_tag_mismatch =
false,
272 TestTraceContainer
trace({
273 { { C::precomputed_first_row, 1 } },
276 EmitPublicLogTraceBuilder trace_builder;
277 trace_builder.process({
event },
trace);
279 check_relation<emit_public_log>(trace);
282TEST(EmitPublicLogConstrainingTest, Interactions)
286 const std::vector<FF> log_fields = { 4, 5 };
287 uint32_t log_size =
static_cast<uint32_t
>(log_fields.size());
288 TrackedSideEffects side_effect_states = { .public_logs = {} };
289 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields,
address } } } };
290 AvmAccumulatedData accumulated_data = {};
291 accumulated_data.public_logs.add_log({
292 .fields = {
FF(4),
FF(5) },
295 auto public_inputs = PublicInputsBuilder().set_accumulated_data(accumulated_data).build();
299 EmitPublicLogWriteEvent
event = {
303 .log_address = log_address,
304 .log_size = log_size,
305 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
306 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
309 .error_memory_out_of_bounds =
false,
310 .error_too_many_log_fields =
false,
311 .error_tag_mismatch =
false,
314 TestTraceContainer
trace = TestTraceContainer({
317 { C::precomputed_first_row, 1 },
320 { C::gt_input_a, side_effect_states_after.get_num_public_log_fields() },
326 { C::execution_sel, 1 },
327 { C::execution_sel_exec_dispatch_emit_public_log, 1 },
328 { C::execution_context_id, 57 },
329 { C::execution_rop_1_, log_address },
330 { C::execution_register_0_, log_size },
331 { C::execution_contract_address,
address },
332 { C::execution_prev_num_public_log_fields, side_effect_states.get_num_public_log_fields() },
333 { C::execution_num_public_log_fields, side_effect_states_after.get_num_public_log_fields() },
334 { C::execution_is_static,
false },
335 { C::execution_sel_opcode_error, 0 },
336 { C::execution_discard, 0 },
339 { C::gt_input_a, log_address + log_size },
346 for (uint32_t i = 0; i <
inputs.size(); ++i) {
348 trace.
set(C::memory_address, i + 1, log_address + i);
350 trace.
set(C::memory_tag, i + 1,
static_cast<uint32_t
>(
inputs[i].get_tag()));
354 trace.
set(C::memory_space_id, i + 1, 57);
364 EmitPublicLogTraceBuilder trace_builder;
365 trace_builder.process({
event },
trace);
367 check_relation<emit_public_log>(trace);
368 check_all_interactions<EmitPublicLogTraceBuilder>(trace);
371TEST(EmitPublicLogConstrainingTest, NegativeStartAfterLatch)
373 TestTraceContainer
trace = TestTraceContainer({ {
374 { C::precomputed_first_row, 1 },
377 { C::emit_public_log_sel, 1 },
378 { C::emit_public_log_start, 1 },
379 { C::emit_public_log_end, 1 },
382 { C::emit_public_log_sel, 1 },
383 { C::emit_public_log_start, 1 },
388 trace.
set(C::emit_public_log_end, 1, 0);
391 "START_AFTER_LATCH");
393 trace.
set(C::emit_public_log_end, 1, 1);
394 trace.
set(C::precomputed_first_row, 0, 0);
397 "START_AFTER_LATCH");
400TEST(EmitPublicLogConstrainingTest, NegativeTraceContinuity)
407 TestTraceContainer
trace = TestTraceContainer({ {
408 { C::precomputed_first_row, 1 },
409 { C::emit_public_log_sel, 1 },
410 { C::emit_public_log_start, 1 },
413 { C::emit_public_log_sel, 1 },
414 { C::emit_public_log_end, 1 },
417 { C::emit_public_log_sel, 0 },
420 { C::emit_public_log_sel, 0 },
426 trace.
set(C::emit_public_log_sel, 3, 1);
432TEST(EmitPublicLogConstrainingTest, NegativeComputationFinishAtEnd)
438 TestTraceContainer
trace = TestTraceContainer({ {
439 { C::precomputed_first_row, 1 },
440 { C::emit_public_log_sel, 1 },
441 { C::emit_public_log_start, 1 },
444 { C::emit_public_log_sel, 1 },
447 { C::emit_public_log_sel, 0 },
454 trace.
set(C::emit_public_log_end, 1, 1);
459TEST(EmitPublicLogConstrainingTest, NegativeRemainingRowsDecrement)
461 TestTraceContainer
trace = TestTraceContainer({ {
462 { C::emit_public_log_sel, 1 },
463 { C::emit_public_log_remaining_rows, 1 },
466 { C::emit_public_log_sel, 1 },
467 { C::emit_public_log_remaining_rows, 0 },
468 { C::emit_public_log_end, 1 },
473 trace.
set(C::emit_public_log_remaining_rows, 1, 1);
476 "REMAINING_ROWS_DECREMENT");
479TEST(EmitPublicLogConstrainingTest, NegativeErrorOutOfBoundsConsistency)
481 TestTraceContainer
trace = TestTraceContainer({ {
482 { C::emit_public_log_sel, 1 },
483 { C::emit_public_log_error_out_of_bounds, 1 },
486 { C::emit_public_log_sel, 1 },
487 { C::emit_public_log_error_out_of_bounds, 1 },
488 { C::emit_public_log_end, 1 },
493 trace.
set(C::emit_public_log_error_out_of_bounds, 1, 0);
497 "ERROR_OUT_OF_BOUNDS_CONSISTENCY");
500TEST(EmitPublicLogConstrainingTest, NegativeErrorTagMismatchConsistency)
502 TestTraceContainer
trace = TestTraceContainer({ {
503 { C::emit_public_log_sel, 1 },
504 { C::emit_public_log_error_tag_mismatch, 1 },
507 { C::emit_public_log_sel, 1 },
508 { C::emit_public_log_error_tag_mismatch, 1 },
509 { C::emit_public_log_end, 1 },
514 trace.
set(C::emit_public_log_error_tag_mismatch, 1, 0);
518 "ERROR_TAG_MISMATCH_CONSISTENCY");
521TEST(EmitPublicLogConstrainingTest, NegativeWrongTagCheck)
523 TestTraceContainer
trace = TestTraceContainer({ {
524 { C::emit_public_log_sel, 1 },
525 { C::emit_public_log_seen_wrong_tag, 0 },
528 { C::emit_public_log_sel, 1 },
529 { C::emit_public_log_seen_wrong_tag, 1 },
530 { C::emit_public_log_correct_tag, 0 },
531 { C::emit_public_log_end, 1 },
536 trace.
set(C::emit_public_log_seen_wrong_tag, 1, 0);
542TEST(EmitPublicLogConstrainingTest, NegativeSelectorShouldWriteToPublicInputsConsistency)
544 TestTraceContainer
trace = TestTraceContainer({ {
545 { C::emit_public_log_sel, 1 },
546 { C::emit_public_log_sel_write_to_public_inputs, 1 },
549 { C::emit_public_log_sel, 1 },
550 { C::emit_public_log_sel_write_to_public_inputs, 1 },
551 { C::emit_public_log_end, 1 },
556 trace.
set(C::emit_public_log_sel_write_to_public_inputs, 1, 0);
560 "SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY");
563TEST(EmitPublicLogConstrainingTest, NegativeLogOffsetIncrement)
565 TestTraceContainer
trace = TestTraceContainer({ {
566 { C::emit_public_log_sel, 1 },
567 { C::emit_public_log_is_write_memory_value, 1 },
568 { C::emit_public_log_log_address, 10 },
571 { C::emit_public_log_sel, 1 },
572 { C::emit_public_log_is_write_memory_value, 1 },
573 { C::emit_public_log_log_address, 11 },
574 { C::emit_public_log_end, 1 },
579 trace.
set(C::emit_public_log_log_address, 1, 9);
582 "LOG_ADDRESS_INCREMENT");
585TEST(EmitPublicLogConstrainingTest, NegativeExecutionClkConsistency)
587 TestTraceContainer
trace = TestTraceContainer({ {
588 { C::emit_public_log_sel, 1 },
589 { C::emit_public_log_execution_clk, 1 },
592 { C::emit_public_log_sel, 1 },
593 { C::emit_public_log_execution_clk, 1 },
594 { C::emit_public_log_end, 1 },
599 trace.
set(C::emit_public_log_execution_clk, 1, 0);
602 "EXEC_CLK_CONSISTENCY");
605TEST(EmitPublicLogConstrainingTest, NegativeSpaceIdConsistency)
607 TestTraceContainer
trace = TestTraceContainer({ {
608 { C::emit_public_log_sel, 1 },
609 { C::emit_public_log_space_id, 17 },
612 { C::emit_public_log_sel, 1 },
613 { C::emit_public_log_space_id, 17 },
614 { C::emit_public_log_end, 1 },
619 trace.
set(C::emit_public_log_space_id, 1, 18);
622 "SPACE_ID_CONSISTENCY");
625TEST(EmitPublicLogConstrainingTest, NegativeContractAddressConsistency)
627 TestTraceContainer
trace = TestTraceContainer({ {
628 { C::emit_public_log_sel, 1 },
629 { C::emit_public_log_contract_address, 42 },
632 { C::emit_public_log_sel, 1 },
633 { C::emit_public_log_contract_address, 42 },
634 { C::emit_public_log_end, 1 },
639 trace.
set(C::emit_public_log_contract_address, 1, 43);
642 "CONTRACT_ADDRESS_CONSISTENCY");
668TEST(EmitPublicLogConstrainingTest, NegativeGhostRowInjectionBlocked)
670 TestTraceContainer
trace;
671 MemoryTraceBuilder memory_trace_builder;
672 PrecomputedTraceBuilder precomputed_trace_builder;
674 uint32_t malicious_clk = 42;
675 uint16_t malicious_space_id = 1;
677 FF malicious_value = 0x1337;
682 .execution_clk = malicious_clk,
684 .addr = malicious_log_addr,
685 .value = MemoryValue::from<FF>(malicious_value),
686 .space_id = malicious_space_id,
690 precomputed_trace_builder.process_sel_range_8(trace);
691 precomputed_trace_builder.process_sel_range_16(trace);
692 precomputed_trace_builder.process_misc(trace, 1 << 16);
693 precomputed_trace_builder.process_tag_parameters(trace);
694 memory_trace_builder.process(mem_events, trace);
696 uint32_t memory_row = 0;
698 if (
trace.
get(C::memory_sel, row) == 1) {
705 uint32_t ghost_row = 0;
708 { C::precomputed_first_row, 1 },
709 { C::execution_clk, ghost_row },
710 { C::precomputed_zero, 0 },
711 { C::emit_public_log_sel, 0 },
712 { C::emit_public_log_is_write_memory_value, 1 },
713 { C::emit_public_log_error_out_of_bounds, 0 },
714 { C::emit_public_log_sel_read_memory, 1 },
715 { C::emit_public_log_execution_clk, malicious_clk },
716 { C::emit_public_log_space_id, malicious_space_id },
717 { C::emit_public_log_log_address, malicious_log_addr },
718 { C::emit_public_log_value, malicious_value },
719 { C::emit_public_log_tag,
static_cast<uint8_t
>(malicious_tag) },
720 { C::emit_public_log_public_inputs_value, malicious_value },
723 trace.
set(C::memory_sel_public_log_read, memory_row, 1);
729 "SEL_SHOULD_READ_MEMORY_IS_SEL_AND_WRITE_MEM_AND_NO_ERR");
732TEST(EmitPublicLogConstrainingTest, NegativeSelToggledAtStartEnd)
736 TestTraceContainer
trace = TestTraceContainer({ {
737 { C::emit_public_log_sel, 1 },
738 { C::emit_public_log_start, 1 },
743 trace.
set(C::emit_public_log_sel, 0, 0);
746 "SEL_ON_START_OR_END");
749TEST(EmitPublicLogConstrainingTest, NegativeInitialSeenWrongTag)
753 TestTraceContainer
trace = TestTraceContainer({ {
754 { C::emit_public_log_sel, 1 },
755 { C::emit_public_log_start, 1 },
756 { C::emit_public_log_seen_wrong_tag, 0 },
761 trace.
set(C::emit_public_log_seen_wrong_tag, 0, 1);
764 "INITIAL_SEEN_WRONG_TAG");
767TEST(EmitPublicLogConstrainingTest, NegativeCheckEndTagMismatch)
771 TestTraceContainer
trace = TestTraceContainer({ {
772 { C::emit_public_log_sel, 1 },
773 { C::emit_public_log_end, 1 },
774 { C::emit_public_log_error_tag_mismatch, 1 },
775 { C::emit_public_log_seen_wrong_tag, 1 },
780 trace.
set(C::emit_public_log_error_tag_mismatch, 0, 0);
783 "CHECK_END_TAG_MISMATCH");
786TEST(EmitPublicLogConstrainingTest, NegativeWriteContractAddressAfterStart)
790 TestTraceContainer
trace = TestTraceContainer({ {
791 { C::emit_public_log_sel, 1 },
792 { C::emit_public_log_start, 1 },
795 { C::emit_public_log_is_write_contract_address, 1 },
800 trace.
set(C::emit_public_log_is_write_contract_address, 1, 0);
804 "WRITE_CONTRACT_ADDRESS_AFTER_START");
807TEST(EmitPublicLogConstrainingTest, NegativeSetAndProgateValueWrite)
811 TestTraceContainer
trace = TestTraceContainer({ {
812 { C::emit_public_log_sel, 1 },
813 { C::emit_public_log_is_write_contract_address, 1 },
814 { C::emit_public_log_is_write_memory_value, 0 },
817 { C::emit_public_log_sel, 1 },
818 { C::emit_public_log_end, 1 },
819 { C::emit_public_log_is_write_memory_value, 1 },
824 trace.
set(C::emit_public_log_is_write_memory_value, 1, 0);
827 "SET_AND_PROGATE_VALUE_WRITE");
830TEST(EmitPublicLogConstrainingTest, NegativeDisabledMemReadValueZero)
834 TestTraceContainer
trace = TestTraceContainer({ {
835 { C::emit_public_log_sel, 1 },
836 { C::emit_public_log_sel_read_memory, 0 },
837 { C::emit_public_log_value, 0 },
842 trace.
set(C::emit_public_log_value, 0, 42);
845 "DISABLED_MEM_READ_VALUE_ZERO");
848TEST(EmitPublicLogConstrainingTest, NegativeDisabledMemReadTagFF)
852 TestTraceContainer
trace = TestTraceContainer({ {
853 { C::emit_public_log_sel, 1 },
854 { C::emit_public_log_sel_read_memory, 0 },
855 { C::emit_public_log_tag, 0 },
860 trace.
set(C::emit_public_log_tag, 0, 3);
863 "DISABLED_MEM_READ_TAG_FF");
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
#define FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH
#define AVM_HIGHEST_MEM_ADDRESS
static constexpr size_t SR_WRONG_TAG_CHECK
static constexpr size_t SR_SEL_ON_START_OR_END
static constexpr size_t SR_SPACE_ID_CONSISTENCY
static constexpr size_t SR_CHECK_END_TAG_MISMATCH
static constexpr size_t SR_LOG_ADDRESS_INCREMENT
static constexpr size_t SR_CONTRACT_ADDRESS_CONSISTENCY
static constexpr size_t SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY
static constexpr size_t SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY
static constexpr size_t SR_SET_AND_PROGATE_VALUE_WRITE
static constexpr size_t SR_WRITE_CONTRACT_ADDRESS_AFTER_START
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_DISABLED_MEM_READ_TAG_FF
static constexpr size_t SR_TRACE_CONTINUITY
static constexpr size_t SR_DISABLED_MEM_READ_VALUE_ZERO
static constexpr size_t SR_EXEC_CLK_CONSISTENCY
static constexpr size_t SR_REMAINING_ROWS_DECREMENT
static constexpr size_t SR_INITIAL_SEEN_WRONG_TAG
static constexpr size_t SR_ERROR_TAG_MISMATCH_CONSISTENCY
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Process the greater-than events and populate the relevant columns in the trace.
void process_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
const FF & get(Column col, uint32_t row) const
uint32_t get_num_rows() const
void set(Column col, uint32_t row, const FF &value)
PrecomputedTraceBuilder precomputed_builder
GreaterThanTraceBuilder gt_builder
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
std::variant< EmitPublicLogWriteEvent, CheckPointEventType > EmitPublicLogEvent
TestTraceContainer empty_trace()
std::vector< FF > random_fields(size_t n)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
tracegen::PublicInputsTraceBuilder public_inputs_builder