-
Notifications
You must be signed in to change notification settings - Fork 78
Closed
Milestone
Description
With refactoring done in #826, we've removed TransactionCompiler
(as it was no longer necessary). But because of this, we lost one nice feature: ability to estimate if a note/tx script may be executed against a given account. We can only "estimate" this because to know for sure, we'd need to either try to execute the note/tx script or perform some deep static analysis on the underlying code. So, the previous function basically did the following:
- Fail, if we can tell for sure that a note cannot be executed against a given account (e.g., we have a
P2ID
note, but the account does not havereceive_asset
procedure). - Succeed otherwise.
Here is the old code that checked this:
/// 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(_) => {},
}
}
We should bring back this functionality. One question, is where to put this function. I think TransactionExecutor
may be a good option, but there could be alternatives as well.