Skip to content

Commit e632f89

Browse files
author
Mohammad Fawaz
committed
Panic when the same external CPI (returning Final) appears in multiple programs in the
import tree
1 parent af59efe commit e632f89

File tree

5 files changed

+183
-19
lines changed

5 files changed

+183
-19
lines changed

crates/fmt/src/format.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,15 @@ fn format_root(node: &SyntaxNode, out: &mut Output) {
264264
}
265265

266266
fn format_program(node: &SyntaxNode, out: &mut Output) {
267+
// AVM-style stub declarations (`program foo.aleo;`) have no L_BRACE. The
268+
// rowan parser treats the missing `{` as an error and may absorb the rest of
269+
// the file as ERROR children via recovery. Reformatting such a node would
270+
// silently drop all that recovered content, so emit it verbatim instead.
271+
if !has_token(node, L_BRACE) {
272+
write_node_verbatim(node, out);
273+
return;
274+
}
275+
267276
let elems = elements(node);
268277

269278
// Check if there are any items in the program body.

crates/passes/src/code_generation/visitor.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -75,22 +75,6 @@ impl CodeGeneratingVisitor<'_> {
7575

7676
match &self.state.ast {
7777
Ast::Program(program) => {
78-
// Register all composites and mappings first for Aleo
79-
// stubs since they are added afterterwards and are not in a
80-
// topological order
81-
for stub in program.stubs.values() {
82-
if let leo_ast::Stub::FromAleo { program, .. } = stub {
83-
for (name, _) in &program.mappings {
84-
self.global_mapping
85-
.insert(Location::new(program.stub_id.as_symbol(), vec![*name]), name.to_string());
86-
}
87-
for (name, leo_ast::Composite { is_record, .. }) in &program.composites {
88-
self.composite_mapping
89-
.insert(Location::new(program.stub_id.as_symbol(), vec![*name]), *is_record);
90-
}
91-
}
92-
}
93-
9478
for stub in program.stubs.values() {
9579
match stub {
9680
leo_ast::Stub::FromLeo { program, .. } => {
@@ -116,7 +100,19 @@ impl CodeGeneratingVisitor<'_> {
116100
bytecode: bytecode.to_string(),
117101
});
118102
}
119-
leo_ast::Stub::FromAleo { .. } | leo_ast::Stub::FromLibrary { .. } => {}
103+
leo_ast::Stub::FromAleo { program, .. } => {
104+
for (name, _) in &program.mappings {
105+
self.global_mapping
106+
.insert(Location::new(program.stub_id.as_symbol(), vec![*name]), name.to_string());
107+
}
108+
for (name, leo_ast::Composite { is_record, .. }) in &program.composites {
109+
self.composite_mapping
110+
.insert(Location::new(program.stub_id.as_symbol(), vec![*name]), *is_record);
111+
}
112+
}
113+
leo_ast::Stub::FromLibrary { .. } => {
114+
// no-op
115+
}
120116
}
121117
}
122118
}

crates/passes/src/monomorphization/program.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> {
275275
// 5. FromLibrary/FromAleo stubs are processed AFTER program_scopes, so that
276276
// reconstruct_library can collect monomorphized composites from reconstructed_composites.
277277

278+
// Capture the original stub insertion order before partitioning, so we can
279+
// reassemble stubs in their original order after processing. The type-checking
280+
// pass that runs after monomorphization depends on this order.
281+
let stub_key_order: Vec<_> = input.stubs.keys().cloned().collect();
282+
278283
// Partition stubs early (non-consuming borrow not possible, so partition now and
279284
// process in two phases).
280285
let (from_leo_stubs, other_stubs): (Vec<_>, Vec<_>) =
@@ -388,8 +393,20 @@ impl ProgramReconstructor for MonomorphizationVisitor<'_> {
388393
let other_stubs: indexmap::IndexMap<_, _> =
389394
other_stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect();
390395

391-
// Combine stubs (FromLeo first, then others) preserving stable ordering.
392-
let stubs = from_leo_stubs.into_iter().chain(other_stubs).collect();
396+
// Reassemble stubs in their original insertion order so that the type-checking
397+
// pass that follows sees them in the same sequence they had before monomorphization.
398+
let mut from_leo_map = from_leo_stubs;
399+
let mut other_map = other_stubs;
400+
let stubs = stub_key_order
401+
.into_iter()
402+
.map(|id| {
403+
let stub = from_leo_map
404+
.swap_remove(&id)
405+
.or_else(|| other_map.swap_remove(&id))
406+
.expect("every stub key must appear in exactly one partition");
407+
(id, stub)
408+
})
409+
.collect();
393410

394411
// Reconstruct modules after `self.reconstructed_composites` and `self.reconstructed_functions`
395412
// have been populated.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import credits.aleo;
2+
program program_b.aleo;
3+
4+
function withdraw:
5+
input r0 as address.private;
6+
input r1 as u64.public;
7+
call credits.aleo/transfer_public_to_private r0 r1 into r2 r3;
8+
async withdraw r3 into r4;
9+
output r2 as credits.aleo/credits.record;
10+
output r4 as program_b.aleo/withdraw.future;
11+
12+
finalize withdraw:
13+
input r0 as credits.aleo/transfer_public_to_private.future;
14+
await r0;
15+
16+
constructor:
17+
assert.eq edition 0u16;
18+
// --- Next Program --- //
19+
import program_b.aleo;
20+
import credits.aleo;
21+
program program_a.aleo;
22+
23+
function payout:
24+
input r0 as address.private;
25+
input r1 as u64.public;
26+
call credits.aleo/transfer_public_to_private r0 r1 into r2 r3;
27+
async payout r3 into r4;
28+
output r2 as credits.aleo/credits.record;
29+
output r4 as program_a.aleo/payout.future;
30+
31+
finalize payout:
32+
input r0 as credits.aleo/transfer_public_to_private.future;
33+
await r0;
34+
35+
constructor:
36+
assert.eq edition 0u16;
37+
38+
39+
---
40+
Note: Treating dependencies as Aleo produces different results:
41+
42+
import credits.aleo;
43+
program program_b.aleo;
44+
45+
function withdraw:
46+
input r0 as address.private;
47+
input r1 as u64.public;
48+
call credits.aleo/transfer_public_to_private r0 r1 into r2 r3;
49+
async withdraw r3 into r4;
50+
output r2 as credits.aleo/credits.record;
51+
output r4 as program_b.aleo/withdraw.future;
52+
53+
finalize withdraw:
54+
input r0 as credits.aleo/transfer_public_to_private.future;
55+
await r0;
56+
57+
constructor:
58+
assert.eq edition 0u16;
59+
// --- Next Program --- //
60+
import credits.aleo;
61+
import program_b.aleo;
62+
program program_a.aleo;
63+
64+
function payout:
65+
input r0 as address.private;
66+
input r1 as u64.public;
67+
call credits.aleo/transfer_public_to_private r0 r1 into r2 r3;
68+
async payout r3 into r4;
69+
output r2 as credits.aleo/credits.record;
70+
output r4 as program_a.aleo/payout.future;
71+
72+
finalize payout:
73+
input r0 as credits.aleo/transfer_public_to_private.future;
74+
await r0;
75+
76+
constructor:
77+
assert.eq edition 0u16;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Test that two programs in the import tree can each call the same external CPI
2+
// function that returns a Final without triggering "Finalization not found".
3+
//
4+
// The bug: when the FromAleo stub (credits.aleo) appears after the FromLeo stub
5+
// (program_b.aleo) in the stubs map, the type checker would try to look up finalize
6+
// types for credits.aleo before they were populated, causing a panic.
7+
8+
// --- aleo stub --- //
9+
program credits.aleo;
10+
11+
record credits:
12+
owner as address.private;
13+
amount as u64.private;
14+
15+
function transfer_public_to_private:
16+
input r0 as address.private;
17+
input r1 as u64.private;
18+
cast r0 r1 into r2 as credits.record;
19+
async transfer_public_to_private into r3;
20+
output r2 as credits.record;
21+
output r3 as credits.aleo/transfer_public_to_private.future;
22+
23+
finalize transfer_public_to_private:
24+
assert.eq 1u8 1u8;
25+
26+
// --- Next Program --- //
27+
28+
import credits.aleo;
29+
30+
program program_b.aleo {
31+
@noupgrade
32+
constructor() {}
33+
34+
fn withdraw(
35+
owner: address,
36+
public amount: u64,
37+
) -> (credits.aleo::credits, Final) {
38+
let (c, f): (credits.aleo::credits, Final) =
39+
credits.aleo::transfer_public_to_private(owner, amount);
40+
return (c, final {
41+
f.run();
42+
});
43+
}
44+
}
45+
46+
// --- Next Program --- //
47+
48+
import program_b.aleo;
49+
import credits.aleo;
50+
51+
program program_a.aleo {
52+
@noupgrade
53+
constructor() {}
54+
55+
fn payout(
56+
recipient: address,
57+
public amount: u64,
58+
) -> (credits.aleo::credits, Final) {
59+
let (c, f): (credits.aleo::credits, Final) =
60+
credits.aleo::transfer_public_to_private(recipient, amount);
61+
return (c, final {
62+
f.run();
63+
});
64+
}
65+
}

0 commit comments

Comments
 (0)