use bigint::{Address, Gas, M256, U256};
#[cfg(not(feature = "std"))]
use core::cmp::max;
#[cfg(feature = "std")]
use std::cmp::max;
use super::State;
use crate::{AccountPatch, Instruction, Memory, Patch};
const G_ZERO: usize = 0;
const G_BASE: usize = 2;
const G_VERYLOW: usize = 3;
const G_LOW: usize = 5;
const G_MID: usize = 8;
const G_HIGH: usize = 10;
const G_JUMPDEST: usize = 1;
const G_SNOOP: usize = 200;
const G_SSET: usize = 20000;
const G_SRESET: usize = 5000;
const R_SRESET: isize = 15000;
const R_NETSRESETCLEAR: isize = 19800;
const R_NETSRESET: isize = 4800;
const R_NETSCLEAR: isize = 15000;
const R_SUICIDE: isize = 24000;
const G_CREATE: usize = 32000;
const G_CODEDEPOSIT: usize = 200;
const G_CALLVALUE: usize = 9000;
const G_CALLSTIPEND: usize = 2300;
const G_NEWACCOUNT: usize = 25000;
const G_EXP: usize = 10;
const G_MEMORY: usize = 3;
const G_LOG: usize = 375;
const G_LOGDATA: usize = 8;
const G_LOGTOPIC: usize = 375;
const G_SHA3: usize = 30;
const G_SHA3WORD: usize = 6;
const G_COPY: usize = 3;
const G_BLOCKHASH: usize = 20;
const G_EXTCODEHASH: usize = 400;
fn sstore_cost<M: Memory, P: Patch>(state: &State<M, P>) -> Gas {
let index: U256 = state.stack.peek(0).unwrap().into();
let value = state.stack.peek(1).unwrap();
let address = state.context.address;
let current = state.account_state.storage_read(address, index).unwrap();
if !state.patch.has_reduced_sstore_gas_metering() {
if current == M256::zero() && value != M256::zero() {
return G_SSET.into();
} else {
return G_SRESET.into();
}
}
trace!("using EIP1283 reduced SSTORE gas metering scheme");
if value == current {
return G_SNOOP.into();
}
let original = state
.account_state
.storage_read_orig(address, index)
.unwrap_or(M256::zero());
if original == current {
if original == M256::zero() {
G_SSET.into()
} else {
G_SRESET.into()
}
} else {
G_SNOOP.into()
}
}
fn call_cost<M: Memory, P: Patch>(machine: &State<M, P>, instruction: &Instruction) -> Gas {
let transfers_value = machine.stack.peek(2).unwrap() != M256::zero();
machine.patch.gas_call() + xfer_cost(instruction, transfers_value) + new_cost(machine, instruction, transfers_value)
}
fn xfer_cost(instruction: &Instruction, transfers_value: bool) -> Gas {
if (instruction == &Instruction::CALL || instruction == &Instruction::CALLCODE) && transfers_value {
G_CALLVALUE.into()
} else {
Gas::zero()
}
}
fn new_cost<M: Memory, P: Patch>(machine: &State<M, P>, instruction: &Instruction, transfers_value: bool) -> Gas {
let address: Address = machine.stack.peek(1).unwrap().into();
let eip161 = !machine.patch.account_patch().empty_considered_exists();
if instruction == &Instruction::CALL || instruction == &Instruction::STATICCALL {
if eip161 {
if transfers_value && !machine.account_state.exists(address).unwrap() {
Gas::from(G_NEWACCOUNT)
} else {
Gas::zero()
}
} else if !machine.account_state.exists(address).unwrap() {
Gas::from(G_NEWACCOUNT)
} else {
Gas::zero()
}
} else {
Gas::zero()
}
}
fn suicide_cost<M: Memory, P: Patch>(machine: &State<M, P>) -> Gas {
let target_address: Address = machine.stack.peek(0).unwrap().into();
let current_balance = machine.account_state.balance(machine.context.address).unwrap();
let is_target_existing = machine.account_state.exists(target_address).unwrap();
let eip161 = !machine.patch.account_patch().empty_considered_exists();
let should_charge_topup = if eip161 {
current_balance != U256::zero() && !is_target_existing
} else {
!is_target_existing
};
debug!(
"suicide in favor of {}, exists: {}, on_eip161: {}",
target_address, is_target_existing, eip161
);
debug!("suicide transfer value: {}", current_balance);
let suicide_gas_topup = if should_charge_topup {
trace!("suicide with new account gas topup");
machine.patch.gas_suicide_new_account()
} else {
trace!("suicide with zero gas topup");
Gas::zero()
};
machine.patch.gas_suicide() + suicide_gas_topup
}
fn memory_expand(current: Gas, from: Gas, len: Gas) -> Gas {
if len == Gas::zero() {
return current;
}
let rem = (from + len) % Gas::from(32u64);
let new = if rem == Gas::zero() {
(from + len) / Gas::from(32u64)
} else {
(from + len) / Gas::from(32u64) + Gas::from(1u64)
};
max(current, new)
}
pub fn code_deposit_gas(len: usize) -> Gas {
Gas::from(G_CODEDEPOSIT) * Gas::from(len)
}
pub fn memory_gas(a: Gas) -> Gas {
Gas::from(G_MEMORY) * a + a * a / Gas::from(512u64)
}
pub fn memory_cost<M: Memory, P: Patch>(instruction: Instruction, state: &State<M, P>) -> Gas {
let stack = &state.stack;
let current = state.memory_cost;
match instruction {
Instruction::SHA3 | Instruction::RETURN | Instruction::REVERT | Instruction::LOG(_) => {
let from: U256 = stack.peek(0).unwrap().into();
let len: U256 = stack.peek(1).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(len))
}
Instruction::CODECOPY | Instruction::CALLDATACOPY | Instruction::RETURNDATACOPY => {
let from: U256 = stack.peek(0).unwrap().into();
let len: U256 = stack.peek(2).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(len))
}
Instruction::EXTCODECOPY => {
let from: U256 = stack.peek(1).unwrap().into();
let len: U256 = stack.peek(3).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(len))
}
Instruction::MLOAD | Instruction::MSTORE => {
let from: U256 = stack.peek(0).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(32u64))
}
Instruction::MSTORE8 => {
let from: U256 = stack.peek(0).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(1u64))
}
Instruction::CREATE | Instruction::CREATE2 => {
let from: U256 = stack.peek(1).unwrap().into();
let len: U256 = stack.peek(2).unwrap().into();
memory_expand(current, Gas::from(from), Gas::from(len))
}
Instruction::CALL | Instruction::CALLCODE => {
let in_from: U256 = stack.peek(3).unwrap().into();
let in_len: U256 = stack.peek(4).unwrap().into();
let out_from: U256 = stack.peek(5).unwrap().into();
let out_len: U256 = stack.peek(6).unwrap().into();
memory_expand(
memory_expand(current, Gas::from(in_from), Gas::from(in_len)),
Gas::from(out_from),
Gas::from(out_len),
)
}
_ => current,
}
}
pub fn gas_cost<M: Memory, P: Patch>(instruction: Instruction, state: &State<M, P>) -> Gas {
match instruction {
Instruction::CALL => call_cost::<M, P>(state, &Instruction::CALL),
Instruction::CALLCODE => call_cost::<M, P>(state, &Instruction::CALLCODE),
Instruction::DELEGATECALL => call_cost::<M, P>(state, &Instruction::DELEGATECALL),
Instruction::STATICCALL => call_cost::<M, P>(state, &Instruction::STATICCALL),
Instruction::SUICIDE => suicide_cost::<M, P>(state),
Instruction::SSTORE => sstore_cost(state),
Instruction::SHA3 => {
let len = state.stack.peek(1).unwrap();
let wordd = Gas::from(len) / Gas::from(32u64);
let wordr = Gas::from(len) % Gas::from(32u64);
Gas::from(G_SHA3)
+ Gas::from(G_SHA3WORD)
* if wordr == Gas::zero() {
wordd
} else {
wordd + Gas::from(1u64)
}
}
Instruction::LOG(v) => {
let len = state.stack.peek(1).unwrap();
Gas::from(G_LOG) + Gas::from(G_LOGDATA) * Gas::from(len) + Gas::from(G_LOGTOPIC) * Gas::from(v)
}
Instruction::EXTCODECOPY => {
let len = state.stack.peek(3).unwrap();
let wordd = Gas::from(len) / Gas::from(32u64);
let wordr = Gas::from(len) % Gas::from(32u64);
state.patch.gas_extcode()
+ Gas::from(G_COPY)
* if wordr == Gas::zero() {
wordd
} else {
wordd + Gas::from(1u64)
}
}
Instruction::CALLDATACOPY | Instruction::CODECOPY | Instruction::RETURNDATACOPY => {
let len = state.stack.peek(2).unwrap();
let wordd = Gas::from(len) / Gas::from(32u64);
let wordr = Gas::from(len) % Gas::from(32u64);
Gas::from(G_VERYLOW)
+ Gas::from(G_COPY)
* if wordr == Gas::zero() {
wordd
} else {
wordd + Gas::from(1u64)
}
}
Instruction::EXP => {
if state.stack.peek(1).unwrap() == M256::zero() {
Gas::from(G_EXP)
} else {
Gas::from(G_EXP)
+ state.patch.gas_expbyte()
* (Gas::from(1u64) + Gas::from(state.stack.peek(1).unwrap().log2floor()) / Gas::from(8u64))
}
}
Instruction::CREATE => G_CREATE.into(),
Instruction::CREATE2 => {
let base = G_CREATE;
let init_code_len = state.stack.peek(2).unwrap().as_usize();
let sha_addup_base = init_code_len / 32 + if init_code_len % 32 == 0 { 0 } else { 1 };
let sha_addup = G_SHA3WORD * sha_addup_base;
(base + sha_addup).into()
}
Instruction::JUMPDEST => G_JUMPDEST.into(),
Instruction::SLOAD => state.patch.gas_sload(),
Instruction::STOP | Instruction::RETURN | Instruction::REVERT => G_ZERO.into(),
Instruction::ADDRESS
| Instruction::ORIGIN
| Instruction::CALLER
| Instruction::CALLVALUE
| Instruction::CALLDATASIZE
| Instruction::RETURNDATASIZE
| Instruction::CODESIZE
| Instruction::GASPRICE
| Instruction::COINBASE
| Instruction::TIMESTAMP
| Instruction::NUMBER
| Instruction::DIFFICULTY
| Instruction::GASLIMIT
| Instruction::POP
| Instruction::PC
| Instruction::MSIZE
| Instruction::GAS => G_BASE.into(),
Instruction::ADD
| Instruction::SUB
| Instruction::NOT
| Instruction::LT
| Instruction::GT
| Instruction::SLT
| Instruction::SGT
| Instruction::EQ
| Instruction::ISZERO
| Instruction::AND
| Instruction::OR
| Instruction::XOR
| Instruction::BYTE
| Instruction::CALLDATALOAD
| Instruction::MLOAD
| Instruction::MSTORE
| Instruction::MSTORE8
| Instruction::PUSH(_)
| Instruction::DUP(_)
| Instruction::SWAP(_)
| Instruction::SHL
| Instruction::SHR
| Instruction::SAR => G_VERYLOW.into(),
Instruction::MUL
| Instruction::DIV
| Instruction::SDIV
| Instruction::MOD
| Instruction::SMOD
| Instruction::SIGNEXTEND => G_LOW.into(),
Instruction::ADDMOD | Instruction::MULMOD | Instruction::JUMP => G_MID.into(),
Instruction::JUMPI => G_HIGH.into(),
Instruction::EXTCODESIZE => state.patch.gas_extcode(),
Instruction::BALANCE => state.patch.gas_balance(),
Instruction::BLOCKHASH => G_BLOCKHASH.into(),
Instruction::EXTCODEHASH => G_EXTCODEHASH.into(),
}
}
pub fn gas_stipend<M: Memory, P: Patch>(instruction: Instruction, state: &State<M, P>) -> Gas {
match instruction {
Instruction::CALL | Instruction::CALLCODE => {
let value = state.stack.peek(2).unwrap();
if value != M256::zero() {
G_CALLSTIPEND.into()
} else {
Gas::zero()
}
}
_ => Gas::zero(),
}
}
pub fn gas_refund<M: Memory, P: Patch>(instruction: Instruction, state: &State<M, P>) -> isize {
match instruction {
Instruction::SSTORE => {
let index: U256 = state.stack.peek(0).unwrap().into();
let value = state.stack.peek(1).unwrap();
let address = state.context.address;
let current = state.account_state.storage_read(address, index).unwrap();
if !state.patch.has_reduced_sstore_gas_metering() {
if current != M256::zero() && value == M256::zero() {
return R_SRESET;
} else {
return 0;
}
}
if current == value {
return 0;
}
let original = state
.account_state
.storage_read_orig(address, index)
.unwrap_or(M256::zero());
let mut refund = 0;
if original == current && value == M256::zero() {
return R_NETSCLEAR;
}
if original != M256::zero() {
if current == M256::zero() {
refund -= R_NETSCLEAR;
} else if value == M256::zero() {
refund += R_NETSCLEAR;
}
}
if original == value {
if original == M256::zero() {
refund += R_NETSRESETCLEAR
} else {
refund += R_NETSRESET
}
}
refund
}
Instruction::SUICIDE => {
if state.removed.contains(&state.context.address) {
0
} else {
R_SUICIDE
}
}
_ => 0,
}
}
pub trait AddRefund {
fn add_refund(self, refund: isize) -> Self;
}
impl AddRefund for Gas {
fn add_refund(self, refund: isize) -> Self {
let (refund, sign) = if refund < 0 {
(0 - refund, true)
} else {
(refund, false)
};
if sign {
self - Gas::from(refund as usize)
} else {
self + Gas::from(refund as usize)
}
}
}