use std::cell::RefCell;
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::num::TryFromIntError;
use namada_account::AccountPublicKeysMap;
use namada_core::address::{self, Address, ESTABLISHED_ADDRESS_BYTES_LEN};
use namada_core::arith::checked;
use namada_core::borsh::{BorshDeserialize, BorshSerializeExt};
use namada_core::chain::BlockHeight;
use namada_core::collections::HashSet;
use namada_core::decode;
use namada_core::hash::Hash;
use namada_core::internal::{HostEnvResult, KeyVal};
use namada_core::storage::{Key, TxIndex, TX_INDEX_LENGTH};
use namada_events::{Event, EventTypeBuilder};
use namada_gas::{
self as gas, Gas, GasMetering, TxGasMeter, VpGasMeter,
MEMORY_ACCESS_GAS_PER_BYTE,
};
use namada_state::prefix_iter::{PrefixIteratorId, PrefixIterators};
use namada_state::write_log::{self, WriteLog};
use namada_state::{
DBIter, InMemory, OptionExt, ResultExt, State, StateRead, StorageHasher,
StorageRead, StorageWrite, TxHostEnvState, VpHostEnvState, DB,
};
pub use namada_state::{Error, Result};
use namada_token::storage_key::{
is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key,
is_any_token_parameter_key,
};
use namada_token::MaspTransaction;
use namada_tx::data::TxSentinel;
use namada_tx::{BatchedTx, BatchedTxRef, Tx, TxCommitments};
use namada_vp::vp_host_fns;
use thiserror::Error;
#[cfg(feature = "wasm-runtime")]
use super::wasm::{TxCache, VpCache};
use crate::memory::VmMemory;
use crate::{
HostRef, RoAccess, RoHostRef, RwAccess, RwHostRef, WasmCacheAccess,
};
#[allow(missing_docs)]
#[allow(clippy::result_large_err)]
#[derive(Error, Debug)]
pub enum TxRuntimeError {
#[error("Out of gas: {0}")]
OutOfGas(gas::Error),
#[error("Gas overflow")]
GasOverflow,
#[error("Trying to modify storage for an address that doesn't exit {0}")]
UnknownAddressStorageModification(Address),
#[error(
"Trying to use a validity predicate with an invalid WASM code hash {0}"
)]
InvalidVpCodeHash(String),
#[error("A validity predicate of an account cannot be deleted")]
CannotDeleteVp,
#[error("Encoding error: {0}")]
EncodingError(std::io::Error),
#[error("Address error: {0}")]
AddressError(address::DecodeError),
#[error("Numeric conversion error: {0}")]
NumConversionError(#[from] TryFromIntError),
#[error("Memory error: {0}")]
MemoryError(Box<dyn std::error::Error + Sync + Send + 'static>),
#[error("No value found in result buffer")]
NoValueInResultBuffer,
#[error("VP code is not allowed in allowlist parameter.")]
DisallowedVp,
}
impl From<TxRuntimeError> for namada_state::Error {
fn from(value: TxRuntimeError) -> Self {
Self::new(value)
}
}
pub type TxResult<T> = namada_state::Result<T>;
pub struct TxVmEnv<MEM, D, H, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
{
pub memory: MEM,
pub ctx: TxCtx<D, H, CA>,
}
#[derive(Debug)]
pub struct TxCtx<D, H, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
{
pub write_log: HostRef<RwAccess, WriteLog>,
pub in_mem: HostRef<RoAccess, InMemory<H>>,
pub db: HostRef<RoAccess, D>,
pub iterators: HostRef<RwAccess, PrefixIterators<'static, D>>,
pub gas_meter: HostRef<RoAccess, RefCell<TxGasMeter>>,
pub sentinel: HostRef<RoAccess, RefCell<TxSentinel>>,
pub tx: HostRef<RoAccess, Tx>,
pub cmt: HostRef<RoAccess, TxCommitments>,
pub tx_index: HostRef<RoAccess, TxIndex>,
pub verifiers: HostRef<RwAccess, BTreeSet<Address>>,
pub result_buffer: HostRef<RwAccess, Option<Vec<u8>>>,
pub yielded_value: HostRef<RwAccess, Option<Vec<u8>>>,
#[cfg(feature = "wasm-runtime")]
pub vp_wasm_cache: HostRef<RwAccess, VpCache<CA>>,
#[cfg(feature = "wasm-runtime")]
pub tx_wasm_cache: HostRef<RwAccess, TxCache<CA>>,
#[cfg(not(feature = "wasm-runtime"))]
pub cache_access: std::marker::PhantomData<CA>,
}
impl<MEM, D, H, CA> TxVmEnv<MEM, D, H, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
CA: WasmCacheAccess,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
memory: MEM,
write_log: &mut WriteLog,
in_mem: &InMemory<H>,
db: &D,
iterators: &mut PrefixIterators<'static, D>,
gas_meter: &RefCell<TxGasMeter>,
sentinel: &RefCell<TxSentinel>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
verifiers: &mut BTreeSet<Address>,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
#[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache<CA>,
) -> Self {
let write_log = unsafe { RwHostRef::new(write_log) };
let in_mem = unsafe { RoHostRef::new(in_mem) };
let db = unsafe { RoHostRef::new(db) };
let iterators = unsafe { RwHostRef::new(iterators) };
let gas_meter = unsafe { RoHostRef::new(gas_meter) };
let sentinel = unsafe { RoHostRef::new(sentinel) };
let tx = unsafe { RoHostRef::new(tx) };
let cmt = unsafe { RoHostRef::new(cmt) };
let tx_index = unsafe { RoHostRef::new(tx_index) };
let verifiers = unsafe { RwHostRef::new(verifiers) };
let result_buffer = unsafe { RwHostRef::new(result_buffer) };
let yielded_value = unsafe { RwHostRef::new(yielded_value) };
#[cfg(feature = "wasm-runtime")]
let vp_wasm_cache = unsafe { RwHostRef::new(vp_wasm_cache) };
#[cfg(feature = "wasm-runtime")]
let tx_wasm_cache = unsafe { RwHostRef::new(tx_wasm_cache) };
let ctx = TxCtx {
write_log,
db,
in_mem,
iterators,
gas_meter,
sentinel,
tx,
cmt,
tx_index,
verifiers,
result_buffer,
yielded_value,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
#[cfg(feature = "wasm-runtime")]
tx_wasm_cache,
#[cfg(not(feature = "wasm-runtime"))]
cache_access: std::marker::PhantomData,
};
Self { memory, ctx }
}
pub fn state(&self) -> TxHostEnvState<'_, D, H> {
self.ctx.state()
}
}
impl<MEM, D, H, CA> Clone for TxVmEnv<MEM, D, H, CA>
where
MEM: Clone,
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
CA: WasmCacheAccess,
{
fn clone(&self) -> Self {
Self {
memory: self.memory.clone(),
ctx: self.ctx.clone(),
}
}
}
impl<D, H, CA> TxCtx<D, H, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
CA: WasmCacheAccess,
{
pub fn state(&self) -> TxHostEnvState<'_, D, H> {
let write_log = unsafe { self.write_log.get_mut() };
let db = unsafe { self.db.get() };
let in_mem = unsafe { self.in_mem.get() };
let gas_meter = unsafe { self.gas_meter.get() };
let sentinel = unsafe { self.sentinel.get() };
TxHostEnvState {
write_log,
db,
in_mem,
gas_meter,
sentinel,
}
}
pub fn gas_meter_and_sentinel(
&self,
) -> (&RefCell<TxGasMeter>, &RefCell<TxSentinel>) {
let gas_meter = unsafe { self.gas_meter.get() };
let sentinel = unsafe { self.sentinel.get() };
(gas_meter, sentinel)
}
}
impl<D, H, CA> Clone for TxCtx<D, H, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
CA: WasmCacheAccess,
{
fn clone(&self) -> Self {
Self {
write_log: self.write_log,
db: self.db,
in_mem: self.in_mem,
iterators: self.iterators,
gas_meter: self.gas_meter,
sentinel: self.sentinel,
tx: self.tx,
cmt: self.cmt,
tx_index: self.tx_index,
verifiers: self.verifiers,
result_buffer: self.result_buffer,
yielded_value: self.yielded_value,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache: self.vp_wasm_cache,
#[cfg(feature = "wasm-runtime")]
tx_wasm_cache: self.tx_wasm_cache,
#[cfg(not(feature = "wasm-runtime"))]
cache_access: std::marker::PhantomData,
}
}
}
pub struct VpVmEnv<MEM, D, H, EVAL, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
{
pub memory: MEM,
pub ctx: VpCtx<D, H, EVAL, CA>,
}
pub struct VpCtx<D, H, EVAL, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
{
pub address: HostRef<RoAccess, Address>,
pub write_log: HostRef<RoAccess, WriteLog>,
pub in_mem: HostRef<RoAccess, InMemory<H>>,
pub db: HostRef<RoAccess, D>,
pub iterators: HostRef<RwAccess, PrefixIterators<'static, D>>,
pub gas_meter: HostRef<RoAccess, RefCell<VpGasMeter>>,
pub tx: HostRef<RoAccess, Tx>,
pub cmt: HostRef<RoAccess, TxCommitments>,
pub tx_index: HostRef<RoAccess, TxIndex>,
pub eval_runner: HostRef<RoAccess, EVAL>,
pub result_buffer: HostRef<RwAccess, Option<Vec<u8>>>,
pub yielded_value: HostRef<RwAccess, Option<Vec<u8>>>,
pub keys_changed: HostRef<RoAccess, BTreeSet<Key>>,
pub verifiers: HostRef<RoAccess, BTreeSet<Address>>,
#[cfg(feature = "wasm-runtime")]
pub vp_wasm_cache: HostRef<RwAccess, VpCache<CA>>,
#[cfg(not(feature = "wasm-runtime"))]
pub cache_access: std::marker::PhantomData<CA>,
}
pub trait VpEvaluator {
type Db: DB + for<'iter> DBIter<'iter>;
type H: StorageHasher;
type Eval: VpEvaluator;
type CA: WasmCacheAccess;
fn eval(
&self,
ctx: VpCtx<Self::Db, Self::H, Self::Eval, Self::CA>,
vp_code_hash: Hash,
input_data: BatchedTxRef<'_>,
) -> HostEnvResult;
}
impl<MEM, D, H, EVAL, CA> VpVmEnv<MEM, D, H, EVAL, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
memory: MEM,
address: &Address,
write_log: &WriteLog,
in_mem: &InMemory<H>,
db: &D,
gas_meter: &RefCell<VpGasMeter>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
iterators: &mut PrefixIterators<'static, D>,
verifiers: &BTreeSet<Address>,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
keys_changed: &BTreeSet<Key>,
eval_runner: &EVAL,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
) -> Self {
let ctx = VpCtx::new(
address,
write_log,
in_mem,
db,
gas_meter,
tx,
cmt,
tx_index,
iterators,
verifiers,
result_buffer,
yielded_value,
keys_changed,
eval_runner,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
);
Self { memory, ctx }
}
pub fn state(&self) -> VpHostEnvState<'_, D, H> {
self.ctx.state()
}
}
impl<MEM, D, H, EVAL, CA> Clone for VpVmEnv<MEM, D, H, EVAL, CA>
where
MEM: Clone,
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
fn clone(&self) -> Self {
Self {
memory: self.memory.clone(),
ctx: self.ctx.clone(),
}
}
}
impl<D, H, EVAL, CA> VpCtx<D, H, EVAL, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
address: &Address,
write_log: &WriteLog,
in_mem: &InMemory<H>,
db: &D,
gas_meter: &RefCell<VpGasMeter>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
iterators: &mut PrefixIterators<'static, D>,
verifiers: &BTreeSet<Address>,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
keys_changed: &BTreeSet<Key>,
eval_runner: &EVAL,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
) -> Self {
let address = unsafe { RoHostRef::new(address) };
let write_log = unsafe { RoHostRef::new(write_log) };
let db = unsafe { RoHostRef::new(db) };
let in_mem = unsafe { RoHostRef::new(in_mem) };
let tx = unsafe { RoHostRef::new(tx) };
let cmt = unsafe { RoHostRef::new(cmt) };
let tx_index = unsafe { RoHostRef::new(tx_index) };
let iterators = unsafe { RwHostRef::new(iterators) };
let gas_meter = unsafe { RoHostRef::new(gas_meter) };
let verifiers = unsafe { RoHostRef::new(verifiers) };
let result_buffer = unsafe { RwHostRef::new(result_buffer) };
let yielded_value = unsafe { RwHostRef::new(yielded_value) };
let keys_changed = unsafe { RoHostRef::new(keys_changed) };
let eval_runner = unsafe { RoHostRef::new(eval_runner) };
#[cfg(feature = "wasm-runtime")]
let vp_wasm_cache = unsafe { RwHostRef::new(vp_wasm_cache) };
Self {
address,
write_log,
db,
in_mem,
iterators,
gas_meter,
tx,
cmt,
tx_index,
eval_runner,
result_buffer,
yielded_value,
keys_changed,
verifiers,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
#[cfg(not(feature = "wasm-runtime"))]
cache_access: std::marker::PhantomData,
}
}
pub fn state(&self) -> VpHostEnvState<'_, D, H> {
let write_log = unsafe { self.write_log.get() };
let db = unsafe { self.db.get() };
let in_mem = unsafe { self.in_mem.get() };
let gas_meter = unsafe { self.gas_meter.get() };
VpHostEnvState {
write_log,
db,
in_mem,
gas_meter,
}
}
pub fn gas_meter(&self) -> &RefCell<VpGasMeter> {
let gas_meter = unsafe { self.gas_meter.get() };
gas_meter
}
}
impl<D, H, EVAL, CA> Clone for VpCtx<D, H, EVAL, CA>
where
D: DB + for<'iter> DBIter<'iter>,
H: StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
fn clone(&self) -> Self {
Self {
address: self.address,
write_log: self.write_log,
db: self.db,
in_mem: self.in_mem,
iterators: self.iterators,
gas_meter: self.gas_meter,
tx: self.tx,
cmt: self.cmt,
tx_index: self.tx_index,
eval_runner: self.eval_runner,
result_buffer: self.result_buffer,
yielded_value: self.yielded_value,
keys_changed: self.keys_changed,
verifiers: self.verifiers,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache: self.vp_wasm_cache,
#[cfg(not(feature = "wasm-runtime"))]
cache_access: std::marker::PhantomData,
}
}
}
pub fn tx_charge_gas<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
used_gas: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
consume_tx_gas(env, used_gas.into())
}
fn consume_tx_gas<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
used_gas: Gas,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (gas_meter, sentinel) = env.ctx.gas_meter_and_sentinel();
gas_meter
.borrow_mut()
.consume(used_gas)
.map_err(|err| {
sentinel.borrow_mut().set_out_of_gas();
tracing::info!(
"Stopping transaction execution because of gas error: {}",
err
);
TxRuntimeError::OutOfGas(err)
})
.into_storage_result()
}
pub fn vp_charge_gas<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
used_gas: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, used_gas.into())
}
pub fn tx_has_key<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_has_key {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let state = env.state();
let present = state.has_key(&key)?;
Ok(HostEnvResult::from(present).to_i64())
}
pub fn tx_read<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_read {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let state = env.state();
let value = state.read_bytes(&key)?;
match value {
Some(value) => {
let len: i64 = value
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
Ok(len)
}
None => Ok(HostEnvResult::Fail.to_i64()),
}
}
pub fn tx_read_temp<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_read {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let write_log = unsafe { env.ctx.write_log.get() };
let (log_val, gas) = write_log.read_temp(&key).into_storage_result()?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
match log_val {
Some(value) => {
let len: i64 = value
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value.clone());
Ok(len)
}
None => Ok(HostEnvResult::Fail.to_i64()),
}
}
pub fn tx_result_buffer<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
result_ptr: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
let value = result_buffer
.take()
.ok_or(TxRuntimeError::NoValueInResultBuffer)?;
let gas = env
.memory
.write_bytes(result_ptr, value)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
pub fn tx_iter_prefix<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
prefix_ptr: u64,
prefix_len: u64,
) -> TxResult<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (prefix, gas) = env
.memory
.read_string(prefix_ptr, prefix_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_iter_prefix {}", prefix);
let prefix = Key::parse(prefix)?;
let write_log = unsafe { env.ctx.write_log.get() };
let db = unsafe { env.ctx.db.get() };
let (iter, gas) = namada_state::iter_prefix_post(write_log, db, &prefix)?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let iterators = unsafe { env.ctx.iterators.get_mut() };
Ok(iterators
.insert(iter)
.ok_or_err_msg("Iterator ID overflow")?
.id())
}
pub fn tx_iter_next<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
iter_id: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
tracing::debug!("tx_iter_next iter_id {}", iter_id,);
let iterators = unsafe { env.ctx.iterators.get_mut() };
let iter_id = PrefixIteratorId::new(iter_id);
while let Some((key, val, iter_gas)) = iterators.next(iter_id) {
let (log_val, log_gas) = {
let state = env.state();
let (log_val, log_gas) = state
.write_log()
.read(&Key::parse(key.clone())?)
.into_storage_result()?;
(log_val.cloned(), log_gas)
};
consume_tx_gas::<MEM, D, H, CA>(env, checked!(iter_gas + log_gas)?)?;
match log_val {
Some(write_log::StorageModification::Write { value }) => {
let key_val = borsh::to_vec(&KeyVal { key, val: value })
.map_err(TxRuntimeError::EncodingError)?;
let len: i64 = key_val
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(key_val);
return Ok(len);
}
Some(write_log::StorageModification::Delete) => {
continue;
}
Some(write_log::StorageModification::InitAccount { .. }) => {
continue;
}
None => {
let key_val = borsh::to_vec(&KeyVal { key, val })
.map_err(TxRuntimeError::EncodingError)?;
let len: i64 = key_val
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(key_val);
return Ok(len);
}
}
}
Ok(HostEnvResult::Fail.to_i64())
}
pub fn tx_write<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
val_ptr: u64,
val_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let (value, gas) = env
.memory
.read_bytes(val_ptr, val_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_update {}, {:?}", key, value);
let key = Key::parse(key)?;
if key.is_validity_predicate().is_some() {
tx_validate_vp_code_hash::<MEM, D, H, CA>(env, &value, &None)?;
}
check_address_existence::<MEM, D, H, CA>(env, &key)?;
let mut state = env.state();
state.write_bytes(&key, value)
}
pub fn tx_write_temp<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
val_ptr: u64,
val_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let (value, gas) = env
.memory
.read_bytes(val_ptr, val_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_write_temp {}, {:?}", key, value);
let key = Key::parse(key)?;
check_address_existence::<MEM, D, H, CA>(env, &key)?;
let mut state = env.state();
let (gas, _size_diff) = state.write_log_mut().write_temp(&key, value)?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
fn check_address_existence<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key: &Key,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let token = if let Some([token, _]) = is_any_token_balance_key(key) {
Some(token)
} else if let Some(token) = is_any_token_parameter_key(key) {
Some(token)
} else {
is_any_minted_balance_key(key).or_else(|| is_any_minter_key(key))
};
let state = env.state();
for addr in key.find_addresses() {
if Some(&addr) == token {
continue;
}
if let Address::Implicit(_) | Address::Internal(_) = &addr {
continue;
}
let vp_key = Key::validity_predicate(&addr);
let is_present = state.has_key(&vp_key)?;
if !is_present {
tracing::info!(
"Trying to write into storage with a key containing an \
address that doesn't exist: {}",
addr
);
return Err(TxRuntimeError::UnknownAddressStorageModification(
addr,
)
.into());
}
}
Ok(())
}
pub fn tx_delete<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
key_ptr: u64,
key_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_delete {}", key);
let key = Key::parse(key)?;
if key.is_validity_predicate().is_some() {
return Err(TxRuntimeError::CannotDeleteVp.into());
}
let mut state = env.state();
state.delete(&key)
}
pub fn tx_emit_event<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
event_ptr: u64,
event_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (event, gas) = env
.memory
.read_bytes(event_ptr, event_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let event: Event = BorshDeserialize::try_from_slice(&event)
.map_err(TxRuntimeError::EncodingError)?;
let mut state = env.state();
let gas = state
.write_log_mut()
.emit_event(event)
.ok_or(TxRuntimeError::GasOverflow)?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
pub fn tx_get_events<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
event_type_ptr: u64,
event_type_len: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (event_type, gas) = env
.memory
.read_string(event_type_ptr, event_type_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let state = env.state();
let value = {
let event_type = EventTypeBuilder::new_with_type(event_type).build();
let events: Vec<_> = state
.write_log()
.lookup_events_with_prefix(&event_type)
.collect();
events.serialize_to_vec()
};
let len: i64 = value
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
Ok(len)
}
pub fn vp_read_pre<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
key_ptr: u64,
key_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
let key = Key::parse(key)?;
let state = env.state();
let value = vp_host_fns::read_pre(gas_meter, &state, &key)?;
tracing::debug!(
"vp_read_pre addr {}, key {}, value {:?}",
unsafe { env.ctx.address.get() },
key,
value,
);
Ok(match value {
Some(value) => {
let len: i64 = value.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
len
}
None => HostEnvResult::Fail.to_i64(),
})
}
pub fn vp_read_post<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
key_ptr: u64,
key_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_read_post {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let state = env.state();
let value = vp_host_fns::read_post(gas_meter, &state, &key)?;
Ok(match value {
Some(value) => {
let len: i64 = value.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
len
}
None => HostEnvResult::Fail.to_i64(),
})
}
pub fn vp_read_temp<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
key_ptr: u64,
key_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_read_temp {}, key {}", key, key_ptr);
let key = Key::parse(key)?;
let state = env.state();
let value = vp_host_fns::read_temp(gas_meter, &state, &key)?;
Ok(match value {
Some(value) => {
let len: i64 = value.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
len
}
None => HostEnvResult::Fail.to_i64(),
})
}
pub fn vp_result_buffer<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
result_ptr: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
let value = result_buffer
.take()
.ok_or(Error::new_const("No value found in result buffer"))?;
let gas = env
.memory
.write_bytes(result_ptr, value)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)
}
pub fn vp_has_key_pre<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
key_ptr: u64,
key_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_has_key_pre {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let state = env.state();
let present = vp_host_fns::has_key_pre(gas_meter, &state, &key)?;
Ok(HostEnvResult::from(present).to_i64())
}
pub fn vp_has_key_post<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
key_ptr: u64,
key_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (key, gas) = env
.memory
.read_string(key_ptr, key_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_has_key_post {}, key {}", key, key_ptr,);
let key = Key::parse(key)?;
let state = env.state();
let present = vp_host_fns::has_key_post(gas_meter, &state, &key)?;
Ok(HostEnvResult::from(present).to_i64())
}
pub fn vp_iter_prefix_pre<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
prefix_ptr: u64,
prefix_len: u64,
) -> Result<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (prefix, gas) = env
.memory
.read_string(prefix_ptr, prefix_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_iter_prefix_pre {}", prefix);
let prefix = Key::parse(prefix)?;
let write_log = unsafe { env.ctx.write_log.get() };
let db = unsafe { env.ctx.db.get() };
let iter = vp_host_fns::iter_prefix_pre(gas_meter, write_log, db, &prefix)?;
let iterators = unsafe { env.ctx.iterators.get_mut() };
Ok(iterators
.insert(iter)
.ok_or_err_msg("Iterator ID overflow")?
.id())
}
pub fn vp_iter_prefix_post<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
prefix_ptr: u64,
prefix_len: u64,
) -> Result<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (prefix, gas) = env
.memory
.read_string(prefix_ptr, prefix_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
tracing::debug!("vp_iter_prefix_post {}", prefix);
let prefix = Key::parse(prefix)?;
let write_log = unsafe { env.ctx.write_log.get() };
let db = unsafe { env.ctx.db.get() };
let iter =
vp_host_fns::iter_prefix_post(gas_meter, write_log, db, &prefix)?;
let iterators = unsafe { env.ctx.iterators.get_mut() };
Ok(iterators
.insert(iter)
.ok_or_err_msg("Iterator ID overflow")?
.id())
}
pub fn vp_iter_next<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
iter_id: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
tracing::debug!("vp_iter_next iter_id {}", iter_id);
let iterators = unsafe { env.ctx.iterators.get_mut() };
let iter_id = PrefixIteratorId::new(iter_id);
if let Some(iter) = iterators.get_mut(iter_id) {
let gas_meter = env.ctx.gas_meter();
if let Some((key, val)) = vp_host_fns::iter_next(gas_meter, iter)? {
let key_val = KeyVal { key, val }.serialize_to_vec();
let len: i64 = key_val.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(key_val);
return Ok(len);
}
}
Ok(HostEnvResult::Fail.to_i64())
}
pub fn tx_insert_verifier<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
addr_ptr: u64,
addr_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (addr, gas) = env
.memory
.read_string(addr_ptr, addr_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_insert_verifier {}, addr_ptr {}", addr, addr_ptr,);
let addr = Address::decode(&addr).map_err(TxRuntimeError::AddressError)?;
let verifiers = unsafe { env.ctx.verifiers.get_mut() };
consume_tx_gas::<MEM, D, H, CA>(
env,
checked!(addr_len * MEMORY_ACCESS_GAS_PER_BYTE)?.into(),
)?;
verifiers.insert(addr);
Ok(())
}
pub fn tx_update_validity_predicate<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
addr_ptr: u64,
addr_len: u64,
code_hash_ptr: u64,
code_hash_len: u64,
code_tag_ptr: u64,
code_tag_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (addr, gas) = env
.memory
.read_string(addr_ptr, addr_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let addr = Address::decode(addr).map_err(TxRuntimeError::AddressError)?;
tracing::debug!("tx_update_validity_predicate for addr {}", addr);
let (code_tag, gas) = env
.memory
.read_bytes(code_tag_ptr, code_tag_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let code_tag = Option::<String>::try_from_slice(&code_tag)
.map_err(TxRuntimeError::EncodingError)?;
let key = Key::validity_predicate(&addr);
let (code_hash, gas) = env
.memory
.read_bytes(code_hash_ptr, code_hash_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tx_validate_vp_code_hash::<MEM, D, H, CA>(env, &code_hash, &code_tag)?;
let mut state = env.state();
let (gas, _size_diff) = state.write_log_mut().write(&key, code_hash)?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
#[allow(clippy::too_many_arguments)]
pub fn tx_init_account<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
code_hash_ptr: u64,
code_hash_len: u64,
code_tag_ptr: u64,
code_tag_len: u64,
entropy_source_ptr: u64,
entropy_source_len: u64,
result_ptr: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (code_hash, gas) = env
.memory
.read_bytes(code_hash_ptr, code_hash_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let (code_tag, gas) = env
.memory
.read_bytes(code_tag_ptr, code_tag_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let code_tag = Option::<String>::try_from_slice(&code_tag)
.map_err(TxRuntimeError::EncodingError)?;
tx_validate_vp_code_hash::<MEM, D, H, CA>(env, &code_hash, &code_tag)?;
let (entropy_source, gas) = env
.memory
.read_bytes(entropy_source_ptr, entropy_source_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
tracing::debug!("tx_init_account");
let code_hash = Hash::try_from(&code_hash[..])
.map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?;
let mut state = env.state();
let (write_log, in_mem, _db) = state.split_borrow();
let gen = &in_mem.address_gen;
let (addr, gas) = write_log.init_account(gen, code_hash, &entropy_source);
let addr_bytes = addr.serialize_to_vec();
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let gas = env
.memory
.write_bytes(result_ptr, addr_bytes)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
pub fn tx_get_chain_id<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
result_ptr: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let state = env.state();
let (chain_id, gas) = state.in_mem().get_chain_id();
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let gas = env
.memory
.write_string(result_ptr, chain_id.to_string())
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
pub fn tx_get_block_height<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
) -> TxResult<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let state = env.state();
let (height, gas) = state.in_mem().get_block_height();
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
Ok(height.0)
}
pub fn tx_get_tx_index<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
) -> TxResult<u32>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
consume_tx_gas::<MEM, D, H, CA>(
env,
(TX_INDEX_LENGTH as u64)
.checked_mul(MEMORY_ACCESS_GAS_PER_BYTE)
.expect("Consts mul that cannot overflow")
.into(),
)?;
let tx_index = unsafe { env.ctx.tx_index.get() };
Ok(tx_index.0)
}
pub fn vp_get_tx_index<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
) -> Result<u32>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let tx_index = unsafe { env.ctx.tx_index.get() };
let tx_idx = vp_host_fns::get_tx_index(gas_meter, tx_index)?;
Ok(tx_idx.0)
}
pub fn tx_get_block_epoch<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
) -> TxResult<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let state = env.state();
let (epoch, gas) = state.in_mem().get_current_epoch();
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
Ok(epoch.0)
}
pub fn tx_get_pred_epochs<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let state = env.state();
let pred_epochs = state.in_mem().block.pred_epochs.clone();
let bytes = pred_epochs.serialize_to_vec();
let len: i64 = bytes
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let len_u64 = u64::try_from(len)?;
consume_tx_gas::<MEM, D, H, CA>(
env,
checked!(MEMORY_ACCESS_GAS_PER_BYTE * len_u64)?.into(),
)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(bytes);
Ok(len)
}
pub fn tx_get_native_token<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
result_ptr: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
consume_tx_gas::<MEM, D, H, CA>(
env,
(ESTABLISHED_ADDRESS_BYTES_LEN as u64)
.checked_mul(MEMORY_ACCESS_GAS_PER_BYTE)
.expect("Consts mul that cannot overflow")
.into(),
)?;
let state = env.state();
let native_token = state.in_mem().native_token.clone();
let native_token_string = native_token.encode();
let gas = env
.memory
.write_string(result_ptr, native_token_string)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)
}
pub fn tx_get_block_header<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
height: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let state = env.state();
let (header, gas) =
StateRead::get_block_header(&state, Some(BlockHeight(height)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
Ok(match header {
Some(h) => {
let value = h.serialize_to_vec();
let len: i64 = value
.len()
.try_into()
.map_err(TxRuntimeError::NumConversionError)?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
len
}
None => HostEnvResult::Fail.to_i64(),
})
}
pub fn vp_get_chain_id<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
result_ptr: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let chain_id = vp_host_fns::get_chain_id(gas_meter, &state)?;
let gas = env
.memory
.write_string(result_ptr, chain_id.to_string())
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)
}
pub fn vp_get_block_height<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
) -> Result<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let height = vp_host_fns::get_block_height(gas_meter, &state)?;
Ok(height.0)
}
pub fn vp_get_block_header<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
height: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let (header, gas) =
StateRead::get_block_header(&state, Some(BlockHeight(height)))?;
vp_host_fns::add_gas(gas_meter, gas)?;
Ok(match header {
Some(h) => {
let value = h.serialize_to_vec();
let len: i64 = value.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
len
}
None => HostEnvResult::Fail.to_i64(),
})
}
pub fn vp_get_tx_code_hash<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
result_ptr: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let tx = unsafe { env.ctx.tx.get() };
let cmt = unsafe { env.ctx.cmt.get() };
let batched_tx = tx.batch_ref_tx(cmt);
let hash = vp_host_fns::get_tx_code_hash(gas_meter, &batched_tx)?;
let mut result_bytes = vec![];
if let Some(hash) = hash {
result_bytes.push(1);
result_bytes.extend(hash.0);
} else {
result_bytes.push(0);
};
let gas = env
.memory
.write_bytes(result_ptr, result_bytes)
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)
}
pub fn vp_get_block_epoch<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
) -> Result<u64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let epoch = vp_host_fns::get_block_epoch(gas_meter, &state)?;
Ok(epoch.0)
}
pub fn vp_get_pred_epochs<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let pred_epochs = vp_host_fns::get_pred_epochs(gas_meter, &state)?;
let bytes = pred_epochs.serialize_to_vec();
let len: i64 = bytes.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(bytes);
Ok(len)
}
pub fn vp_get_events<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
event_type_ptr: u64,
event_type_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (event_type, gas) = env
.memory
.read_string(event_type_ptr, event_type_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
let state = env.state();
let events = vp_host_fns::get_events(gas_meter, &state, event_type)?;
let value = events.serialize_to_vec();
let len: i64 = value.len().try_into()?;
let result_buffer = unsafe { env.ctx.result_buffer.get_mut() };
result_buffer.replace(value);
Ok(len)
}
#[allow(clippy::too_many_arguments)]
pub fn vp_verify_tx_section_signature<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
hash_list_ptr: u64,
hash_list_len: u64,
public_keys_map_ptr: u64,
public_keys_map_len: u64,
signer_ptr: u64,
signer_len: u64,
threshold: u8,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (hash_list, gas) = env
.memory
.read_bytes(hash_list_ptr, hash_list_len.try_into()?)
.map_err(Into::into)?;
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
let hashes: [Hash; 1] = decode(hash_list)?;
let (public_keys_map, gas) = env
.memory
.read_bytes(public_keys_map_ptr, public_keys_map_len.try_into()?)
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)?;
let public_keys_map: AccountPublicKeysMap = decode(public_keys_map)?;
let (signer, gas) = env
.memory
.read_bytes(signer_ptr, signer_len.try_into()?)
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)?;
let signer: Address = decode(signer)?;
let tx = unsafe { env.ctx.tx.get() };
match tx.verify_signatures(
&HashSet::from_iter(hashes),
public_keys_map,
&Some(signer),
threshold,
|| {
gas_meter
.borrow_mut()
.consume(gas::VERIFY_TX_SIG_GAS.into())
},
) {
Ok(_) => Ok(()),
Err(err) => match err {
namada_tx::VerifySigError::Gas(inner) => {
Err(vp_host_fns::RuntimeError::OutOfGas(inner))
.into_storage_result()
}
namada_tx::VerifySigError::InvalidSectionSignature(inner) => {
Err(vp_host_fns::RuntimeError::InvalidSectionSignature(inner))
.into_storage_result()
}
err => Err(Error::new_alloc(err.to_string())),
},
}
}
pub fn tx_log_string<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
str_ptr: u64,
str_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (str, _gas) = env
.memory
.read_string(str_ptr, str_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
tracing::info!("WASM Transaction log: {}", str);
Ok(())
}
fn tx_validate_vp_code_hash<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
code_hash: &[u8],
code_tag: &Option<String>,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let code_hash = Hash::try_from(code_hash)
.map_err(|e| TxRuntimeError::InvalidVpCodeHash(e.to_string()))?;
if let Some(tag) = code_tag {
let hash_key = Key::wasm_hash(tag);
let (result, gas) = env.state().db_read(&hash_key)?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
if let Some(tag_hash) = result {
let tag_hash = Hash::try_from(&tag_hash[..]).map_err(|e| {
TxRuntimeError::InvalidVpCodeHash(e.to_string())
})?;
if tag_hash != code_hash {
return Err(TxRuntimeError::InvalidVpCodeHash(
"The VP code tag does not correspond to the given code \
hash"
.to_string(),
)
.into());
}
} else {
return Err(TxRuntimeError::InvalidVpCodeHash(
"The VP code tag doesn't exist".to_string(),
)
.into());
}
}
if !namada_parameters::is_vp_allowed(&env.ctx.state(), &code_hash)? {
return Err(TxRuntimeError::DisallowedVp.into());
}
let code_key = Key::wasm_code(&code_hash);
let is_present = env.state().has_key(&code_key)?;
if !is_present {
return Err(TxRuntimeError::InvalidVpCodeHash(
"The corresponding VP code doesn't exist".to_string(),
)
.into());
}
Ok(())
}
pub fn tx_set_commitment_sentinel<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
) where
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
MEM: VmMemory,
CA: WasmCacheAccess,
{
let sentinel = unsafe { env.ctx.sentinel.get() };
sentinel.borrow_mut().set_invalid_commitment();
}
#[allow(clippy::too_many_arguments)]
pub fn tx_verify_tx_section_signature<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
hash_list_ptr: u64,
hash_list_len: u64,
public_keys_map_ptr: u64,
public_keys_map_len: u64,
threshold: u8,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (hash_list, gas) = env
.memory
.read_bytes(hash_list_ptr, hash_list_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let hashes = <[Hash; 1]>::try_from_slice(&hash_list)
.map_err(TxRuntimeError::EncodingError)?;
let (public_keys_map, gas) = env
.memory
.read_bytes(public_keys_map_ptr, public_keys_map_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let public_keys_map =
AccountPublicKeysMap::try_from_slice(&public_keys_map)
.map_err(TxRuntimeError::EncodingError)?;
let tx = unsafe { env.ctx.tx.get() };
let (gas_meter, sentinel) = env.ctx.gas_meter_and_sentinel();
match tx.verify_signatures(
&HashSet::from_iter(hashes),
public_keys_map,
&None,
threshold,
|| {
gas_meter
.borrow_mut()
.consume(gas::VERIFY_TX_SIG_GAS.into())
},
) {
Ok(_) => Ok(HostEnvResult::Success.to_i64()),
Err(err) => match err {
namada_tx::VerifySigError::Gas(inner) => {
sentinel.borrow_mut().set_out_of_gas();
Err(TxRuntimeError::OutOfGas(inner))
}
namada_tx::VerifySigError::InvalidSectionSignature(_) => {
Ok(HostEnvResult::Fail.to_i64())
}
_ => Ok(HostEnvResult::Fail.to_i64()),
},
}
.into_storage_result()
}
pub fn tx_update_masp_note_commitment_tree<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
transaction_ptr: u64,
transaction_len: u64,
) -> TxResult<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (serialized_transaction, gas) = env
.memory
.read_bytes(transaction_ptr, transaction_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let transaction = MaspTransaction::try_from_slice(&serialized_transaction)
.map_err(TxRuntimeError::EncodingError)?;
match namada_token::utils::update_note_commitment_tree(
&mut env.state(),
&transaction,
) {
Ok(()) => Ok(HostEnvResult::Success.to_i64()),
Err(_) => {
Ok(HostEnvResult::Fail.to_i64())
}
}
}
pub fn tx_yield_value<MEM, D, H, CA>(
env: &mut TxVmEnv<MEM, D, H, CA>,
buf_ptr: u64,
buf_len: u64,
) -> TxResult<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
CA: WasmCacheAccess,
{
let (value_to_yield, gas) = env
.memory
.read_bytes(buf_ptr, buf_len.try_into()?)
.map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?;
consume_tx_gas::<MEM, D, H, CA>(env, gas)?;
let host_buf = unsafe { env.ctx.yielded_value.get_mut() };
host_buf.replace(value_to_yield);
Ok(())
}
pub fn vp_eval<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
vp_code_hash_ptr: u64,
vp_code_hash_len: u64,
input_data_ptr: u64,
input_data_len: u64,
) -> Result<i64>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator<Db = D, H = H, Eval = EVAL, CA = CA>,
CA: WasmCacheAccess,
{
let (vp_code_hash, gas) = env
.memory
.read_bytes(vp_code_hash_ptr, vp_code_hash_len.try_into()?)
.map_err(Into::into)?;
let tx = {
let gas_meter = env.ctx.gas_meter();
vp_host_fns::add_gas(gas_meter, gas)?;
let (input_data, gas) = env
.memory
.read_bytes(input_data_ptr, input_data_len.try_into()?)
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)?;
let tx: BatchedTx = decode(input_data)?;
tx
};
let eval_runner = unsafe { env.ctx.eval_runner.get() };
let vp_code_hash = Hash(vp_code_hash.try_into().map_err(|e| {
Error::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Not a valid hash: {:?}", e),
))
})?);
let batch_ref = BatchedTxRef {
tx: &tx.tx,
cmt: &tx.cmt,
};
Ok(eval_runner
.eval(env.ctx.clone(), vp_code_hash, batch_ref)
.to_i64())
}
pub fn vp_get_native_token<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
result_ptr: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let gas_meter = env.ctx.gas_meter();
let state = env.state();
let native_token = vp_host_fns::get_native_token(gas_meter, &state)?;
let native_token_string = native_token.encode();
let gas = env
.memory
.write_string(result_ptr, native_token_string)
.map_err(Into::into)?;
vp_host_fns::add_gas(gas_meter, gas)
}
pub fn vp_log_string<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
str_ptr: u64,
str_len: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (str, _gas) = env
.memory
.read_string(str_ptr, str_len.try_into()?)
.map_err(Into::into)?;
tracing::info!("WASM Validity predicate log: {}", str);
Ok(())
}
pub fn vp_yield_value<MEM, D, H, EVAL, CA>(
env: &mut VpVmEnv<MEM, D, H, EVAL, CA>,
buf_ptr: u64,
buf_len: u64,
) -> Result<()>
where
MEM: VmMemory,
D: 'static + DB + for<'iter> DBIter<'iter>,
H: 'static + StorageHasher,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
let (value_to_yield, gas) = env
.memory
.read_bytes(buf_ptr, buf_len.try_into()?)
.map_err(Into::into)?;
vp_host_fns::add_gas(env.ctx.gas_meter(), gas)?;
let host_buf = unsafe { env.ctx.yielded_value.get_mut() };
host_buf.replace(value_to_yield);
Ok(())
}
#[cfg(feature = "testing")]
pub mod testing {
use std::rc::Rc;
use super::*;
use crate::memory::testing::NativeMemory;
use crate::wasm::memory::WasmMemory;
#[allow(clippy::too_many_arguments)]
pub fn tx_env<S, CA>(
state: &mut S,
iterators: &mut PrefixIterators<'static, <S as StateRead>::D>,
verifiers: &mut BTreeSet<Address>,
gas_meter: &RefCell<TxGasMeter>,
sentinel: &RefCell<TxSentinel>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
#[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache<CA>,
) -> TxVmEnv<NativeMemory, <S as StateRead>::D, <S as StateRead>::H, CA>
where
S: State,
CA: WasmCacheAccess,
{
let (write_log, in_mem, db) = state.split_borrow();
TxVmEnv::new(
NativeMemory,
write_log,
in_mem,
db,
iterators,
gas_meter,
sentinel,
tx,
cmt,
tx_index,
verifiers,
result_buffer,
yielded_value,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
#[cfg(feature = "wasm-runtime")]
tx_wasm_cache,
)
}
#[allow(clippy::too_many_arguments)]
pub fn tx_env_with_wasm_memory<S, CA>(
state: &mut S,
iterators: &mut PrefixIterators<'static, <S as StateRead>::D>,
verifiers: &mut BTreeSet<Address>,
gas_meter: &RefCell<TxGasMeter>,
sentinel: &RefCell<TxSentinel>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
store: Rc<RefCell<wasmer::Store>>,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
#[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache<CA>,
) -> TxVmEnv<WasmMemory, <S as StateRead>::D, <S as StateRead>::H, CA>
where
S: State,
CA: WasmCacheAccess,
{
let wasm_memory = {
let mut borrowed_store = store.borrow_mut();
crate::wasm::memory::prepare_tx_memory(&mut *borrowed_store)
.unwrap()
};
let (write_log, in_mem, db) = state.split_borrow();
let mut env = TxVmEnv::new(
WasmMemory::new(Rc::downgrade(&store)),
write_log,
in_mem,
db,
iterators,
gas_meter,
sentinel,
tx,
cmt,
tx_index,
verifiers,
result_buffer,
yielded_value,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
#[cfg(feature = "wasm-runtime")]
tx_wasm_cache,
);
env.memory.init_from(&wasm_memory);
env
}
#[allow(clippy::too_many_arguments)]
pub fn vp_env<S, EVAL, CA>(
address: &Address,
state: &S,
iterators: &mut PrefixIterators<'static, <S as StateRead>::D>,
gas_meter: &RefCell<VpGasMeter>,
tx: &Tx,
cmt: &TxCommitments,
tx_index: &TxIndex,
verifiers: &BTreeSet<Address>,
result_buffer: &mut Option<Vec<u8>>,
yielded_value: &mut Option<Vec<u8>>,
keys_changed: &BTreeSet<Key>,
eval_runner: &EVAL,
#[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache<CA>,
) -> VpVmEnv<NativeMemory, <S as StateRead>::D, <S as StateRead>::H, EVAL, CA>
where
S: StateRead,
EVAL: VpEvaluator,
CA: WasmCacheAccess,
{
VpVmEnv::new(
NativeMemory,
address,
state.write_log(),
state.in_mem(),
state.db(),
gas_meter,
tx,
cmt,
tx_index,
iterators,
verifiers,
result_buffer,
yielded_value,
keys_changed,
eval_runner,
#[cfg(feature = "wasm-runtime")]
vp_wasm_cache,
)
}
}