ethereumvm 0.11.0

EthereumVM - a Portable Blockchain Virtual Machine
//! System operations instructions

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

#[cfg(not(feature = "std"))]
use alloc::rc::Rc;
#[cfg(feature = "std")]
use std::rc::Rc;

use bigint::{Address, Gas, H256, M256, U256};
use block_core::TransactionAction;

use super::{Control, State};
use crate::eval::util::{copy_from_memory, l64};
use crate::{Log, Memory, Patch, ValidTransaction};

#[cfg(not(feature = "std"))]
use core::cmp::min;
#[cfg(feature = "std")]
use std::cmp::min;

use sha3::{Digest, Keccak256};

pub fn suicide<M: Memory, P: Patch>(state: &mut State<M, P>) {
    pop!(state, address: Address);
    let balance = state.account_state.balance(state.context.address).unwrap();
    if !state.removed.contains(&state.context.address) {
        state.removed.push(state.context.address);
    }
    state.account_state.increase_balance(address, balance);

    let balance = state.account_state.balance(state.context.address).unwrap();
    state.account_state.decrease_balance(state.context.address, balance);
}

pub fn log<M: Memory, P: Patch>(state: &mut State<M, P>, topic_len: usize) {
    pop!(state, index: U256, len: U256);
    let data = copy_from_memory(&state.memory, index, len);
    let mut topics = Vec::new();
    for _ in 0..topic_len {
        topics.push(H256::from(state.stack.pop().unwrap()));
    }

    state.logs.push(Log {
        address: state.context.address,
        data,
        topics,
    });
}

pub fn sha3<M: Memory, P: Patch>(state: &mut State<M, P>) {
    pop!(state, from: U256, len: U256);
    let data = copy_from_memory(&state.memory, from, len);
    let ret = Keccak256::digest(data.as_slice());
    push!(state, M256::from(ret.as_slice()));
}

macro_rules! try_callstack_limit {
    ( $state:expr, $patch:expr ) => {
        if $state.depth > $patch.callstack_limit() {
            push!($state, M256::zero());
            return None;
        }
    };
}

macro_rules! try_balance {
    ( $state:expr, $value:expr, $gas:expr ) => {
        if $state.account_state.balance($state.context.address).unwrap() < $value {
            push!($state, M256::zero());
            return None;
        }
    };
}

pub fn create<M: Memory, P: Patch>(state: &mut State<M, P>, after_gas: Gas, is_create2: bool) -> Option<Control> {
    let l64_after_gas = if state.patch.call_create_l64_after_gas() {
        l64(after_gas)
    } else {
        after_gas
    };

    pop!(state, value: U256);
    pop!(state, init_start: U256, init_len: U256);

    try_callstack_limit!(state, state.patch);
    try_balance!(state, value, Gas::zero());

    let init = Rc::new(copy_from_memory(&state.memory, init_start, init_len));
    let transaction = if !is_create2 {
        ValidTransaction {
            caller: Some(state.context.address),
            gas_price: state.context.gas_price,
            gas_limit: l64_after_gas,
            value,
            input: init,
            action: TransactionAction::Create,
            nonce: state.account_state.nonce(state.context.address).unwrap(),
        }
    } else {
        pop!(state, salt: H256);
        let init_hash = Keccak256::digest(&init);
        let init_hash = M256::from(&init_hash[..]);
        ValidTransaction {
            caller: Some(state.context.address),
            gas_price: state.context.gas_price,
            gas_limit: l64_after_gas,
            value,
            input: init,
            action: TransactionAction::Create2(salt, init_hash),
            nonce: state.account_state.nonce(state.context.address).unwrap(),
        }
    };

    let context = transaction
        .into_context::<P>(
            Gas::zero(),
            Some(state.context.origin),
            &mut state.account_state,
            true,
            state.context.is_static,
        )
        .unwrap();

    push!(state, context.address.into());
    Some(Control::InvokeCreate(context))
}

pub fn call<M: Memory, P: Patch>(
    state: &mut State<M, P>,
    stipend_gas: Gas,
    after_gas: Gas,
    as_self: bool,
) -> Option<Control> {
    let l64_after_gas = if state.patch.call_create_l64_after_gas() {
        l64(after_gas)
    } else {
        after_gas
    };

    pop!(state, gas: Gas, to: Address, value: U256);
    pop!(state, in_start: U256, in_len: U256, out_start: U256, out_len: U256);
    let gas_limit = min(gas, l64_after_gas) + stipend_gas;

    try_callstack_limit!(state, state.patch);
    try_balance!(state, value, gas_limit);

    let input = Rc::new(copy_from_memory(&state.memory, in_start, in_len));
    let transaction = ValidTransaction {
        caller: Some(state.context.address),
        gas_price: state.context.gas_price,
        gas_limit,
        value,
        input,
        action: TransactionAction::Call(to),
        nonce: state.account_state.nonce(state.context.address).unwrap(),
    };

    let mut context = transaction
        .into_context::<P>(
            Gas::zero(),
            Some(state.context.origin),
            &mut state.account_state,
            true,
            state.context.is_static,
        )
        .unwrap();
    if as_self {
        context.address = state.context.address;
    }

    push!(state, M256::from(1u64));
    Some(Control::InvokeCall(context, (out_start, out_len)))
}

pub fn static_call<M: Memory, P: Patch>(state: &mut State<M, P>, stipend_gas: Gas, after_gas: Gas) -> Option<Control> {
    let l64_after_gas = if state.patch.call_create_l64_after_gas() {
        l64(after_gas)
    } else {
        after_gas
    };

    pop!(state, gas: Gas, to: Address);
    pop!(state, in_start: U256, in_len: U256, out_start: U256, out_len: U256);
    let gas_limit = min(gas, l64_after_gas) + stipend_gas;

    try_callstack_limit!(state, state.patch);

    let input = Rc::new(copy_from_memory(&state.memory, in_start, in_len));
    let transaction = ValidTransaction {
        caller: Some(state.context.address),
        gas_price: state.context.gas_price,
        gas_limit,
        value: U256::zero(),
        input,
        action: TransactionAction::Call(to),
        nonce: state.account_state.nonce(state.context.address).unwrap(),
    };

    let context = transaction
        .into_context::<P>(
            Gas::zero(),
            Some(state.context.origin),
            &mut state.account_state,
            true,
            true,
        )
        .unwrap();

    push!(state, M256::from(1u64));
    Some(Control::InvokeCall(context, (out_start, out_len)))
}

pub fn delegate_call<M: Memory, P: Patch>(state: &mut State<M, P>, after_gas: Gas) -> Option<Control> {
    let l64_after_gas = if state.patch.call_create_l64_after_gas() {
        l64(after_gas)
    } else {
        after_gas
    };

    pop!(state, gas: Gas, to: Address);
    pop!(state, in_start: U256, in_len: U256, out_start: U256, out_len: U256);
    let gas_limit = min(gas, l64_after_gas);

    try_callstack_limit!(state, state.patch);

    let input = Rc::new(copy_from_memory(&state.memory, in_start, in_len));
    let transaction = ValidTransaction {
        caller: Some(state.context.caller),
        gas_price: state.context.gas_price,
        gas_limit,
        value: state.context.value,
        input,
        action: TransactionAction::Call(to),
        nonce: state.account_state.nonce(state.context.address).unwrap(),
    };

    let mut context = transaction
        .into_context::<P>(
            Gas::zero(),
            Some(state.context.origin),
            &mut state.account_state,
            true,
            state.context.is_static,
        )
        .unwrap();
    context.value = U256::zero();
    context.address = state.context.address;

    push!(state, M256::from(1u64));
    Some(Control::InvokeCall(context, (out_start, out_len)))
}