Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ability to determine if a note/tx script cannot be executed against a given account #831

Open
bobbinth opened this issue Aug 18, 2024 · 0 comments

Comments

@bobbinth
Copy link
Contributor

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 have receive_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant