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

fix: handle calls to non-existent contracts in AVM witgen #10862

Merged
merged 4 commits into from
Dec 19, 2024

Conversation

dbanks12
Copy link
Collaborator

@dbanks12 dbanks12 commented Dec 18, 2024

Exceptionally halt & consume all gas on a call to a non-existent contract. Should be able to prove.

Hacked this to work for top-level/enqueued-calls by adding a dummy row (op_add) and then raising an exceptional halt.

Resolves #10373
Resolves #10044

Follow-up work:

  • Add tests for bytecode deserialization failures (sim & witgen)

Comment on lines +114 to +116
if (contract_bytecode.bytecode.size() == 0) {
vinfo("Excluding non-existent contract from bytecode hash columns...");
} else {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only real diff here. We are assuming that if the hinted bytecode is empty, the contract doesn't exist and should therefore be omitted from the bytecode hashing.

Comment on lines -447 to 467
trace_builder.allocate_gas_for_call(l2_gas_allocated_to_enqueued_call, da_gas_allocated_to_enqueued_call);
// Find the bytecode based on contract address of the public call request
std::vector<uint8_t> bytecode =
trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, check_bytecode_membership);
std::vector<uint8_t> bytecode;
try {
bytecode =
trace_builder.get_bytecode(trace_builder.current_ext_call_ctx.contract_address, check_bytecode_membership);
} catch ([[maybe_unused]] const std::runtime_error& e) {
info("AVM enqueued call exceptionally halted. Error: No bytecode found for enqueued call");
// FIXME: properly handle case when bytecode is not found!
// For now, we add a dummy row in main trace to mutate later.
// Dummy row in main trace to mutate afterwards.
// This error was encountered before any opcodes were executed, but
// we need at least one row in the execution trace to then mutate and say "it halted and consumed all gas!"
trace_builder.op_add(0, 0, 0, 0, OpCode::ADD_8);
trace_builder.handle_exceptional_halt();
return AvmError::NO_BYTECODE_FOUND;
;
}

trace_builder.allocate_gas_for_call(l2_gas_allocated_to_enqueued_call, da_gas_allocated_to_enqueued_call);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i COULD have had get_bytecode return [byteode, error]... but this is way simpler...

Comment on lines -462 to 486
// FIXME: properly handle case when an instruction fails parsing
// especially first instruction in bytecode
if (!is_ok(error)) {
info("AVM failed to deserialize bytecode at pc: ", pc);
// FIXME: properly handle case when an instruction fails parsing!
// For now, we add a dummy row in main trace to mutate later.
// This error was encountered before any opcodes were executed, but
// we need at least one row in the execution trace to then mutate and say "it halted and consumed all gas!"
trace_builder.op_add(0, 0, 0, 0, OpCode::ADD_8);
error = parse_error;
break;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested this yet, but it should work! We can write a test for deserialization failures in a follow-up

.internal_return_ptr_stack = {},
.tree_snapshot = {},
};
if (is_ok(error)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just moved into if is ok

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably move some tests into the bulk test. But we can't do that with tests that are expected to revert....

Comment on lines 56 to 75
const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork();
const contractDataSource = new MockedAvmTestContractDataSource();
const contractDataSource = new MockedAvmTestContractDataSource(skipContractDeployments);
const worldStateDB = new WorldStateDB(merkleTrees, contractDataSource);

const contractInstance = contractDataSource.contractInstance;
const contractAddressNullifier = siloNullifier(
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
contractInstance.address.toField(),
);
await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
// other contract address used by the bulk test's GETCONTRACTINSTANCE test
const otherContractAddressNullifier = siloNullifier(
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
contractDataSource.otherContractInstance.address.toField(),
);
await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);

if (!skipContractDeployments) {
const contractAddressNullifier = siloNullifier(
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
contractInstance.address.toField(),
);
await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [contractAddressNullifier.toBuffer()], 0);
// other contract address used by the bulk test's GETCONTRACTINSTANCE test
const otherContractAddressNullifier = siloNullifier(
AztecAddress.fromNumber(DEPLOYER_CONTRACT_ADDRESS),
contractDataSource.otherContractInstance.address.toField(),
);
await merkleTrees.batchInsert(MerkleTreeId.NULLIFIER_TREE, [otherContractAddressNullifier.toBuffer()], 0);
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip contract deployment completely when we want to fail at the top-level due to bytecode not found.

@dbanks12 dbanks12 changed the title fix: handle calls to no-code contracts in AVM witgen fix: handle calls to non-existent contracts in AVM witgen Dec 18, 2024
@dbanks12 dbanks12 merged commit 8820bd5 into master Dec 19, 2024
72 of 77 checks passed
@dbanks12 dbanks12 deleted the db/no-code-contracts branch December 19, 2024 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants