-
Notifications
You must be signed in to change notification settings - Fork 234
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
feat: calls to non-existent contracts in the AVM simulator return failure #10051
base: master
Are you sure you want to change the base?
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
it(`Nested call to non-existent contract `, async () => { | ||
const calldata = [value0, value1]; | ||
const context = createContext(calldata); | ||
const callBytecode = getAvmTestContractBytecode('nested_call_to_add'); | ||
|
||
const nestedTrace = mock<PublicSideEffectTraceInterface>(); | ||
mockTraceFork(trace, nestedTrace); | ||
|
||
const results = await new AvmSimulator(context).executeBytecode(callBytecode); | ||
expect(results.reverted).toBe(true); | ||
expect(results.output).toEqual([]); | ||
|
||
expectTracedNestedCall(context.environment, nestedTrace, /*isStaticCall=*/ false, /*exists=*/ false); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing non-existent contract by not mocking getBytecode
// This assumes that we will not be able to send messages to accounts without code | ||
// Pending classes and instances impl details | ||
if (!bytecode) { | ||
throw new NoBytecodeForContractError(this.context.environment.address); | ||
// revert without consuming any gas | ||
const message = `No bytecode found at: ${this.context.environment.address}. Reverting...`; | ||
const revertReason = new AvmRevertReason( | ||
message, | ||
/*failingFunction=*/ { | ||
contractAddress: this.context.environment.address, | ||
functionSelector: this.context.environment.functionSelector, | ||
}, | ||
/*noirCallStack=*/ [], | ||
); | ||
this.log.warn(message); | ||
return new AvmContractCallResult( | ||
/*reverted=*/ true, | ||
/*output=*/ [], | ||
/*gasLeft=*/ this.context.machineState.gasLeft, | ||
revertReason, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it bad to hand-craft an AvmRevertReason
like this?
// should only charge gas for the call instruction itself | ||
// (all gas allocated to the nested call should be refunded) | ||
const callGasCost = instruction.gasCost(argsSize); | ||
expect(context.machineState.l2GasLeft).toEqual(initialL2Gas - callGasCost.l2Gas); | ||
expect(context.machineState.daGasLeft).toEqual(initialDaGas - callGasCost.daGas); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed instruction.gasCost
from protected to public so that I can confirm here that the external call is charging no more than the cost of the call opcode itself. Is that an acceptable decision?
if (!bytecode) { | ||
throw new NoBytecodeForContractError(this.context.environment.address); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we "throw" then we'd need to wrap calls to the AVM in try-catch. It would be fine for external calls, but breaks things for enqueued calls.
A call to a non-existent contract does nothing and returns failure. The nested context does not consume any gas. The
getBytecode
is still traced because the circuit will need a hint to know that it should do a non-membership check of the contract.If it is a nested call, the CALL opcode consumes its regular gas, but the nested context consumes nothing, so the entire gas allocation is refunded to the caller.