1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
use alloc::{collections::BTreeMap, vec::Vec};

use miden_objects::{
    assembly::{Assembler, AssemblyContext, ModuleAst, ProgramAst},
    transaction::{InputNote, InputNotes, TransactionScript},
    Felt, NoteError, TransactionScriptError, Word,
};

use super::{
    AccountCode, AccountId, CodeBlock, Digest, NoteScript, Program, TransactionCompilerError,
    TransactionKernel,
};

#[cfg(test)]
mod tests;

// TRANSACTION COMPILER
// ================================================================================================

/// The transaction compiler is responsible for building executable programs for Miden rollup
/// transactions.
///
/// The generated programs can then be executed on the Miden VM to update the states of accounts
/// involved in these transactions.
///
/// In addition to transaction compilation, transaction compiler provides methods which can be
/// used to compile Miden account code and note scripts.
pub struct TransactionCompiler {
    assembler: Assembler,
    account_procedures: BTreeMap<AccountId, Vec<Digest>>,
    kernel_main: CodeBlock,
}

impl TransactionCompiler {
    // CONSTRUCTOR
    // --------------------------------------------------------------------------------------------
    /// Returns a new [TransactionCompiler].
    pub fn new() -> TransactionCompiler {
        let assembler = TransactionKernel::assembler();

        // compile transaction kernel main
        let main_ast = TransactionKernel::main().expect("main is well formed");
        let kernel_main = assembler
            .compile_in_context(&main_ast, &mut AssemblyContext::for_program(Some(&main_ast)))
            .expect("main is well formed");

        TransactionCompiler {
            assembler,
            account_procedures: BTreeMap::default(),
            kernel_main,
        }
    }

    /// Puts the [TransactionCompiler] into debug mode.
    ///
    /// When transaction compiler is in debug mode, all transaction-related code (note scripts,
    /// account code) will be compiled in debug mode which will preserve debug artifacts from the
    /// original source code.
    pub fn with_debug_mode(mut self, in_debug_mode: bool) -> Self {
        self.assembler = self.assembler.with_debug_mode(in_debug_mode);
        self
    }

    // ACCOUNT CODE AND NOTE SCRIPT COMPILERS
    // --------------------------------------------------------------------------------------------

    /// Compiles the provided module into [AccountCode] and associates the resulting procedures
    /// with the specified account ID.
    pub fn load_account(
        &mut self,
        account_id: AccountId,
        account_code: ModuleAst,
    ) -> Result<AccountCode, TransactionCompilerError> {
        let account_code = AccountCode::new(account_code, &self.assembler)
            .map_err(TransactionCompilerError::LoadAccountFailed)?;
        self.account_procedures.insert(account_id, account_code.procedures().to_vec());
        Ok(account_code)
    }

    /// Loads the provided account interface (vector of procedure digests) into this compiler.
    /// Returns the old account interface if it previously existed.
    pub fn load_account_interface(
        &mut self,
        account_id: AccountId,
        procedures: Vec<Digest>,
    ) -> Option<Vec<Digest>> {
        self.account_procedures.insert(account_id, procedures)
    }

    /// Compiles the provided program into the [NoteScript] and checks (to the extent possible)
    /// if a note could be executed against all accounts with the specified interfaces.
    pub fn compile_note_script(
        &self,
        note_script_ast: ProgramAst,
        target_account_proc: Vec<ScriptTarget>,
    ) -> Result<NoteScript, TransactionCompilerError> {
        let (note_script, code_block) =
            NoteScript::new(note_script_ast, &self.assembler).map_err(|err| match err {
                NoteError::ScriptCompilationError(err) => {
                    TransactionCompilerError::CompileNoteScriptFailed(err)
                },
                _ => TransactionCompilerError::NoteScriptError(err),
            })?;
        for note_target in target_account_proc.into_iter() {
            verify_program_account_compatibility(
                &code_block,
                &self.get_target_interface(note_target)?,
                ScriptType::NoteScript,
            )?;
        }

        Ok(note_script)
    }

    /// Constructs a [TransactionScript] by compiling the provided source code and checking the
    /// compatibility of the resulting program with the target account interfaces.
    pub fn compile_tx_script<T>(
        &self,
        tx_script_ast: ProgramAst,
        tx_script_inputs: T,
        target_account_proc: Vec<ScriptTarget>,
    ) -> Result<TransactionScript, TransactionCompilerError>
    where
        T: IntoIterator<Item = (Word, Vec<Felt>)>,
    {
        let (tx_script, code_block) =
            TransactionScript::new(tx_script_ast, tx_script_inputs, &self.assembler).map_err(
                |e| match e {
                    TransactionScriptError::ScriptCompilationError(asm_error) => {
                        TransactionCompilerError::CompileTxScriptFailed(asm_error)
                    },
                },
            )?;
        for target in target_account_proc.into_iter() {
            verify_program_account_compatibility(
                &code_block,
                &self.get_target_interface(target)?,
                ScriptType::TransactionScript,
            )?;
        }
        Ok(tx_script)
    }

    // TRANSACTION PROGRAM BUILDER
    // --------------------------------------------------------------------------------------------
    /// Compiles a transaction which executes the provided notes and an optional tx script against
    /// the specified account. Returns the compiled transaction program.
    ///
    /// The account is assumed to have been previously loaded into this compiler.
    pub fn compile_transaction(
        &self,
        account_id: AccountId,
        notes: &InputNotes<InputNote>,
        tx_script: Option<&ProgramAst>,
    ) -> Result<Program, TransactionCompilerError> {
        // Fetch the account interface from the `account_procedures` map. Return an error if the
        // interface is not found.
        let target_account_interface = self
            .account_procedures
            .get(&account_id)
            .cloned()
            .ok_or(TransactionCompilerError::AccountInterfaceNotFound(account_id))?;

        // Transaction must contain at least one input note or a transaction script
        if notes.is_empty() && tx_script.is_none() {
            return Err(TransactionCompilerError::NoTransactionDriver);
        }

        // Create the [AssemblyContext] for compilation of notes scripts and the transaction script
        let mut assembly_context = AssemblyContext::for_program(None);

        // Compile note scripts
        let note_script_programs =
            self.compile_notes(&target_account_interface, notes, &mut assembly_context)?;

        // Compile the transaction script
        let tx_script_program = match tx_script {
            Some(tx_script) => Some(self.compile_tx_script_program(
                tx_script,
                &mut assembly_context,
                target_account_interface,
            )?),
            None => None,
        };

        // Create [CodeBlockTable] from [AssemblyContext]
        let mut cb_table = self
            .assembler
            .build_cb_table(assembly_context)
            .map_err(TransactionCompilerError::BuildCodeBlockTableFailed)?;

        // insert note roots into [CodeBlockTable]
        note_script_programs.into_iter().for_each(|note_root| {
            cb_table.insert(note_root);
        });

        // insert transaction script into [CodeBlockTable]
        if let Some(tx_script_program) = tx_script_program {
            cb_table.insert(tx_script_program);
        }

        // Create transaction program with kernel
        let program = Program::with_kernel(
            self.kernel_main.clone(),
            self.assembler.kernel().clone(),
            cb_table,
        );

        // Create compiled transaction
        Ok(program)
    }

    // HELPER METHODS
    // --------------------------------------------------------------------------------------------

    /// Compiles the provided notes into [CodeBlock]s (programs) and verifies that each note is
    /// compatible with the target account interfaces. Returns a vector of the compiled note
    /// programs.
    fn compile_notes(
        &self,
        target_account_interface: &[Digest],
        notes: &InputNotes<InputNote>,
        assembly_context: &mut AssemblyContext,
    ) -> Result<Vec<CodeBlock>, TransactionCompilerError> {
        let mut note_programs = Vec::new();

        // Create and verify note programs. Note programs are verified against the target account.
        for recorded_note in notes.iter() {
            let note_program = self
                .assembler
                .compile_in_context(recorded_note.note().script().code(), assembly_context)
                .map_err(TransactionCompilerError::CompileNoteScriptFailed)?;
            verify_program_account_compatibility(
                &note_program,
                target_account_interface,
                ScriptType::NoteScript,
            )?;
            note_programs.push(note_program);
        }

        Ok(note_programs)
    }

    /// Returns a [CodeBlock] of the compiled transaction script program.
    ///
    /// The transaction script compatibility is verified against the target account interface.
    fn compile_tx_script_program(
        &self,
        tx_script: &ProgramAst,
        assembly_context: &mut AssemblyContext,
        target_account_interface: Vec<Digest>,
    ) -> Result<CodeBlock, TransactionCompilerError> {
        let tx_script_code_block = self
            .assembler
            .compile_in_context(tx_script, assembly_context)
            .map_err(TransactionCompilerError::CompileTxScriptFailed)?;
        verify_program_account_compatibility(
            &tx_script_code_block,
            &target_account_interface,
            ScriptType::TransactionScript,
        )?;
        Ok(tx_script_code_block)
    }

    /// Returns the account interface associated with the provided [ScriptTarget].
    ///
    /// # Errors
    /// - If the account interface associated with the [AccountId] provided as a target can not be
    ///   found in the `account_procedures` map.
    fn get_target_interface(
        &self,
        target: ScriptTarget,
    ) -> Result<Vec<Digest>, TransactionCompilerError> {
        match target {
            ScriptTarget::AccountId(id) => self
                .account_procedures
                .get(&id)
                .cloned()
                .ok_or(TransactionCompilerError::AccountInterfaceNotFound(id)),
            ScriptTarget::Procedures(procs) => Ok(procs),
        }
    }
}

impl Default for TransactionCompiler {
    fn default() -> Self {
        Self::new()
    }
}

// TRANSACTION COMPILER HELPERS
// ------------------------------------------------------------------------------------------------

/// Verifies that the provided program is compatible with the target account interface.
///
/// This is achieved by checking that at least one execution branch in the program is compatible
/// with the target account interface.
///
/// # Errors
/// Returns an error if the program is not compatible with the target account interface.
fn verify_program_account_compatibility(
    program: &CodeBlock,
    target_account_interface: &[Digest],
    script_type: ScriptType,
) -> Result<(), TransactionCompilerError> {
    // collect call branches
    let branches = collect_call_branches(program);

    // if none of the branches are compatible with the target account, return an error
    if !branches.iter().any(|call_targets| {
        call_targets.iter().all(|target| target_account_interface.contains(target))
    }) {
        return match script_type {
            ScriptType::NoteScript => {
                Err(TransactionCompilerError::NoteIncompatibleWithAccountInterface(program.hash()))
            },
            ScriptType::TransactionScript => Err(
                TransactionCompilerError::TxScriptIncompatibleWithAccountInterface(program.hash()),
            ),
        };
    }

    Ok(())
}

/// Collect call branches by recursively traversing through program execution branches and
/// accumulating call targets.
fn collect_call_branches(code_block: &CodeBlock) -> Vec<Vec<Digest>> {
    let mut branches = vec![vec![]];
    recursively_collect_call_branches(code_block, &mut branches);
    branches
}

/// Generates a list of calls invoked in each execution branch of the provided code block.
fn recursively_collect_call_branches(code_block: &CodeBlock, branches: &mut Vec<Vec<Digest>>) {
    match code_block {
        CodeBlock::Join(block) => {
            recursively_collect_call_branches(block.first(), branches);
            recursively_collect_call_branches(block.second(), branches);
        },
        CodeBlock::Split(block) => {
            let current_len = branches.last().expect("at least one execution branch").len();
            recursively_collect_call_branches(block.on_false(), branches);

            // If the previous branch had additional calls we need to create a new branch
            if branches.last().expect("at least one execution branch").len() > current_len {
                branches.push(
                    branches.last().expect("at least one execution branch")[..current_len].to_vec(),
                );
            }

            recursively_collect_call_branches(block.on_true(), branches);
        },
        CodeBlock::Loop(block) => {
            recursively_collect_call_branches(block.body(), branches);
        },
        CodeBlock::Call(block) => {
            if block.is_syscall() {
                return;
            }

            branches
                .last_mut()
                .expect("at least one execution branch")
                .push(block.fn_hash());
        },
        CodeBlock::Span(_) => {},
        CodeBlock::Proxy(_) => {},
        CodeBlock::Dyn(_) => {},
    }
}

// SCRIPT TARGET
// ================================================================================================

/// The [ScriptTarget] enum is used to specify the target account interface for note and
/// transaction scripts.
///
/// This is specified as an account ID (for which the interface should be fetched) or a vector of
/// procedure digests which represents the account interface.
#[derive(Clone)]
pub enum ScriptTarget {
    AccountId(AccountId),
    Procedures(Vec<Digest>),
}

// SCRIPT TYPE
// ================================================================================================

enum ScriptType {
    NoteScript,
    TransactionScript,
}