Skip to content

Commit faec221

Browse files
committed
feat!: a static context propagates staticness to nested calls, even those that are not static calls.
1 parent 6ececd7 commit faec221

File tree

6 files changed

+474
-201
lines changed

6 files changed

+474
-201
lines changed

barretenberg/cpp/pil/vm2/context.pil

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,12 @@ namespace execution;
193193
// otherwise = 0 ==> is_static' = is_static
194194
#[IS_STATIC_NEXT_ROW]
195195
NOT_LAST_EXEC * DEFAULT_CTX_ROW * (is_static' - is_static) = 0;
196-
NOT_LAST_EXEC * sel_enter_call * (is_static' - sel_execute_static_call) = 0;
196+
// An external call from a non-static context only creates a nested static context if the opcode is STATICCALL.
197+
#[IS_STATIC_IF_STATIC_CALL]
198+
NOT_LAST_EXEC * sel_enter_call * (1 - is_static) * (is_static' - sel_execute_static_call) = 0;
199+
// An external call from a static context always creates a nested static context.
200+
#[IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT]
201+
NOT_LAST_EXEC * sel_enter_call * is_static * (is_static' - 1) = 0;
197202

198203
// nested_exit_call = 1 ==> constraints come from lookup
199204
// sel_enter_call = 1 ==> parent_calldata_addr' = rop[4] (resolved operand 5 from execution trace)

barretenberg/cpp/src/barretenberg/vm2/constraining/relations/context.test.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,25 @@ TEST(ContextConstrainingTest, ContextSwitchingCallReturn)
6161
{ C::execution_context_id, 1 },
6262
{ C::execution_next_context_id, 2 },
6363
{ C::execution_bytecode_id, top_bytecode_id },
64+
{ C::execution_is_static, 0 }, // Non-static context
6465
{ C::execution_parent_l2_gas_limit, 2000 },
6566
{ C::execution_parent_da_gas_limit, 4000 },
6667
{ C::execution_parent_l2_gas_used, 500 },
6768
{ C::execution_parent_da_gas_used, 1500 },
69+
{ C::execution_enqueued_call_start, 1 },
6870
},
6971
// CALL
7072
{
7173
{ C::execution_sel, 1 },
7274
{ C::execution_pc, 1 },
7375
{ C::execution_next_pc, 2 },
7476
{ C::execution_sel_execute_call, 1 },
77+
{ C::execution_sel_execute_static_call, 0 }, // Regular CALL, not STATICCALL
7578
{ C::execution_sel_enter_call, 1 },
7679
{ C::execution_context_id, 1 },
7780
{ C::execution_next_context_id, 2 },
7881
{ C::execution_bytecode_id, top_bytecode_id }, // Same as previous row (propagated)
82+
{ C::execution_is_static, 0 }, // Still non-static
7983
{ C::execution_rop_4_, /*cd offset=*/10 },
8084
{ C::execution_register_2_, /*contract address=*/0xdeadbeef },
8185
{ C::execution_register_3_, /*cd size=*/1 },
@@ -96,6 +100,7 @@ TEST(ContextConstrainingTest, ContextSwitchingCallReturn)
96100
{ C::execution_has_parent_ctx, 1 },
97101
{ C::execution_contract_address, 0xdeadbeef },
98102
{ C::execution_bytecode_id, nested_bytecode_id }, // New bytecode_id on entering new context
103+
{ C::execution_is_static, 0 }, // Remains non-static after regular CALL
99104
{ C::execution_parent_calldata_addr, 10 },
100105
{ C::execution_parent_calldata_size, 1 },
101106
},
@@ -633,6 +638,134 @@ TEST(ContextConstrainingTest, BytecodeIdPropagation)
633638
"BYTECODE_ID_NEXT_ROW"); // Should fail constraint
634639
}
635640

641+
TEST(ContextConstrainingTest, IsStaticRegularCallFromNonStaticContext)
642+
{
643+
// Non-static context making a regular CALL - should remain non-static
644+
TestTraceContainer trace({
645+
{ { C::precomputed_first_row, 1 } },
646+
{
647+
{ C::execution_sel, 1 },
648+
{ C::execution_context_id, 1 },
649+
{ C::execution_next_context_id, 2 },
650+
{ C::execution_is_static, 0 }, // Non-static context
651+
{ C::execution_sel_enter_call, 1 },
652+
{ C::execution_sel_execute_call, 1 }, // Regular CALL
653+
{ C::execution_sel_execute_static_call, 0 },
654+
},
655+
{
656+
{ C::execution_sel, 1 },
657+
{ C::execution_context_id, 2 },
658+
{ C::execution_next_context_id, 3 },
659+
{ C::execution_is_static, 0 }, // Should remain non-static
660+
},
661+
});
662+
check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL);
663+
664+
// Negative test: change is_static
665+
// regular call from non-static context cannot become static
666+
trace.set(C::execution_is_static, 2, 1);
667+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL),
668+
"IS_STATIC_IF_STATIC_CALL");
669+
670+
// reset is_static
671+
trace.set(C::execution_is_static, 2, 0);
672+
}
673+
674+
TEST(ContextConstrainingTest, IsStaticStaticCallFromNonStaticContext)
675+
{
676+
// Non-static context making a STATICCALL - should become static
677+
TestTraceContainer trace({
678+
{ { C::precomputed_first_row, 1 } },
679+
{
680+
{ C::execution_sel, 1 },
681+
{ C::execution_context_id, 1 },
682+
{ C::execution_next_context_id, 2 },
683+
{ C::execution_is_static, 0 }, // Non-static context
684+
{ C::execution_sel_enter_call, 1 },
685+
{ C::execution_sel_execute_call, 0 },
686+
{ C::execution_sel_execute_static_call, 1 }, // STATICCALL
687+
},
688+
{
689+
{ C::execution_sel, 1 },
690+
{ C::execution_context_id, 2 },
691+
{ C::execution_next_context_id, 3 },
692+
{ C::execution_is_static, 1 }, // Should become static
693+
},
694+
});
695+
check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL);
696+
697+
// Negative test: change is_static
698+
// static call from non-static context MUST become static
699+
trace.set(C::execution_is_static, 2, 0);
700+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL),
701+
"IS_STATIC_IF_STATIC_CALL");
702+
703+
// reset is_static
704+
trace.set(C::execution_is_static, 2, 1);
705+
}
706+
707+
TEST(ContextConstrainingTest, IsStaticCallFromStaticContext)
708+
{
709+
// Static context making any call - must remain static
710+
TestTraceContainer trace({
711+
{ { C::precomputed_first_row, 1 } },
712+
{
713+
{ C::execution_sel, 1 },
714+
{ C::execution_context_id, 1 },
715+
{ C::execution_next_context_id, 2 },
716+
{ C::execution_is_static, 1 }, // Static context
717+
{ C::execution_sel_enter_call, 1 },
718+
{ C::execution_sel_execute_call, 1 }, // Regular CALL
719+
{ C::execution_sel_execute_static_call, 0 },
720+
},
721+
{
722+
{ C::execution_sel, 1 },
723+
{ C::execution_context_id, 2 },
724+
{ C::execution_next_context_id, 3 },
725+
{ C::execution_is_static, 1 }, // Must remain static
726+
},
727+
});
728+
check_relation<context>(trace, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT);
729+
730+
// Negative test: change is_static
731+
// static call from static context MUST remain static
732+
trace.set(C::execution_is_static, 2, 0);
733+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT),
734+
"IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT");
735+
736+
// reset is_static
737+
trace.set(C::execution_is_static, 2, 1);
738+
}
739+
740+
TEST(ContextConstrainingTest, IsStaticPropagationWithoutCalls)
741+
{
742+
// is_static propagation without calls
743+
TestTraceContainer trace({
744+
{ { C::precomputed_first_row, 1 } },
745+
{
746+
{ C::execution_sel, 1 },
747+
{ C::execution_context_id, 1 },
748+
{ C::execution_next_context_id, 1 },
749+
{ C::execution_is_static, 1 }, // Static context
750+
},
751+
{
752+
{ C::execution_sel, 1 },
753+
{ C::execution_context_id, 1 },
754+
{ C::execution_next_context_id, 1 },
755+
{ C::execution_is_static, 1 }, // Should propagate
756+
},
757+
});
758+
check_relation<context>(trace, context::SR_IS_STATIC_NEXT_ROW);
759+
760+
// Negative test: change is_static
761+
// staticness must propagate without calls
762+
trace.set(C::execution_is_static, 2, 0);
763+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_NEXT_ROW), "IS_STATIC_NEXT_ROW");
764+
765+
// reset is_static
766+
trace.set(C::execution_is_static, 2, 1);
767+
}
768+
636769
TEST(ContextConstrainingTest, ContextIdPropagation)
637770
{
638771
TestTraceContainer trace({

0 commit comments

Comments
 (0)