Skip to content

Commit

Permalink
Insufficient caller balance (CREATE/CREATE2) (scroll-tech#453)
Browse files Browse the repository at this point in the history
* chore: refactor to support insuffbal for create(2)

* chore: make existing opcode impl compatible

* test: insufficient balance failing test scenario

* feat: handle state transition for insufficient balance (create/create2)

* fix: bus-mapping tests compilation

* fix: rw index for is_success

* fix: handle value == 0 (revert previously made change)
  • Loading branch information
roynalnaruto authored Apr 9, 2023
1 parent f3ebc6a commit ad03460
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 142 deletions.
17 changes: 15 additions & 2 deletions bus-mapping/src/circuit_input_builder/input_state_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::{
TransactionContext,
};
use crate::{
error::{get_step_reported_error, ExecError},
error::{get_step_reported_error, ExecError, InsufficientBalanceError},
exec_trace::OperationRef,
operation::{
AccountField, AccountOp, CallContextField, CallContextOp, MemoryOp, Op, OpEnum, Operation,
Expand Down Expand Up @@ -1390,7 +1390,20 @@ impl<'a> CircuitInputStateRef<'a> {
return Err(Error::AccountNotFound(sender));
}
if account.balance < value {
return Ok(Some(ExecError::InsufficientBalance));
return Ok(Some(match step.op {
OpcodeId::CALL | OpcodeId::CALLCODE => {
ExecError::InsufficientBalance(InsufficientBalanceError::Call)
}
OpcodeId::CREATE => {
ExecError::InsufficientBalance(InsufficientBalanceError::Create)
}
OpcodeId::CREATE2 => {
ExecError::InsufficientBalance(InsufficientBalanceError::Create2)
}
op => {
unreachable!("insufficient balance error unexpected for opcode: {:?}", op)
}
}));
}

// Address collision
Expand Down
6 changes: 4 additions & 2 deletions bus-mapping/src/circuit_input_builder/tracer_tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;
use crate::{
circuit_input_builder::access::gen_state_access_trace,
error::ExecError,
error::{ExecError, InsufficientBalanceError},
geth_errors::{
GETH_ERR_GAS_UINT_OVERFLOW, GETH_ERR_OUT_OF_GAS, GETH_ERR_STACK_OVERFLOW,
GETH_ERR_STACK_UNDERFLOW,
Expand Down Expand Up @@ -289,7 +289,9 @@ fn tracer_err_insufficient_balance() {
let mut builder = CircuitInputBuilderTx::new(&block, step);
assert_eq!(
builder.state_ref().get_step_err(step, next_step).unwrap(),
Some(ExecError::InsufficientBalance)
Some(ExecError::InsufficientBalance(
InsufficientBalanceError::Call
))
);
}

Expand Down
15 changes: 13 additions & 2 deletions bus-mapping/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ pub enum OogError {
SelfDestruct,
}

/// Insufficient balance errors by opcode/state.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InsufficientBalanceError {
/// Insufficient balance during CALL/CALLCODE opcode.
Call,
/// Insufficient balance during CREATE opcode.
Create,
/// Insufficient balance during CREATE2 opcode.
Create2,
}

/// EVM Execution Error
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExecError {
Expand All @@ -114,8 +125,8 @@ pub enum ExecError {
WriteProtection,
/// For CALL, CALLCODE, DELEGATECALL, STATICCALL
Depth,
/// For CALL, CALLCODE
InsufficientBalance,
/// For CALL, CALLCODE, CREATE, CREATE2
InsufficientBalance(InsufficientBalanceError),
/// For CREATE, CREATE2
ContractAddressCollision,
/// contract must not begin with 0xef due to EIP #3541 EVM Object Format
Expand Down
14 changes: 9 additions & 5 deletions bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Definition of each opcode of the EVM.
use crate::{
circuit_input_builder::{CircuitInputStateRef, ExecStep},
error::{ExecError, OogError},
error::{ExecError, InsufficientBalanceError, OogError},
evm::OpcodeId,
operation::{
AccountField, AccountOp, CallContextField, TxAccessListAccountOp, TxReceiptField,
Expand Down Expand Up @@ -317,12 +317,16 @@ fn fn_gen_error_state_associated_ops(
ExecError::CodeStoreOutOfGas => Some(ErrorCodeStore::gen_associated_ops),
ExecError::MaxCodeSizeExceeded => Some(ErrorCodeStore::gen_associated_ops),
// call & callcode can encounter InsufficientBalance error, Use pop-7 generic CallOpcode
ExecError::InsufficientBalance => {
if geth_step.op.is_create() {
unimplemented!("insufficient balance for create");
}
ExecError::InsufficientBalance(InsufficientBalanceError::Call) => {
Some(CallOpcode::<7>::gen_associated_ops)
}
// create & create2 can encounter insufficient balance.
ExecError::InsufficientBalance(InsufficientBalanceError::Create) => {
Some(Create::<false>::gen_associated_ops)
}
ExecError::InsufficientBalance(InsufficientBalanceError::Create2) => {
Some(Create::<true>::gen_associated_ops)
}
ExecError::PrecompileFailed => Some(PrecompileFailed::gen_associated_ops),
ExecError::WriteProtection => Some(ErrorWriteProtection::gen_associated_ops),
ExecError::ReturnDataOutOfBounds => Some(ErrorReturnDataOutOfBound::gen_associated_ops),
Expand Down
22 changes: 21 additions & 1 deletion bus-mapping/src/evm/opcodes/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ impl<const IS_CREATE2: bool> Opcode for Create<IS_CREATE2> {
value_prev: caller_nonce.into(),
},
)?;
let caller_balance = state.sdb.get_balance(&callee.caller_address);
state.account_read(
&mut exec_step,
callee.caller_address,
AccountField::Balance,
caller_balance,
);
let insufficient_balance = callee.value > caller_balance;

// TODO: look into when this can be pushed. Could it be done in parse call?
state.push_call(callee.clone());
Expand Down Expand Up @@ -142,7 +150,7 @@ impl<const IS_CREATE2: bool> Opcode for Create<IS_CREATE2> {
code_hash_previous.to_word(),
);

if !callee_exists {
if !callee_exists && !insufficient_balance {
state.transfer(
&mut exec_step,
callee.caller_address,
Expand Down Expand Up @@ -192,6 +200,18 @@ impl<const IS_CREATE2: bool> Opcode for Create<IS_CREATE2> {
caller.depth.to_word(),
);

if insufficient_balance {
for (field, value) in [
(CallContextField::LastCalleeId, 0.into()),
(CallContextField::LastCalleeReturnDataOffset, 0.into()),
(CallContextField::LastCalleeReturnDataLength, 0.into()),
] {
state.call_context_write(&mut exec_step, caller.call_id, field, value);
}
state.handle_return(geth_step)?;
return Ok(vec![exec_step]);
}

for (field, value) in [
(CallContextField::CallerId, caller.call_id.into()),
(CallContextField::IsSuccess, callee.is_success.to_word()),
Expand Down
6 changes: 6 additions & 0 deletions bus-mapping/src/state_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ impl StateDB {
self.dirty_storage.insert((*addr, *key), *value);
}

/// Get balance of account with the given address.
pub fn get_balance(&self, addr: &Address) -> Word {
let (_, account) = self.get_account(addr);
account.balance
}

/// Get nonce of account with `addr`.
pub fn get_nonce(&self, addr: &Address) -> u64 {
let (_, account) = self.get_account(addr);
Expand Down
7 changes: 0 additions & 7 deletions zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ pub(crate) struct ExecutionConfig<F> {
error_code_store: Box<ErrorCodeStoreGadget<F>>,
error_oog_self_destruct:
Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasSELFDESTRUCT }>>,
error_insufficient_balance:
Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorInsufficientBalance }>>,
error_nonce_uint_overflow:
Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorNonceUintOverflow }>>,
error_invalid_jump: Box<ErrorInvalidJumpGadget<F>>,
Expand Down Expand Up @@ -597,7 +595,6 @@ impl<F: Field> ExecutionConfig<F> {
error_oog_create2: configure_gadget!(),
error_oog_self_destruct: configure_gadget!(),
error_code_store: configure_gadget!(),
error_insufficient_balance: configure_gadget!(),
error_invalid_jump: configure_gadget!(),
error_invalid_opcode: configure_gadget!(),
error_write_protection: configure_gadget!(),
Expand Down Expand Up @@ -1424,10 +1421,6 @@ impl<F: Field> ExecutionConfig<F> {
ExecutionState::ErrorStack => {
assign_exec_step!(self.error_stack)
}

ExecutionState::ErrorInsufficientBalance => {
assign_exec_step!(self.error_insufficient_balance)
}
ExecutionState::ErrorInvalidJump => {
assign_exec_step!(self.error_invalid_jump)
}
Expand Down
Loading

0 comments on commit ad03460

Please sign in to comment.