Skip to content

Commit 206d423

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

File tree

6 files changed

+480
-202
lines changed

6 files changed

+480
-202
lines changed

barretenberg/cpp/pil/vm2/context.pil

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ namespace execution;
3737
pol commit contract_address;
3838
pol commit bytecode_id;
3939
pol commit transaction_fee;
40-
// Constrained boolean by tx trace (for enqueued call) and #[NEXT_IS_STATIC] for nested
40+
// Constrained boolean by tx trace for enqueued call, #[IS_STATIC_NEXT_ROW] during normal execution,
41+
// IS_STATIC_IF_STATIC_CALL+IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT for nested calls,
42+
// and CTX_STACK_CALL for returns or failures.
4143
pol commit is_static;
4244

4345
pol commit parent_calldata_addr;
@@ -193,7 +195,12 @@ namespace execution;
193195
// otherwise = 0 ==> is_static' = is_static
194196
#[IS_STATIC_NEXT_ROW]
195197
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;
198+
// An external call from a non-static context only creates a nested static context if the opcode is STATICCALL.
199+
#[IS_STATIC_IF_STATIC_CALL]
200+
NOT_LAST_EXEC * sel_enter_call * (1 - is_static) * (is_static' - sel_execute_static_call) = 0;
201+
// An external call from a static context always creates a nested static context.
202+
#[IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT]
203+
NOT_LAST_EXEC * sel_enter_call * is_static * (is_static' - 1) = 0;
197204

198205
// nested_exit_call = 1 ==> constraints come from lookup
199206
// 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: 136 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,137 @@ 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>(
663+
trace, context::SR_IS_STATIC_IF_STATIC_CALL, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT);
664+
665+
// Negative test: change is_static
666+
// regular call from non-static context cannot become static
667+
trace.set(C::execution_is_static, 2, 1);
668+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL),
669+
"IS_STATIC_IF_STATIC_CALL");
670+
671+
// reset is_static
672+
trace.set(C::execution_is_static, 2, 0);
673+
}
674+
675+
TEST(ContextConstrainingTest, IsStaticStaticCallFromNonStaticContext)
676+
{
677+
// Non-static context making a STATICCALL - should become static
678+
TestTraceContainer trace({
679+
{ { C::precomputed_first_row, 1 } },
680+
{
681+
{ C::execution_sel, 1 },
682+
{ C::execution_context_id, 1 },
683+
{ C::execution_next_context_id, 2 },
684+
{ C::execution_is_static, 0 }, // Non-static context
685+
{ C::execution_sel_enter_call, 1 },
686+
{ C::execution_sel_execute_call, 0 },
687+
{ C::execution_sel_execute_static_call, 1 }, // STATICCALL
688+
},
689+
{
690+
{ C::execution_sel, 1 },
691+
{ C::execution_context_id, 2 },
692+
{ C::execution_next_context_id, 3 },
693+
{ C::execution_is_static, 1 }, // Should become static
694+
},
695+
});
696+
check_relation<context>(
697+
trace, context::SR_IS_STATIC_IF_STATIC_CALL, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT);
698+
699+
// Negative test: change is_static
700+
// static call from non-static context MUST become static
701+
trace.set(C::execution_is_static, 2, 0);
702+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_STATIC_CALL),
703+
"IS_STATIC_IF_STATIC_CALL");
704+
705+
// reset is_static
706+
trace.set(C::execution_is_static, 2, 1);
707+
}
708+
709+
TEST(ContextConstrainingTest, IsStaticCallFromStaticContext)
710+
{
711+
// Static context making any call - must remain static
712+
TestTraceContainer trace({
713+
{ { C::precomputed_first_row, 1 } },
714+
{
715+
{ C::execution_sel, 1 },
716+
{ C::execution_context_id, 1 },
717+
{ C::execution_next_context_id, 2 },
718+
{ C::execution_is_static, 1 }, // Static context
719+
{ C::execution_sel_enter_call, 1 },
720+
{ C::execution_sel_execute_call, 1 }, // Regular CALL
721+
{ C::execution_sel_execute_static_call, 0 },
722+
},
723+
{
724+
{ C::execution_sel, 1 },
725+
{ C::execution_context_id, 2 },
726+
{ C::execution_next_context_id, 3 },
727+
{ C::execution_is_static, 1 }, // Must remain static
728+
},
729+
});
730+
check_relation<context>(
731+
trace, context::SR_IS_STATIC_IF_STATIC_CALL, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT);
732+
733+
// Negative test: change is_static
734+
// static call from static context MUST remain static
735+
trace.set(C::execution_is_static, 2, 0);
736+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT),
737+
"IS_STATIC_IF_CALL_FROM_STATIC_CONTEXT");
738+
739+
// reset is_static
740+
trace.set(C::execution_is_static, 2, 1);
741+
}
742+
743+
TEST(ContextConstrainingTest, IsStaticPropagationWithoutCalls)
744+
{
745+
// is_static propagation without calls
746+
TestTraceContainer trace({
747+
{ { C::precomputed_first_row, 1 } },
748+
{
749+
{ C::execution_sel, 1 },
750+
{ C::execution_context_id, 1 },
751+
{ C::execution_next_context_id, 1 },
752+
{ C::execution_is_static, 1 }, // Static context
753+
},
754+
{
755+
{ C::execution_sel, 1 },
756+
{ C::execution_context_id, 1 },
757+
{ C::execution_next_context_id, 1 },
758+
{ C::execution_is_static, 1 }, // Should propagate
759+
},
760+
});
761+
check_relation<context>(trace, context::SR_IS_STATIC_NEXT_ROW);
762+
763+
// Negative test: change is_static
764+
// staticness must propagate without calls
765+
trace.set(C::execution_is_static, 2, 0);
766+
EXPECT_THROW_WITH_MESSAGE(check_relation<context>(trace, context::SR_IS_STATIC_NEXT_ROW), "IS_STATIC_NEXT_ROW");
767+
768+
// reset is_static
769+
trace.set(C::execution_is_static, 2, 1);
770+
}
771+
636772
TEST(ContextConstrainingTest, ContextIdPropagation)
637773
{
638774
TestTraceContainer trace({

0 commit comments

Comments
 (0)