Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Streamline the math gadget API (#1765)
Browse files Browse the repository at this point in the history
### Description

We improve the readability by sweeping gadget constructors under the
constraint builder.


### Issue Link

Quick search and replace

### Type of change

Refactor (no updates to logic)

### Contents

- changed `XGadget::construct(cb, ...)` to `cb.x(...)`
- some IsZero checks are replaced with IsEqual checks.

### Rationale

We keep consumption of the following gadgets unchanged because they are
not used frequently enough.

-  abs_word
-  add_word
-  binary_number
-  byte_size
-  cmp_words
-  comparison
-  modulo
-  mul_add_words
-  mul_add_words512
-  mul_word_u64
  • Loading branch information
ChihChengLiang authored Feb 15, 2024
1 parent 3151efd commit cd5edab
Show file tree
Hide file tree
Showing 50 changed files with 230 additions and 189 deletions.
2 changes: 1 addition & 1 deletion zkevm-circuits/src/evm_circuit/execution/addmod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl<F: Field> ExecutionGadget<F> for AddModGadget<F> {
let a_reduced = cb.query_word32();
let d = cb.query_word32();

let n_is_zero = IsZeroWordGadget::construct(cb, &n);
let n_is_zero = cb.is_zero_word(&n);

// 1. check k * N + a_reduced == a without overflow
let muladd_k_n_areduced =
Expand Down
2 changes: 1 addition & 1 deletion zkevm-circuits/src/evm_circuit/execution/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<F: Field> ExecutionGadget<F> for BalanceGadget<F> {
AccountFieldTag::CodeHash,
code_hash.to_word(),
);
let not_exists = IsZeroWordGadget::construct(cb, &code_hash);
let not_exists = cb.is_zero_word(&code_hash);
let exists = not::expr(not_exists.expr());
let balance = cb.query_word32();
cb.condition(exists.expr(), |cb| {
Expand Down
7 changes: 3 additions & 4 deletions zkevm-circuits/src/evm_circuit/execution/begin_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {
let gas_left = tx.gas.expr() - tx.intrinsic_gas();
let sufficient_gas_left = RangeCheckGadget::construct(cb, gas_left.clone());

let tx_caller_address_is_zero = IsZeroWordGadget::construct(cb, &tx.caller_address);
let tx_caller_address_is_zero = cb.is_zero_word(&tx.caller_address);
cb.require_equal(
"CallerAddress != 0 (not a padding tx)",
tx_caller_address_is_zero.expr(),
Expand Down Expand Up @@ -155,9 +155,8 @@ impl<F: Field> ExecutionGadget<F> for BeginTxGadget<F> {

// Read code_hash of callee
let code_hash = cb.query_word_unchecked();
let is_empty_code_hash =
IsEqualWordGadget::construct(cb, &code_hash.to_word(), &cb.empty_code_hash());
let callee_not_exists = IsZeroWordGadget::construct(cb, &code_hash);
let is_empty_code_hash = cb.is_eq_word(&code_hash.to_word(), &cb.empty_code_hash());
let callee_not_exists = cb.is_zero_word(&code_hash);
// no_callee_code is true when the account exists and has empty
// code hash, or when the account doesn't exist (which we encode with
// code_hash = 0).
Expand Down
2 changes: 1 addition & 1 deletion zkevm-circuits/src/evm_circuit/execution/byte.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl<F: Field> ExecutionGadget<F> for ByteGadget<F> {
let is_byte_selected = array_init(|idx| {
// Check if this byte is selected looking only at the LSB of the
// index word
IsEqualGadget::construct(cb, index.limbs[0].expr(), (31 - idx).expr())
cb.is_eq(index.limbs[0].expr(), (31 - idx).expr())
});

// Sum all possible selected bytes
Expand Down
58 changes: 29 additions & 29 deletions zkevm-circuits/src/evm_circuit/execution/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::{
Transition::{Delta, To},
},
math_gadget::{
ConstantDivisionGadget, IsZeroGadget, LtGadget, LtWordGadget, MinMaxGadget,
ConstantDivisionGadget, IsEqualGadget, IsZeroGadget, LtGadget, LtWordGadget,
MinMaxGadget,
},
memory_gadget::{CommonMemoryAddressGadget, MemoryAddressGadget},
not, or,
Expand Down Expand Up @@ -44,10 +45,10 @@ use std::cmp::min;

pub(crate) struct CallOpGadget<F> {
opcode: Cell<F>,
is_call: IsZeroGadget<F>,
is_callcode: IsZeroGadget<F>,
is_delegatecall: IsZeroGadget<F>,
is_staticcall: IsZeroGadget<F>,
is_call: IsEqualGadget<F>,
is_callcode: IsEqualGadget<F>,
is_delegatecall: IsEqualGadget<F>,
is_staticcall: IsEqualGadget<F>,
tx_id: Cell<F>,
reversion_info: ReversionInfo<F>,
current_callee_address: WordLoHiCell<F>,
Expand Down Expand Up @@ -94,12 +95,10 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
let opcode = cb.query_cell();
cb.opcode_lookup(opcode.expr(), 1.expr());
let is_call = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CALL.expr());
let is_callcode = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CALLCODE.expr());
let is_delegatecall =
IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::DELEGATECALL.expr());
let is_staticcall =
IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::STATICCALL.expr());
let is_call = cb.is_eq(opcode.expr(), OpcodeId::CALL.expr());
let is_callcode = cb.is_eq(opcode.expr(), OpcodeId::CALLCODE.expr());
let is_delegatecall = cb.is_eq(opcode.expr(), OpcodeId::DELEGATECALL.expr());
let is_staticcall = cb.is_eq(opcode.expr(), OpcodeId::STATICCALL.expr());

// Use rw_counter of the step which triggers next call as its call_id.
let callee_call_id = cb.curr.state.rw_counter.clone();
Expand Down Expand Up @@ -196,9 +195,9 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
// rwc_delta = 8 + is_delegatecall * 2 + call_gadget.rw_delta() +
// callee_reversion_info.rw_delta()
let is_insufficient_balance =
LtWordGadget::construct(cb, &caller_balance.to_word(), &call_gadget.value.to_word());
cb.is_lt_word(&caller_balance.to_word(), &call_gadget.value.to_word());
// depth < 1025
let is_depth_ok = LtGadget::construct(cb, depth.expr(), 1025.expr());
let is_depth_ok = cb.is_lt(depth.expr(), 1025.expr());

let is_precheck_ok = and::expr([
is_depth_ok.expr(),
Expand All @@ -215,18 +214,15 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {

// whether the call is to a precompiled contract.
// precompile contracts are stored from address 0x01 to 0x09.
let is_code_address_zero = IsZeroGadget::construct(cb, call_gadget.callee_address.expr());
let is_precompile_lt =
LtGadget::construct(cb, call_gadget.callee_address.expr(), 0x0A.expr());
let is_code_address_zero = cb.is_zero(call_gadget.callee_address.expr());
let is_precompile_lt = cb.is_lt(call_gadget.callee_address.expr(), 0x0A.expr());
let is_precompile = and::expr([
not::expr(is_code_address_zero.expr()),
is_precompile_lt.expr(),
]);
let precompile_return_length = cb.query_cell();
let precompile_return_length_zero =
IsZeroGadget::construct(cb, precompile_return_length.expr());
let precompile_return_data_copy_size = MinMaxGadget::construct(
cb,
let precompile_return_length_zero = cb.is_zero(precompile_return_length.expr());
let precompile_return_data_copy_size = cb.min_max(
precompile_return_length.expr(),
call_gadget.rd_address.length(),
);
Expand Down Expand Up @@ -275,10 +271,10 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
let gas_cost = call_gadget.gas_cost_expr(is_warm_prev.expr(), is_call.expr());
// Apply EIP 150
let gas_available = cb.curr.state.gas_left.expr() - gas_cost.clone();
let one_64th_gas = ConstantDivisionGadget::construct(cb, gas_available.clone(), 64);
let one_64th_gas = cb.div_by_const(gas_available.clone(), 64);
let all_but_one_64th_gas = gas_available - one_64th_gas.quotient();
let capped_callee_gas_left =
MinMaxGadget::construct(cb, call_gadget.gas_expr(), all_but_one_64th_gas.clone());
cb.min_max(call_gadget.gas_expr(), all_but_one_64th_gas.clone());
let callee_gas_left = select::expr(
call_gadget.gas_is_u64.expr(),
capped_callee_gas_left.min(),
Expand Down Expand Up @@ -480,10 +476,10 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
let callee_gas_left = callee_gas_left.expr()
+ call_gadget.has_value.clone() * GAS_STIPEND_CALL_WITH_VALUE.expr();

let precompile_output_word_size_div: ConstantDivisionGadget<F, N_BYTES_U64> =
ConstantDivisionGadget::construct(cb, precompile_output_rws.expr(), 32);
let precompile_output_word_size_div =
cb.div_by_const(precompile_output_rws.expr(), 32);
let precompile_output_word_size_div_remainder_zero =
IsZeroGadget::construct(cb, precompile_output_word_size_div.remainder());
cb.is_zero(precompile_output_word_size_div.remainder());
let precompile_output_word_size = precompile_output_word_size_div.quotient()
+ 1.expr()
- precompile_output_word_size_div_remainder_zero.expr();
Expand Down Expand Up @@ -891,22 +887,26 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
self.is_call.assign(
region,
offset,
F::from(opcode.as_u64()) - F::from(OpcodeId::CALL.as_u64()),
F::from(opcode.as_u64()),
F::from(OpcodeId::CALL.as_u64()),
)?;
self.is_callcode.assign(
region,
offset,
F::from(opcode.as_u64()) - F::from(OpcodeId::CALLCODE.as_u64()),
F::from(opcode.as_u64()),
F::from(OpcodeId::CALLCODE.as_u64()),
)?;
self.is_delegatecall.assign(
region,
offset,
F::from(opcode.as_u64()) - F::from(OpcodeId::DELEGATECALL.as_u64()),
F::from(opcode.as_u64()),
F::from(OpcodeId::DELEGATECALL.as_u64()),
)?;
self.is_staticcall.assign(
region,
offset,
F::from(opcode.as_u64()) - F::from(OpcodeId::STATICCALL.as_u64()),
F::from(opcode.as_u64()),
F::from(OpcodeId::STATICCALL.as_u64()),
)?;
self.tx_id
.assign(region, offset, Value::known(F::from(tx_id.low_u64())))?;
Expand Down
4 changes: 2 additions & 2 deletions zkevm-circuits/src/evm_circuit/execution/comparator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ impl<F: Field> ExecutionGadget<F> for ComparatorGadget<F> {
let b = cb.query_word_unchecked();

// Check if opcode is EQ
let is_eq = IsEqualGadget::construct(cb, opcode.expr(), OpcodeId::EQ.expr());
let is_eq = cb.is_eq(opcode.expr(), OpcodeId::EQ.expr());
// Check if opcode is GT. For GT we swap the stack inputs so that we
// actually do greater than instead of smaller than.
let is_gt = IsEqualGadget::construct(cb, opcode.expr(), OpcodeId::GT.expr());
let is_gt = cb.is_eq(opcode.expr(), OpcodeId::GT.expr());

let word_comparison = CmpWordsGadget::construct(cb, a.clone(), b.clone());

Expand Down
20 changes: 10 additions & 10 deletions zkevm-circuits/src/evm_circuit/execution/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
Transition::{Delta, To},
},
math_gadget::{
ConstantDivisionGadget, ContractCreateGadget, IsZeroGadget, IsZeroWordGadget,
ConstantDivisionGadget, ContractCreateGadget, IsEqualGadget, IsZeroWordGadget,
LtGadget, LtWordGadget,
},
memory_gadget::{
Expand Down Expand Up @@ -53,7 +53,7 @@ pub(crate) struct CreateGadget<F, const IS_CREATE2: bool, const S: ExecutionStat
reversion_info: ReversionInfo<F>,
depth: Cell<F>,

is_create2: IsZeroGadget<F>,
is_create2: IsEqualGadget<F>,
is_success: Cell<F>,
was_warm: Cell<F>,
value: Word32Cell<F>,
Expand Down Expand Up @@ -95,7 +95,7 @@ impl<F: Field, const IS_CREATE2: bool, const S: ExecutionState> ExecutionGadget<
opcode.expr(),
vec![OpcodeId::CREATE2.expr(), OpcodeId::CREATE.expr()],
);
let is_create2 = IsZeroGadget::construct(cb, opcode.expr() - OpcodeId::CREATE2.expr());
let is_create2 = cb.is_eq(opcode.expr(), OpcodeId::CREATE2.expr());

// Use rw_counter of the step which triggers next call as its call_id.
let callee_call_id = cb.curr.state.rw_counter.clone();
Expand Down Expand Up @@ -143,10 +143,9 @@ impl<F: Field, const IS_CREATE2: bool, const S: ExecutionState> ExecutionGadget<
);

// Pre-check: call depth, user's nonce and user's balance
let is_depth_in_range = LtGadget::construct(cb, depth.expr(), 1025.expr());
let is_insufficient_balance =
LtWordGadget::construct(cb, &caller_balance.to_word(), &value.to_word());
let is_nonce_in_range = LtGadget::construct(cb, caller_nonce.expr(), u64::MAX.expr());
let is_depth_in_range = cb.is_lt(depth.expr(), 1025.expr());
let is_insufficient_balance = cb.is_lt_word(&caller_balance.to_word(), &value.to_word());
let is_nonce_in_range = cb.is_lt(caller_nonce.expr(), u64::MAX.expr());
let is_precheck_ok = and::expr([
is_depth_in_range.expr(),
not::expr(is_insufficient_balance.expr()),
Expand All @@ -169,7 +168,7 @@ impl<F: Field, const IS_CREATE2: bool, const S: ExecutionState> ExecutionGadget<
);
let gas_cost = GasCost::CREATE.expr() + memory_expansion.gas_cost() + keccak_gas_cost;
let gas_remaining = cb.curr.state.gas_left.expr() - gas_cost.clone();
let gas_left = ConstantDivisionGadget::construct(cb, gas_remaining.clone(), 64);
let gas_left = cb.div_by_const(gas_remaining.clone(), 64);
let callee_gas_left = gas_remaining - gas_left.quotient();

let was_warm = cb.query_bool();
Expand Down Expand Up @@ -209,7 +208,7 @@ impl<F: Field, const IS_CREATE2: bool, const S: ExecutionState> ExecutionGadget<
// empty_code_hash_word))` to represent `(callee_nonce == 0 && (prev_code_hash_word
// == 0 or prev_code_hash_word == empty_code_hash_word))`
let prev_code_hash_word = prev_code_hash.to_word();
let prev_code_hash_is_zero = IsZeroWordGadget::construct(cb, &prev_code_hash_word);
let prev_code_hash_is_zero = cb.is_zero_word(&prev_code_hash_word);
cb.condition(not::expr(prev_code_hash_is_zero.expr()), |cb| {
cb.account_read(
contract_addr.to_word(),
Expand Down Expand Up @@ -525,7 +524,8 @@ impl<F: Field, const IS_CREATE2: bool, const S: ExecutionState> ExecutionGadget<
self.is_create2.assign(
region,
offset,
F::from(opcode.as_u64()) - F::from(OpcodeId::CREATE2.as_u64()),
F::from(opcode.as_u64()),
F::from(OpcodeId::CREATE2.as_u64()),
)?;

self.tx_id
Expand Down
11 changes: 5 additions & 6 deletions zkevm-circuits/src/evm_circuit/execution/end_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
constraint_builder::{
ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition, Transition::Same,
},
math_gadget::{IsEqualGadget, IsZeroGadget},
math_gadget::IsEqualGadget,
not, CachedRegion, Cell,
},
witness::{Block, Call, ExecStep, Transaction},
Expand All @@ -22,7 +22,7 @@ use halo2_proofs::{circuit::Value, plonk::Error};
pub(crate) struct EndBlockGadget<F> {
total_txs: Cell<F>,
total_txs_is_max_txs: IsEqualGadget<F>,
is_empty_block: IsZeroGadget<F>,
is_empty_block: IsEqualGadget<F>,
max_rws: Cell<F>,
max_txs: Cell<F>,
}
Expand All @@ -36,10 +36,9 @@ impl<F: Field> ExecutionGadget<F> for EndBlockGadget<F> {
let max_txs = cb.query_copy_cell();
let max_rws = cb.query_copy_cell();
let total_txs = cb.query_cell();
let total_txs_is_max_txs = IsEqualGadget::construct(cb, total_txs.expr(), max_txs.expr());
let total_txs_is_max_txs = cb.is_eq(total_txs.expr(), max_txs.expr());
// Note that rw_counter starts at 1
let is_empty_block =
IsZeroGadget::construct(cb, cb.curr.state.rw_counter.clone().expr() - 1.expr());
let is_empty_block = cb.is_eq(cb.curr.state.rw_counter.clone().expr(), 1.expr());

let total_rws_before_padding = cb.curr.state.rw_counter.clone().expr() - 1.expr()
+ select::expr(
Expand Down Expand Up @@ -126,7 +125,7 @@ impl<F: Field> ExecutionGadget<F> for EndBlockGadget<F> {
step: &ExecStep,
) -> Result<(), Error> {
self.is_empty_block
.assign(region, offset, F::from(u64::from(step.rwc) - 1))?;
.assign(region, offset, F::from(u64::from(step.rwc)), F::ONE)?;
let max_rws = F::from(block.circuits_params.max_rws as u64);
let max_rws_assigned = self.max_rws.assign(region, offset, Value::known(max_rws))?;

Expand Down
4 changes: 2 additions & 2 deletions zkevm-circuits/src/evm_circuit/execution/end_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
);
let refund = cb.query_cell();
cb.tx_refund_read(tx_id.expr(), WordLoHi::from_lo_unchecked(refund.expr()));
let effective_refund = MinMaxGadget::construct(cb, max_refund.quotient(), refund.expr());
let effective_refund = cb.min_max(max_refund.quotient(), refund.expr());

// Add effective_refund * tx_gas_price back to caller's balance
let mul_gas_price_by_refund = MulWordByU64Gadget::construct(
Expand All @@ -85,7 +85,7 @@ impl<F: Field> ExecutionGadget<F> for EndTxGadget<F> {
// Add gas_used * effective_tip to coinbase's balance
let coinbase = cb.query_word_unchecked();
let coinbase_code_hash = cb.query_word_unchecked();
let coinbase_code_hash_is_zero = IsZeroWordGadget::construct(cb, &coinbase_code_hash);
let coinbase_code_hash_is_zero = cb.is_zero_word(&coinbase_code_hash);
cb.account_read(
coinbase.to_word(),
AccountFieldTag::CodeHash,
Expand Down
3 changes: 1 addition & 2 deletions zkevm-circuits/src/evm_circuit/execution/error_code_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorCodeStoreGadget<F> {
);

// constrain code size > MAXCODESIZE
let max_code_size_exceed =
LtGadget::construct(cb, MAXCODESIZE.expr(), memory_address.length());
let max_code_size_exceed = cb.is_lt(MAXCODESIZE.expr(), memory_address.length());

// check must be one of CodeStoreOutOfGas or MaxCodeSizeExceeded
cb.require_in_set(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorInvalidCreationCodeGadget<F> {
cb.memory_lookup(0.expr(), memory_address.offset(), first_byte.expr(), None);

let is_first_byte_invalid =
IsEqualGadget::construct(cb, first_byte.expr(), INVALID_INIT_CODE_FIRST_BYTE.expr());
cb.is_eq(first_byte.expr(), INVALID_INIT_CODE_FIRST_BYTE.expr());
cb.require_true(
"is_first_byte_invalid is true",
is_first_byte_invalid.expr(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ impl<F: Field> ExecutionGadget<F> for ErrorInvalidJumpGadget<F> {
vec![OpcodeId::JUMP.expr(), OpcodeId::JUMPI.expr()],
);

let is_jumpi = IsEqualGadget::construct(cb, opcode.expr(), OpcodeId::JUMPI.expr());
let is_jumpi = cb.is_eq(opcode.expr(), OpcodeId::JUMPI.expr());

// initialize is_jump_dest
let is_jump_dest = IsEqualGadget::construct(cb, value.expr(), OpcodeId::JUMPDEST.expr());
let is_jump_dest = cb.is_eq(value.expr(), OpcodeId::JUMPDEST.expr());

// first default this condition, if use will re-construct with real condition
// value
let is_condition_zero = IsZeroWordGadget::construct(cb, &condition);
let is_condition_zero = cb.is_zero_word(&condition);

// Pop the value from the stack
cb.stack_pop(dest.original_word().to_word());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorOOGAccountAccessGadget<F> {
GasCost::COLD_ACCOUNT_ACCESS.expr(),
);

let insufficient_gas_cost =
LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_cost);
let insufficient_gas_cost = cb.is_lt(cb.curr.state.gas_left.expr(), gas_cost);

cb.require_equal(
"Gas left is less than gas cost",
Expand Down
2 changes: 1 addition & 1 deletion zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorOOGCallGadget<F> {
let gas_cost = call_gadget.gas_cost_expr(is_warm.expr(), is_call.expr());

// Check if the amount of gas available is less than the amount of gas required
let insufficient_gas = LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_cost);
let insufficient_gas = cb.is_lt(cb.curr.state.gas_left.expr(), gas_cost);

cb.require_equal(
"Either Memory address is overflow or gas left is less than cost",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorOOGConstantGadget<F> {
cb.constant_gas_lookup(opcode.expr(), gas_required.expr());
// Check if the amount of gas available is less than the amount of gas
// required
let insufficient_gas =
LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_required.expr());
let insufficient_gas = cb.is_lt(cb.curr.state.gas_left.expr(), gas_required.expr());
cb.require_equal(
"constant gas left is less than gas required ",
insufficient_gas.expr(),
Expand Down
Loading

0 comments on commit cd5edab

Please sign in to comment.