-
Notifications
You must be signed in to change notification settings - Fork 272
implement insufficient balance state #313
Changes from all commits
57af570
03de9eb
e6b8c5c
2b56efb
375d09a
4ed0916
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,19 @@ | ||||||
# ErrorInsufficientBalance state | ||||||
|
||||||
## Procedure | ||||||
### EVM behavior | ||||||
this type of error only occurs when executing op code is `Call` `CallCode` ,`Create` or `Create2`. | ||||||
For `Call` `CallCode`, it will pop 7 stack elements , and the third is transfer `value` within the call. | ||||||
when caller's balance < `value`, then go to `ErrorInsufficientBalance` state. for this kind of error, the failed | ||||||
call result was pushed into stack, continue to execute next step. | ||||||
|
||||||
### circuit behavior | ||||||
1. pop 7 stack elements, even though the other six elements not closely relevant to this error | ||||||
state constraints, but in order to be accordance with evm trace, also need to handle them here for stack pointer | ||||||
transition, which impact next step's stack status. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
2. lookup current callee address and its balance, then ensure balance < `value` | ||||||
|
||||||
## Code | ||||||
|
||||||
Please refer to `src/zkevm_specs/evm/execution/error_insufficient_balance.py`. |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||
from ...util import FQ | ||||
from ..instruction import Instruction, Transition | ||||
from ..table import CallContextFieldTag, AccountFieldTag | ||||
from ..opcode import Opcode | ||||
|
||||
|
||||
def insufficient_balance(instruction: Instruction): | ||||
opcode = instruction.opcode_lookup(True) | ||||
# TODO: add Create / Create2 in the future | ||||
instruction.constrain_in(opcode, [FQ(Opcode.CALL), FQ(Opcode.CALLCODE)]) | ||||
# TODO: for create/create2 have different stack from Call, will handle it in the future | ||||
# Lookup values from stack | ||||
Comment on lines
+9
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, we should already build the lagrange polynomial now that we will use as a selector for the different possible opcodes. This would be the bear minimum to me. Because otherwise when we implement stuff into |
||||
instruction.stack_pop() | ||||
instruction.stack_pop() | ||||
value_rlc = instruction.stack_pop() | ||||
instruction.stack_pop() | ||||
instruction.stack_pop() | ||||
instruction.stack_pop() | ||||
instruction.stack_pop() | ||||
is_success_rlc = instruction.stack_push() | ||||
# if is_success_rlc value is zero then decode RLC should also be zero | ||||
instruction.constrain_zero(is_success_rlc) | ||||
|
||||
value = instruction.rlc_to_fq(value_rlc, 31) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of
|
||||
current_address = instruction.call_context_lookup(CallContextFieldTag.CalleeAddress) | ||||
caller_balance_rlc = instruction.account_read(current_address, AccountFieldTag.Balance) | ||||
caller_balance = instruction.rlc_to_fq(caller_balance_rlc, 31) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||||
# compare value and balance | ||||
insufficient_balance, _ = instruction.compare(caller_balance, value, 31) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||||
|
||||
instruction.constrain_equal(insufficient_balance, FQ(1)) | ||||
|
||||
# Do step state transition | ||||
instruction.constrain_step_state_transition( | ||||
call_id=Transition.same(), | ||||
rw_counter=Transition.delta(10), | ||||
program_counter=Transition.delta(1), | ||||
stack_pointer=Transition.delta(6), | ||||
# TODO: handle gas_left | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should add the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should at least handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @CPerezz thanks for reviewing, yes, I am considering refactoring this merging into |
||||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import pytest | ||
|
||
from zkevm_specs.evm import ( | ||
ExecutionState, | ||
StepState, | ||
Opcode, | ||
verify_steps, | ||
Tables, | ||
Block, | ||
Bytecode, | ||
RWDictionary, | ||
CallContextFieldTag, | ||
AccountFieldTag, | ||
) | ||
from zkevm_specs.util import rand_fq, RLC | ||
from itertools import chain | ||
from collections import namedtuple | ||
|
||
|
||
TESTING_DATA = ( | ||
# balance | transfer value | ||
(200, 250), | ||
(1, 2), | ||
) | ||
|
||
|
||
@pytest.mark.parametrize("balance, transfer_value", TESTING_DATA) | ||
def test_insufficient_balance(balance: int, transfer_value: int): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have test cases for both |
||
randomness = rand_fq() | ||
|
||
block = Block() | ||
bytecode = Bytecode().call(0, 0xFC, transfer_value, 0, 0, 0, 0).stop() | ||
bytecode_hash = RLC(bytecode.hash(), randomness) | ||
|
||
tables = Tables( | ||
block_table=set(block.table_assignments(randomness)), | ||
tx_table=set(), | ||
bytecode_table=set(bytecode.table_assignments(randomness)), | ||
rw_table=set( | ||
RWDictionary(9) | ||
.stack_read(1, 1010, RLC(10000, randomness)) # gas | ||
.stack_read(1, 1011, RLC(0xFC, randomness)) # address | ||
.stack_read(1, 1012, RLC(transfer_value, randomness)) # value | ||
.stack_read(1, 1013, RLC(0, randomness)) | ||
.stack_read(1, 1014, RLC(10, randomness)) | ||
.stack_read(1, 1015, RLC(0, randomness)) | ||
.stack_read(1, 1016, RLC(0, randomness)) | ||
.stack_write(1, 1016, RLC(0, randomness)) | ||
.call_context_read(1, CallContextFieldTag.CalleeAddress, 0xFE) | ||
.account_read(0xFE, AccountFieldTag.Balance, RLC(balance, randomness)) | ||
.rws | ||
), | ||
) | ||
|
||
verify_steps( | ||
randomness=randomness, | ||
tables=tables, | ||
steps=[ | ||
StepState( | ||
execution_state=ExecutionState.ErrorInsufficientBalance, | ||
rw_counter=9, | ||
call_id=1, | ||
is_root=True, | ||
is_create=False, | ||
code_hash=bytecode_hash, | ||
program_counter=231, | ||
stack_pointer=1010, | ||
gas_left=8, | ||
), | ||
StepState( | ||
execution_state=ExecutionState.STOP, | ||
program_counter=232, | ||
rw_counter=19, | ||
call_id=1, | ||
stack_pointer=1016, | ||
gas_left=0, | ||
), | ||
], | ||
) | ||
|
||
|
||
CallContext = namedtuple( | ||
"CallContext", | ||
[ | ||
"is_root", | ||
"is_create", | ||
"program_counter", | ||
"stack_pointer", | ||
"gas_left", | ||
"memory_size", | ||
"reversible_write_counter", | ||
], | ||
defaults=[True, False, 232, 1023, 10, 0, 0], | ||
) | ||
|
||
TESTING_DATA_NOT_ROOT = ((CallContext(), 100, 101),) |
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.