#![allow(unused_variables)]
#![allow(dead_code)]
use core::cell::RefCell;
use core::cmp::Ordering;
use core::fmt::Debug;
use std::rc::Rc;
use soroban_env_common::{
num::{i256_from_pieces, i256_into_pieces, u256_from_pieces, u256_into_pieces},
xdr::{
int128_helpers, AccountId, Asset, ContractCodeEntry, ContractCostType, ContractDataEntry,
ContractEventType, ContractId, CreateContractArgs, ExtensionPoint, Hash, HashIdPreimage,
HostFunction, HostFunctionArgs, HostFunctionType, Int128Parts, Int256Parts,
LedgerEntryData, LedgerKey, LedgerKeyContractCode, PublicKey, ScAddress, ScBytes,
ScContractExecutable, ScHostContextErrorCode, ScHostFnErrorCode, ScHostObjErrorCode,
ScHostStorageErrorCode, ScHostValErrorCode, ScMap, ScMapEntry, ScStatusType, ScString,
ScSymbol, ScUnknownErrorCode, ScVal, ScVec, UInt128Parts, UInt256Parts,
UploadContractWasmArgs,
},
AddressObject, Bool, BytesObject, Convert, I128Object, I256Object, I64Object, MapObject,
ScValObjRef, ScValObject, Status, StringObject, SymbolObject, SymbolSmall, TryFromVal,
TryIntoVal, U128Object, U256Object, U32Val, U64Object, U64Val, VecObject, VmCaller,
VmCallerEnv, Void, I256, U256,
};
use crate::auth::{AuthorizationManager, AuthorizationManagerSnapshot, RecordedAuthPayload};
use crate::events::{
DebugError, DebugEvent, Events, InternalContractEvent, InternalEvent, InternalEventsBuffer,
};
use crate::storage::{Storage, StorageMap};
use crate::{
budget::{AsBudget, Budget},
storage::{TempStorage, TempStorageMap},
};
use crate::host_object::{HostMap, HostObject, HostObjectType, HostVec};
use crate::SymbolStr;
#[cfg(feature = "vm")]
use crate::Vm;
use crate::{EnvBase, Object, RawVal, Symbol};
pub(crate) mod comparison;
mod conversion;
mod data_helper;
pub(crate) mod declared_size;
mod diagnostics_helper;
mod err_helper;
mod error;
pub(crate) mod invoker_type;
mod mem_helper;
pub(crate) mod metered_clone;
pub(crate) mod metered_map;
pub(crate) mod metered_vector;
pub(crate) mod metered_xdr;
mod validity;
pub use error::HostError;
use self::metered_vector::MeteredVector;
use self::{invoker_type::InvokerType, metered_clone::MeteredClone};
use crate::Compare;
const RESERVED_CONTRACT_FN_PREFIX: &str = "__";
#[derive(Clone)]
struct RollbackPoint {
storage: StorageMap,
temp_storage: TempStorageMap,
events: usize,
auth: Option<AuthorizationManagerSnapshot>,
}
#[cfg(any(test, feature = "testutils"))]
pub trait ContractFunctionSet {
fn call(&self, func: &Symbol, host: &Host, args: &[RawVal]) -> Option<RawVal>;
}
#[cfg(any(test, feature = "testutils"))]
#[derive(Debug, Clone)]
pub struct TestContractFrame {
pub id: Hash,
pub func: Symbol,
pub args: Vec<RawVal>,
panic: Rc<RefCell<Option<Status>>>,
}
#[cfg(any(test, feature = "testutils"))]
impl TestContractFrame {
pub fn new(id: Hash, func: Symbol, args: Vec<RawVal>) -> Self {
Self {
id,
func,
args,
panic: Rc::new(RefCell::new(None)),
}
}
}
#[derive(Clone)]
pub(crate) enum Frame {
#[cfg(feature = "vm")]
ContractVM(Rc<Vm>, Symbol, Vec<RawVal>),
HostFunction(HostFunctionType),
Token(Hash, Symbol, Vec<RawVal>),
#[cfg(any(test, feature = "testutils"))]
TestContract(TestContractFrame),
}
pub(crate) enum ContractReentryMode {
Prohibited,
SelfAllowed,
Allowed,
}
#[cfg(feature = "vm")]
pub(crate) struct VmSlice {
vm: Rc<Vm>,
pos: u32,
len: u32,
}
#[derive(Debug, Clone, Default)]
pub struct LedgerInfo {
pub protocol_version: u32,
pub sequence_number: u32,
pub timestamp: u64,
pub network_id: [u8; 32],
pub base_reserve: u32,
}
#[derive(Clone, Default)]
pub enum DiagnosticLevel {
#[default]
None,
Debug,
}
#[derive(Clone, Default)]
pub(crate) struct HostImpl {
source_account: RefCell<Option<AccountId>>,
ledger: RefCell<Option<LedgerInfo>>,
objects: RefCell<Vec<HostObject>>,
storage: RefCell<Storage>,
temp_storage: RefCell<TempStorage>,
pub(crate) context: RefCell<Vec<Frame>>,
pub(crate) budget: Budget,
pub(crate) events: RefCell<InternalEventsBuffer>,
authorization_manager: RefCell<AuthorizationManager>,
diagnostic_level: RefCell<DiagnosticLevel>,
#[cfg(any(test, feature = "testutils"))]
contracts: RefCell<std::collections::HashMap<Hash, Rc<dyn ContractFunctionSet>>>,
#[cfg(any(test, feature = "testutils"))]
previous_authorization_manager: RefCell<Option<AuthorizationManager>>,
}
#[derive(Default, Clone)]
pub struct Host(pub(crate) Rc<HostImpl>);
impl Debug for HostImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "HostImpl(...)")
}
}
impl Debug for Host {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Host({:x})", Rc::<HostImpl>::as_ptr(&self.0) as usize)
}
}
impl Convert<&Object, ScValObject> for Host {
type Error = HostError;
fn convert(&self, ob: &Object) -> Result<ScValObject, Self::Error> {
self.from_host_obj(*ob)
}
}
impl Convert<Object, ScValObject> for Host {
type Error = HostError;
fn convert(&self, ob: Object) -> Result<ScValObject, Self::Error> {
self.from_host_obj(ob)
}
}
impl<'a> Convert<&ScValObjRef<'a>, Object> for Host {
type Error = HostError;
fn convert(&self, ob: &ScValObjRef<'a>) -> Result<Object, Self::Error> {
self.to_host_obj(ob)
}
}
impl<'a> Convert<ScValObjRef<'a>, Object> for Host {
type Error = HostError;
fn convert(&self, ob: ScValObjRef<'a>) -> Result<Object, Self::Error> {
self.to_host_obj(&ob)
}
}
impl Host {
pub fn with_storage_and_budget(storage: Storage, budget: Budget) -> Self {
Self(Rc::new(HostImpl {
source_account: RefCell::new(None),
ledger: RefCell::new(None),
objects: Default::default(),
storage: RefCell::new(storage),
temp_storage: Default::default(),
context: Default::default(),
budget: budget.clone(),
events: Default::default(),
authorization_manager: RefCell::new(
AuthorizationManager::new_enforcing_without_authorizations(budget),
),
diagnostic_level: Default::default(),
#[cfg(any(test, feature = "testutils"))]
contracts: Default::default(),
#[cfg(any(test, feature = "testutils"))]
previous_authorization_manager: RefCell::new(None),
}))
}
pub fn set_source_account(&self, source_account: AccountId) {
*self.0.source_account.borrow_mut() = Some(source_account);
}
#[cfg(any(test, feature = "testutils"))]
pub fn remove_source_account(&self) {
*self.0.source_account.borrow_mut() = None;
}
pub fn source_account(&self) -> Option<AccountId> {
self.0.source_account.borrow().clone()
}
pub fn switch_to_recording_auth(&self) {
*self.0.authorization_manager.borrow_mut() =
AuthorizationManager::new_recording(self.budget_cloned());
}
pub fn set_authorization_entries(
&self,
auth_entries: Vec<soroban_env_common::xdr::ContractAuth>,
) -> Result<(), HostError> {
let new_auth_manager = AuthorizationManager::new_enforcing(self, auth_entries)?;
*self.0.authorization_manager.borrow_mut() = new_auth_manager;
Ok(())
}
pub fn set_ledger_info(&self, info: LedgerInfo) {
*self.0.ledger.borrow_mut() = Some(info)
}
pub fn with_ledger_info<F, T>(&self, f: F) -> Result<T, HostError>
where
F: FnOnce(&LedgerInfo) -> Result<T, HostError>,
{
match self.0.ledger.borrow().as_ref() {
None => Err(self.err_general("missing ledger info")),
Some(li) => f(li),
}
}
pub fn with_mut_ledger_info<F>(&self, mut f: F) -> Result<(), HostError>
where
F: FnMut(&mut LedgerInfo),
{
match self.0.ledger.borrow_mut().as_mut() {
None => Err(self.err_general("missing ledger info")),
Some(li) => {
f(li);
Ok(())
}
}
}
pub fn with_budget<T, F>(&self, f: F) -> T
where
F: FnOnce(Budget) -> T,
{
f(self.0.budget.clone())
}
pub(crate) fn budget_ref(&self) -> &Budget {
&self.0.budget
}
pub fn budget_cloned(&self) -> Budget {
self.0.budget.clone()
}
pub fn charge_budget(&self, ty: ContractCostType, input: Option<u64>) -> Result<(), HostError> {
self.0.budget.clone().charge(ty, input)
}
pub fn set_diagnostic_level(&self, diagnostic_level: DiagnosticLevel) {
*self.0.diagnostic_level.borrow_mut() = diagnostic_level;
}
pub fn is_debug(&self) -> bool {
matches!(*self.0.diagnostic_level.borrow(), DiagnosticLevel::Debug)
}
pub(crate) fn get_events_mut<F, U>(&self, f: F) -> Result<U, HostError>
where
F: FnOnce(&mut InternalEventsBuffer) -> Result<U, HostError>,
{
f(&mut self.0.events.borrow_mut())
}
pub fn record_debug_event<T>(&self, src: T) -> Result<(), HostError>
where
DebugEvent: From<T>,
{
let event: DebugEvent = src.into();
self.get_events_mut(|events| {
Ok(events.record(InternalEvent::Debug(event), self.as_budget()))
})?
}
pub(crate) fn record_contract_event(
&self,
type_: ContractEventType,
topics: VecObject,
data: RawVal,
) -> Result<(), HostError> {
self.validate_contract_event_topics(topics)?;
let ce = InternalContractEvent {
type_,
contract_id: self.bytesobj_from_internal_contract_id()?,
topics,
data,
};
self.get_events_mut(|events| {
Ok(events.record(InternalEvent::Contract(ce), self.as_budget()))
})?
}
pub fn with_mut_storage<F, U>(&self, f: F) -> Result<U, HostError>
where
F: FnOnce(&mut Storage) -> Result<U, HostError>,
{
f(&mut self.0.storage.borrow_mut())
}
pub fn try_finish(self) -> Result<(Storage, Budget, Events), (Self, HostError)> {
let events = self
.0
.events
.borrow()
.externalize(&self)
.map_err(|e| (self.clone(), e))?;
Rc::try_unwrap(self.0)
.map(|host_impl| {
let storage = host_impl.storage.into_inner();
let budget = host_impl.budget;
(storage, budget, events)
})
.map_err(|e| (Host(e), ScUnknownErrorCode::General.into()))
}
fn push_frame(&self, frame: Frame) -> Result<RollbackPoint, HostError> {
let mut auth_snapshot = None;
if let Ok(mut auth_manager) = self.0.authorization_manager.try_borrow_mut() {
auth_manager.push_frame(self, &frame)?;
auth_snapshot = Some(auth_manager.snapshot());
}
self.0.context.borrow_mut().push(frame);
Ok(RollbackPoint {
storage: self.0.storage.borrow().map.clone(),
temp_storage: self.0.temp_storage.borrow().map.clone(),
events: self.0.events.borrow().vec.len(),
auth: auth_snapshot,
})
}
fn pop_frame(&self, orp: Option<RollbackPoint>) -> Result<(), HostError> {
self.0
.context
.borrow_mut()
.pop()
.expect("unmatched host frame push/pop");
if let Ok(mut auth_manager) = self.0.authorization_manager.try_borrow_mut() {
auth_manager.pop_frame();
}
if self.0.context.borrow().is_empty() {
self.0
.authorization_manager
.borrow_mut()
.maybe_emulate_authentication(self)?;
#[cfg(any(test, feature = "testutils"))]
{
*self.0.previous_authorization_manager.borrow_mut() =
Some(self.0.authorization_manager.borrow().clone());
self.0.authorization_manager.borrow_mut().reset();
}
}
if let Some(rp) = orp {
self.0.storage.borrow_mut().map = rp.storage;
self.0.events.borrow_mut().rollback(rp.events)?;
if let Some(auth_rp) = rp.auth {
self.0.authorization_manager.borrow_mut().rollback(auth_rp);
}
}
Ok(())
}
fn with_current_frame<F, U>(&self, f: F) -> Result<U, HostError>
where
F: FnOnce(&Frame) -> Result<U, HostError>,
{
f(self
.0
.context
.borrow()
.last()
.ok_or_else(|| self.err(DebugError::new(ScHostContextErrorCode::NoContractRunning)))?)
}
fn with_current_frame_opt<F, U>(&self, f: F) -> Result<U, HostError>
where
F: FnOnce(Option<&Frame>) -> Result<U, HostError>,
{
f(self.0.context.borrow().last())
}
pub(crate) fn with_frame<F>(&self, frame: Frame, f: F) -> Result<RawVal, HostError>
where
F: FnOnce() -> Result<RawVal, HostError>,
{
self.charge_budget(ContractCostType::GuardFrame, None)?;
let start_depth = self.0.context.borrow().len();
let rp = self.push_frame(frame)?;
let res = f();
let res = match res {
Ok(v) if v.is::<Status>() => {
let st: Status = v.try_into()?;
if st.is_ok() {
Ok(RawVal::VOID.into())
} else {
Err(self.err_status(st))
}
}
res => res,
};
if res.is_err() {
self.pop_frame(Some(rp))?;
} else {
self.pop_frame(None)?;
}
let end_depth = self.0.context.borrow().len();
assert_eq!(start_depth, end_depth);
res
}
#[cfg(any(test, feature = "testutils"))]
pub fn with_test_contract_frame<F>(
&self,
id: Hash,
func: Symbol,
f: F,
) -> Result<RawVal, HostError>
where
F: FnOnce() -> Result<RawVal, HostError>,
{
self.with_frame(
Frame::TestContract(TestContractFrame::new(id, func, vec![])),
f,
)
}
#[cfg(any(test, feature = "testutils"))]
pub fn call_account_contract_check_auth(
&self,
contract: BytesObject,
args: VecObject,
) -> Result<RawVal, HostError> {
use crate::native_contract::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME;
let contract_id = self.hash_from_bytesobj_input("contract", contract)?;
let args = self.call_args_from_obj(args)?;
let res = self.call_n_internal(
&contract_id,
ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(self)?,
args.as_slice(),
ContractReentryMode::Prohibited,
true,
);
if let Err(e) = &res {
let evt = DebugEvent::new()
.msg("check auth invocation for a custom account contract resulted in error {}")
.arg::<RawVal>(e.status.into());
self.record_debug_event(evt)?;
}
res
}
pub(crate) fn get_current_contract_id_opt_internal(&self) -> Result<Option<Hash>, HostError> {
self.with_current_frame(|frame| match frame {
#[cfg(feature = "vm")]
Frame::ContractVM(vm, _, _) => Ok(Some(vm.contract_id.metered_clone(&self.0.budget)?)),
Frame::HostFunction(_) => Ok(None),
Frame::Token(id, _, _) => Ok(Some(id.metered_clone(&self.0.budget)?)),
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(tc) => Ok(Some(tc.id.clone())),
})
}
pub(crate) fn get_current_contract_id_internal(&self) -> Result<Hash, HostError> {
if let Some(id) = self.get_current_contract_id_opt_internal()? {
Ok(id)
} else {
Err(self.err_general("Current context has no contract ID"))
}
}
pub(crate) unsafe fn unchecked_visit_val_obj<F, U>(
&self,
obj: impl Into<Object>,
f: F,
) -> Result<U, HostError>
where
F: FnOnce(Option<&HostObject>) -> Result<U, HostError>,
{
self.charge_budget(ContractCostType::VisitObject, None)?;
let r = self.0.objects.borrow();
let obj: Object = obj.into();
let handle: u32 = obj.get_handle();
f(r.get(handle as usize))
}
pub(crate) fn visit_obj<HOT: HostObjectType, F, U>(
&self,
obj: HOT::Wrapper,
f: F,
) -> Result<U, HostError>
where
F: FnOnce(&HOT) -> Result<U, HostError>,
{
unsafe {
self.unchecked_visit_val_obj(obj, |hopt| match hopt {
None => Err(self.err_status(ScHostObjErrorCode::UnknownReference)),
Some(hobj) => match HOT::try_extract(hobj) {
None => Err(self.err_status(ScHostObjErrorCode::UnexpectedType)),
Some(hot) => f(hot),
},
})
}
}
pub fn inject_val(&self, v: &ScVal) -> Result<RawVal, HostError> {
self.to_host_val(v).map(Into::into)
}
pub fn get_events(&self) -> Result<Events, HostError> {
self.0.events.borrow().externalize(self)
}
pub(crate) fn from_host_val(&self, val: RawVal) -> Result<ScVal, HostError> {
self.charge_budget(ContractCostType::ValXdrConv, None)?;
ScVal::try_from_val(self, &val)
.map_err(|_| self.err_status(ScHostValErrorCode::UnknownError))
}
pub(crate) fn to_host_val(&self, v: &ScVal) -> Result<RawVal, HostError> {
self.charge_budget(ContractCostType::ValXdrConv, None)?;
v.try_into_val(self)
.map_err(|_| self.err_status(ScHostValErrorCode::UnknownError))
}
pub(crate) fn from_host_obj(&self, ob: impl Into<Object>) -> Result<ScValObject, HostError> {
unsafe {
self.unchecked_visit_val_obj(ob.into(), |ob| {
self.charge_budget(ContractCostType::ValXdrConv, None)?;
let val = match ob {
None => {
return Err(self.err_status(ScHostObjErrorCode::UnknownReference));
}
Some(ho) => match ho {
HostObject::Vec(vv) => {
metered_clone::charge_heap_alloc::<ScVal>(
vv.len() as u64,
self.as_budget(),
)?;
let sv = vv.iter().map(|e| self.from_host_val(*e)).collect::<Result<
Vec<ScVal>,
HostError,
>>(
)?;
ScVal::Vec(Some(ScVec(self.map_err(sv.try_into())?)))
}
HostObject::Map(mm) => {
metered_clone::charge_heap_alloc::<ScMapEntry>(
mm.len() as u64,
self.as_budget(),
)?;
let mut mv = Vec::with_capacity(mm.len());
for (k, v) in mm.iter(self)? {
let key = self.from_host_val(*k)?;
let val = self.from_host_val(*v)?;
mv.push(ScMapEntry { key, val });
}
ScVal::Map(Some(ScMap(self.map_err(mv.try_into())?)))
}
HostObject::U64(u) => ScVal::U64(*u),
HostObject::I64(i) => ScVal::I64(*i),
HostObject::TimePoint(tp) => {
ScVal::Timepoint(tp.metered_clone(self.as_budget())?)
}
HostObject::Duration(d) => {
ScVal::Duration(d.metered_clone(self.as_budget())?)
}
HostObject::U128(u) => ScVal::U128(UInt128Parts {
hi: int128_helpers::u128_hi(*u),
lo: int128_helpers::u128_lo(*u),
}),
HostObject::I128(i) => ScVal::I128(Int128Parts {
hi: int128_helpers::i128_hi(*i),
lo: int128_helpers::i128_lo(*i),
}),
HostObject::U256(u) => {
let (hi_hi, hi_lo, lo_hi, lo_lo) = u256_into_pieces(*u);
ScVal::U256(UInt256Parts {
hi_hi,
hi_lo,
lo_hi,
lo_lo,
})
}
HostObject::I256(i) => {
let (hi_hi, hi_lo, lo_hi, lo_lo) = i256_into_pieces(*i);
ScVal::I256(Int256Parts {
hi_hi,
hi_lo,
lo_hi,
lo_lo,
})
}
HostObject::Bytes(b) => ScVal::Bytes(b.metered_clone(self.as_budget())?),
HostObject::String(s) => ScVal::String(s.metered_clone(self.as_budget())?),
HostObject::Symbol(s) => ScVal::Symbol(s.metered_clone(self.as_budget())?),
HostObject::ContractExecutable(cc) => {
ScVal::ContractExecutable(cc.metered_clone(self.as_budget())?)
}
HostObject::Address(addr) => {
ScVal::Address(addr.metered_clone(self.as_budget())?)
}
HostObject::NonceKey(nk) => {
ScVal::LedgerKeyNonce(nk.metered_clone(self.as_budget())?)
}
},
};
Ok(ScValObject::unchecked_from_val(val))
})
}
}
pub(crate) fn to_host_obj(&self, ob: &ScValObjRef<'_>) -> Result<Object, HostError> {
self.charge_budget(ContractCostType::ValXdrConv, None)?;
let val: &ScVal = (*ob).into();
match val {
ScVal::Vec(Some(v)) => {
metered_clone::charge_heap_alloc::<RawVal>(v.len() as u64, self.as_budget())?;
let mut vv = Vec::with_capacity(v.len());
for e in v.iter() {
vv.push(self.to_host_val(e)?)
}
Ok(self.add_host_object(HostVec::from_vec(vv)?)?.into())
}
ScVal::Map(Some(m)) => {
metered_clone::charge_heap_alloc::<(RawVal, RawVal)>(
m.len() as u64,
self.as_budget(),
)?;
let mut mm = Vec::with_capacity(m.len());
for pair in m.iter() {
let k = self.to_host_val(&pair.key)?;
let v = self.to_host_val(&pair.val)?;
mm.push((k, v))
}
Ok(self.add_host_object(HostMap::from_map(mm, self)?)?.into())
}
ScVal::Vec(None) | ScVal::Map(None) => {
Err(self.err_status(ScHostValErrorCode::MissingObject))
}
ScVal::U64(u) => Ok(self.add_host_object(*u)?.into()),
ScVal::I64(i) => Ok(self.add_host_object(*i)?.into()),
ScVal::Timepoint(t) => Ok(self
.add_host_object(t.metered_clone(self.as_budget())?)?
.into()),
ScVal::Duration(d) => Ok(self
.add_host_object(d.metered_clone(self.as_budget())?)?
.into()),
ScVal::U128(u) => Ok(self
.add_host_object(int128_helpers::u128_from_pieces(u.hi, u.lo))?
.into()),
ScVal::I128(i) => Ok(self
.add_host_object(int128_helpers::i128_from_pieces(i.hi, i.lo))?
.into()),
ScVal::U256(u) => Ok(self
.add_host_object(u256_from_pieces(u.hi_hi, u.hi_lo, u.lo_hi, u.lo_lo))?
.into()),
ScVal::I256(i) => Ok(self
.add_host_object(i256_from_pieces(i.hi_hi, i.hi_lo, i.lo_hi, i.lo_lo))?
.into()),
ScVal::Bytes(b) => Ok(self
.add_host_object(b.metered_clone(self.as_budget())?)?
.into()),
ScVal::String(s) => Ok(self
.add_host_object(s.metered_clone(self.as_budget())?)?
.into()),
ScVal::Symbol(s) => Ok(self
.add_host_object(s.metered_clone(self.as_budget())?)?
.into()),
ScVal::ContractExecutable(cc) => Ok(self
.add_host_object(cc.metered_clone(self.as_budget())?)?
.into()),
ScVal::LedgerKeyNonce(_) => {
Err(self.err_general("nonce keys aren't allowed to be used directly"))
}
ScVal::Address(addr) => Ok(self
.add_host_object(addr.metered_clone(self.as_budget())?)?
.into()),
ScVal::Bool(_)
| ScVal::Void
| ScVal::Status(_)
| ScVal::U32(_)
| ScVal::I32(_)
| ScVal::LedgerKeyContractExecutable => {
Err(self.err_status(ScHostObjErrorCode::UnexpectedType))
}
}
}
pub(crate) fn add_host_object<HOT: HostObjectType>(
&self,
hot: HOT,
) -> Result<HOT::Wrapper, HostError> {
let prev_len = self.0.objects.borrow().len();
if prev_len > u32::MAX as usize {
return Err(self.err_status(ScHostObjErrorCode::ObjectCountExceedsU32Max));
}
metered_clone::charge_heap_alloc::<HostObject>(1, self.as_budget())?;
self.0.objects.borrow_mut().push(HOT::inject(hot));
let handle = prev_len as u32;
Ok(HOT::new_from_handle(handle))
}
fn create_contract_with_id(
&self,
contract_id: BytesObject,
contract_source: ScContractExecutable,
) -> Result<(), HostError> {
let new_contract_id = self.hash_from_bytesobj_input("id_obj", contract_id)?;
let storage_key = self.contract_executable_ledger_key(&new_contract_id)?;
if self
.0
.storage
.borrow_mut()
.has(&storage_key, self.as_budget())?
{
return Err(self.err_general("Contract already exists"));
}
if let ScContractExecutable::WasmRef(wasm_hash) = &contract_source {
if !self.contract_code_exists(wasm_hash)? {
return Err(self.err_general("Wasm does not exist"));
}
}
self.store_contract_executable(contract_source, new_contract_id, &storage_key)?;
Ok(())
}
fn maybe_initialize_asset_token(
&self,
contract_id: BytesObject,
id_preimage: HashIdPreimage,
) -> Result<(), HostError> {
if let HashIdPreimage::ContractIdFromAsset(asset_preimage) = id_preimage {
let mut asset_bytes: Vec<u8> = Default::default();
self.metered_write_xdr(&asset_preimage.asset, &mut asset_bytes)?;
self.call_n(
contract_id,
Symbol::try_from_val(self, &"init_asset")?,
&[self
.add_host_object(self.scbytes_from_vec(asset_bytes)?)?
.into()],
ContractReentryMode::Prohibited,
)?;
Ok(())
} else {
Ok(())
}
}
fn create_contract_with_id_preimage(
&self,
contract_source: ScContractExecutable,
id_preimage: HashIdPreimage,
) -> Result<BytesObject, HostError> {
let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
let id_obj = self.add_host_object(self.scbytes_from_hash(&Hash(id_arr))?)?;
self.create_contract_with_id(id_obj, contract_source.metered_clone(self.budget_ref())?)?;
self.maybe_initialize_asset_token(id_obj, id_preimage)?;
Ok(id_obj)
}
pub(crate) fn get_contract_id_from_asset(&self, asset: Asset) -> Result<Hash, HostError> {
let id_preimage = self.id_preimage_from_asset(asset)?;
let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
Ok(Hash(id_arr))
}
fn call_contract_fn(
&self,
id: &Hash,
func: &Symbol,
args: &[RawVal],
) -> Result<RawVal, HostError> {
let storage_key = self.contract_executable_ledger_key(id)?;
match self.retrieve_contract_executable_from_storage(&storage_key)? {
#[cfg(feature = "vm")]
ScContractExecutable::WasmRef(wasm_hash) => {
let code_entry = self.retrieve_wasm_from_storage(&wasm_hash)?;
let vm = Vm::new(
self,
id.metered_clone(&self.0.budget)?,
code_entry.code.as_slice(),
)?;
vm.invoke_function_raw(self, func, args)
}
#[cfg(not(feature = "vm"))]
ScContractExecutable::WasmRef(_) => Err(self.err_general("could not dispatch")),
ScContractExecutable::Token => {
self.with_frame(Frame::Token(id.clone(), *func, args.to_vec()), || {
use crate::native_contract::{NativeContract, Token};
Token.call(func, self, args)
})
}
}
}
fn call_n(
&self,
id: BytesObject,
func: Symbol,
args: &[RawVal],
reentry_mode: ContractReentryMode,
) -> Result<RawVal, HostError> {
let id = self.hash_from_bytesobj_input("contract", id)?;
self.call_n_internal(&id, func, args, reentry_mode, false)
}
pub(crate) fn call_n_internal(
&self,
id: &Hash,
func: Symbol,
args: &[RawVal],
reentry_mode: ContractReentryMode,
internal_host_call: bool,
) -> Result<RawVal, HostError> {
if !internal_host_call
&& SymbolStr::try_from_val(self, &func)?
.to_string()
.as_str()
.starts_with(RESERVED_CONTRACT_FN_PREFIX)
{
return Err(self.err_status_msg(
ScHostContextErrorCode::UnknownError,
"can't invoke a reserved function directly",
));
}
if !matches!(reentry_mode, ContractReentryMode::Allowed) {
let mut is_last_non_host_frame = true;
for f in self.0.context.borrow().iter().rev() {
let exist_id = match f {
#[cfg(feature = "vm")]
Frame::ContractVM(vm, _, _) => &vm.contract_id,
Frame::Token(id, _, _) => id,
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(tc) => &tc.id,
Frame::HostFunction(_) => continue,
};
if id == exist_id {
if matches!(reentry_mode, ContractReentryMode::SelfAllowed)
&& is_last_non_host_frame
{
is_last_non_host_frame = false;
continue;
}
return Err(self.err_status_msg(
ScHostContextErrorCode::UnknownError,
"Contract re-entry is not allowed",
));
}
is_last_non_host_frame = false;
}
}
self.fn_call_diagnostics(id, &func, args)?;
#[cfg(any(test, feature = "testutils"))]
{
let cfs_option = self.0.contracts.borrow().get(&id).cloned();
if let Some(cfs) = cfs_option {
let frame = TestContractFrame::new(id.clone(), func, args.to_vec());
let panic = frame.panic.clone();
return self.with_frame(Frame::TestContract(frame), || {
use std::any::Any;
use std::panic::AssertUnwindSafe;
type PanicVal = Box<dyn Any + Send>;
let closure = AssertUnwindSafe(move || cfs.call(&func, self, args));
let res: Result<Option<RawVal>, PanicVal> = testutils::call_with_suppressed_panic_hook(closure);
match res {
Ok(Some(rawval)) => {
self.fn_return_diagnostics(id, &func, &rawval)?;
Ok(rawval)
},
Ok(None) => Err(self.err(
DebugError::general()
.msg("error '{}': calling unknown contract function '{}'")
.arg::<RawVal>(func.into()),
)),
Err(panic_payload) => {
let func: RawVal = func.into();
let mut status: Status = ScUnknownErrorCode::General.into();
let mut event = DebugEvent::new().msg("caught panic from contract function '{}'").arg(func);
if let Some(st) = *panic.borrow() {
status = st;
event = DebugEvent::new().msg("caught panic from contract function '{}', propagating escalated error '{}'").arg(func).arg(st.to_raw());
}
else if cfg!(feature = "hostfn_log_fmt_values") {
if let Some(str) = panic_payload.downcast_ref::<&str>() {
let msg: String = format!("caught panic '{}' from contract function '{:?}'", str, func);
event = DebugEvent::new().msg(msg);
} else if let Some(str) = panic_payload.downcast_ref::<String>() {
let msg: String = format!("caught panic '{}' from contract function '{:?}'", str, func);
event = DebugEvent::new().msg(msg);
}
}
Err(self.err(DebugError{event, status}))
}
}
});
}
}
let res = self.call_contract_fn(id, &func, args);
match &res {
Ok(res) => self.fn_return_diagnostics(id, &func, res)?,
Err(err) => {}
}
res
}
fn invoke_function_raw(&self, hf: HostFunctionArgs) -> Result<RawVal, HostError> {
let hf_type = hf.discriminant();
match hf {
HostFunctionArgs::InvokeContract(args) => {
if let [ScVal::Bytes(scbytes), ScVal::Symbol(scsym), rest @ ..] = args.as_slice() {
self.with_frame(Frame::HostFunction(hf_type), || {
let object = self.add_host_object(scbytes.clone())?;
let symbol: Symbol = scsym.as_slice().try_into_val(self)?;
let args = self.scvals_to_rawvals(rest)?;
self.call_n(object, symbol, &args[..], ContractReentryMode::Prohibited)
})
} else {
Err(self.err_status_msg(
ScHostFnErrorCode::InputArgsWrongLength,
"unexpected arguments to 'call' host function",
))
}
}
HostFunctionArgs::CreateContract(args) => self
.with_frame(Frame::HostFunction(hf_type), || {
self.create_contract(args).map(<RawVal>::from)
}),
HostFunctionArgs::UploadContractWasm(args) => self
.with_frame(Frame::HostFunction(hf_type), || {
self.install_contract(args).map(<RawVal>::from)
}),
}
}
pub fn invoke_functions(&self, host_fns: Vec<HostFunction>) -> Result<Vec<ScVal>, HostError> {
let is_recording_auth = self.0.authorization_manager.borrow().is_recording();
let mut res = vec![];
for hf in host_fns {
if !is_recording_auth {
self.set_authorization_entries(hf.auth.to_vec())?;
}
let rv = self.invoke_function_raw(hf.args)?;
res.push(self.from_host_val(rv)?);
}
Ok(res)
}
#[cfg(any(test, feature = "testutils"))]
pub fn register_test_contract(
&self,
contract_id: BytesObject,
contract_fns: Rc<dyn ContractFunctionSet>,
) -> Result<(), HostError> {
let hash = self.hash_from_bytesobj_input("contract_id", contract_id)?;
let mut contracts = self.0.contracts.borrow_mut();
contracts.insert(hash, contract_fns);
Ok(())
}
#[cfg(any(test, feature = "testutils"))]
pub fn add_ledger_entry(
&self,
key: &Rc<LedgerKey>,
val: &Rc<soroban_env_common::xdr::LedgerEntry>,
) -> Result<(), HostError> {
self.with_mut_storage(|storage| storage.put(key, val, self.as_budget()))
}
#[cfg(any(test, feature = "testutils"))]
pub fn get_authenticated_top_authorizations(
&self,
) -> Result<Vec<(ScAddress, Hash, ScSymbol, ScVec)>, HostError> {
Ok(self
.0
.previous_authorization_manager
.borrow_mut()
.as_mut()
.ok_or_else(|| {
self.err_general("previous invocation is missing - no auth data to get")
})?
.get_authenticated_top_authorizations())
}
#[cfg(any(test, feature = "testutils"))]
pub fn get_authenticated_authorizations(
&self,
) -> Result<Vec<(ScAddress, Hash, ScSymbol, ScVec)>, HostError> {
Ok(self
.0
.previous_authorization_manager
.borrow_mut()
.as_mut()
.ok_or_else(|| {
self.err_general("previous invocation is missing - no auth data to get")
})?
.get_authenticated_authorizations())
}
#[cfg(any(test, feature = "testutils"))]
pub fn reset_temp_storage(&self) {
*self.0.temp_storage.borrow_mut() = Default::default();
}
pub fn system_event(&self, topics: VecObject, data: RawVal) -> Result<(), HostError> {
self.record_contract_event(ContractEventType::System, topics, data)?;
Ok(())
}
fn create_contract(&self, args: CreateContractArgs) -> Result<BytesObject, HostError> {
let id_preimage = match args.contract_id {
ContractId::Asset(asset) => self.id_preimage_from_asset(asset)?,
ContractId::SourceAccount(salt) => self.id_preimage_from_source_account(salt)?,
ContractId::Ed25519PublicKey(key_with_signature) => {
let signature_payload_preimage = self.create_contract_args_hash_preimage(
args.executable.metered_clone(self.budget_ref())?,
key_with_signature.salt.metered_clone(self.budget_ref())?,
)?;
let signature_payload = self.metered_hash_xdr(&signature_payload_preimage)?;
self.verify_sig_ed25519_internal(
&signature_payload,
&self.ed25519_pub_key_from_bytes(&key_with_signature.key.0)?,
&self.signature_from_bytes(
"create_contract_sig",
&key_with_signature.signature.0,
)?,
)?;
self.id_preimage_from_ed25519(key_with_signature.key, key_with_signature.salt)?
}
};
self.create_contract_with_id_preimage(args.executable, id_preimage)
}
fn install_contract(&self, args: UploadContractWasmArgs) -> Result<BytesObject, HostError> {
let hash_bytes = self.metered_hash_xdr(&args)?;
let hash_obj = self.add_host_object(self.scbytes_from_hash(&Hash(hash_bytes))?)?;
let code_key = Rc::new(LedgerKey::ContractCode(LedgerKeyContractCode {
hash: Hash(hash_bytes.metered_clone(self.budget_ref())?),
}));
if !self
.0
.storage
.borrow_mut()
.has(&code_key, self.as_budget())?
{
self.with_mut_storage(|storage| {
let data = LedgerEntryData::ContractCode(ContractCodeEntry {
hash: Hash(hash_bytes),
code: args.code,
ext: ExtensionPoint::V0,
});
storage.put(
&code_key,
&Host::ledger_entry_from_data(data),
self.as_budget(),
)
})?;
}
Ok(hash_obj)
}
pub(crate) fn verify_sig_ed25519_internal(
&self,
payload: &[u8],
public_key: &ed25519_dalek::PublicKey,
sig: &ed25519_dalek::Signature,
) -> Result<(), HostError> {
use ed25519_dalek::Verifier;
self.charge_budget(
ContractCostType::VerifyEd25519Sig,
Some(payload.len() as u64),
)?;
public_key
.verify(payload, sig)
.map_err(|_| self.err_general("Failed ED25519 verification"))
}
pub(crate) fn get_invoking_contract_internal(&self) -> Result<Hash, HostError> {
let frames = self.0.context.borrow();
let hash = match frames.as_slice() {
[.., f2, _] => match f2 {
#[cfg(feature = "vm")]
Frame::ContractVM(vm, _, _) => Ok(vm.contract_id.metered_clone(&self.0.budget)?),
Frame::HostFunction(_) => Err(self.err_general("invoker is not a contract")),
Frame::Token(id, _, _) => Ok(id.clone()),
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(tc) => Ok(tc.id.clone()), },
_ => Err(self.err_general("no frames to derive the invoker from")),
}?;
Ok(hash)
}
pub fn get_recorded_auth_payloads(&self) -> Result<Vec<RecordedAuthPayload>, HostError> {
#[cfg(not(any(test, feature = "testutils")))]
{
self.0
.authorization_manager
.borrow()
.get_recorded_auth_payloads()
}
#[cfg(any(test, feature = "testutils"))]
{
self.0
.previous_authorization_manager
.borrow()
.as_ref()
.ok_or(self.err_general("previous invocation is missing - no payloads recorded"))?
.get_recorded_auth_payloads()
}
}
fn symbol_matches(&self, s: &[u8], sym: Symbol) -> Result<bool, HostError> {
if let Ok(ss) = SymbolSmall::try_from(sym) {
let sstr: SymbolStr = ss.into();
let slice: &[u8] = sstr.as_ref();
self.as_budget()
.compare(&slice, &s)
.map(|c| c == Ordering::Equal)
} else {
let sobj: SymbolObject = sym.try_into()?;
self.visit_obj(sobj, |scsym: &ScSymbol| {
self.as_budget()
.compare(&scsym.as_slice(), &s)
.map(|c| c == Ordering::Equal)
})
}
}
fn check_symbol_matches(&self, s: &[u8], sym: Symbol) -> Result<(), HostError> {
if self.symbol_matches(s, sym)? {
Ok(())
} else {
Err(self.err_general("symbol mismatch"))
}
}
fn get_invoker_type(&self) -> Result<u64, HostError> {
let frames = self.0.context.borrow();
let st = match frames.as_slice() {
[.., f2, _] => match f2 {
#[cfg(feature = "vm")]
Frame::ContractVM(_, _, _) => Ok(InvokerType::Contract),
Frame::HostFunction(_) => Ok(InvokerType::Account),
Frame::Token(id, _, _) => Ok(InvokerType::Contract),
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(_) => Ok(InvokerType::Contract),
},
[f1] => Ok(InvokerType::Account),
_ => Err(self.err_general("no frames to derive the invoker from")),
}?;
Ok(st as u64)
}
}
impl EnvBase for Host {
type Error = HostError;
#[cfg(feature = "testutils")]
fn escalate_error_to_panic(&self, e: Self::Error) -> ! {
let _ = self.with_current_frame_opt(|f| {
if let Some(Frame::TestContract(frame)) = f {
*frame.panic.borrow_mut() = Some(e.status);
}
Ok(())
});
let escalation = self.err_status_msg(e.status, "escalating error '{}' to panic");
panic!("{:?}", escalation)
}
fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
todo!()
}
fn check_same_env(&self, other: &Self) {
assert!(Rc::ptr_eq(&self.0, &other.0));
}
fn deep_clone(&self) -> Self {
Host(Rc::new((*self.0).clone()))
}
fn bytes_copy_from_slice(
&self,
b: BytesObject,
b_pos: U32Val,
slice: &[u8],
) -> Result<BytesObject, HostError> {
self.memobj_copy_from_slice::<ScBytes>(b, b_pos, slice)
}
fn bytes_copy_to_slice(
&self,
b: BytesObject,
b_pos: U32Val,
slice: &mut [u8],
) -> Result<(), HostError> {
self.memobj_copy_to_slice::<ScBytes>(b, b_pos, slice)
}
fn string_copy_to_slice(
&self,
b: StringObject,
b_pos: U32Val,
slice: &mut [u8],
) -> Result<(), HostError> {
self.memobj_copy_to_slice::<ScString>(b, b_pos, slice)
}
fn symbol_copy_to_slice(
&self,
s: SymbolObject,
b_pos: U32Val,
slice: &mut [u8],
) -> Result<(), HostError> {
let len = self.visit_obj(s, |sym: &ScSymbol| Ok(sym.len()))?;
self.memobj_copy_to_slice::<ScSymbol>(s, b_pos, &mut slice[..len])
}
fn bytes_new_from_slice(&self, mem: &[u8]) -> Result<BytesObject, HostError> {
self.add_host_object(self.scbytes_from_slice(mem)?)
}
fn string_new_from_slice(&self, s: &str) -> Result<StringObject, HostError> {
self.add_host_object(ScString(s.as_bytes().to_vec().try_into()?))
}
fn symbol_new_from_slice(&self, s: &str) -> Result<SymbolObject, HostError> {
for ch in s.chars() {
SymbolSmall::validate_char(ch)?;
}
self.add_host_object(ScSymbol(s.as_bytes().to_vec().try_into()?))
}
fn map_new_from_slices(&self, keys: &[&str], vals: &[RawVal]) -> Result<MapObject, HostError> {
metered_clone::charge_container_bulk_init_with_elts::<Vec<Symbol>, Symbol>(
keys.len() as u64,
self.as_budget(),
)?;
let mut key_syms: Vec<Symbol> = Vec::with_capacity(keys.len());
for k in keys.iter() {
key_syms.push(Symbol::try_from_val(self, k)?);
}
let pair_iter = key_syms
.iter()
.map(|s| s.to_raw())
.zip(vals.iter().cloned());
let map = HostMap::from_exact_iter(pair_iter, self)?;
self.add_host_object(map)
}
fn map_unpack_to_slice(
&self,
map: MapObject,
keys: &[&str],
vals: &mut [RawVal],
) -> Result<Void, HostError> {
metered_clone::charge_shallow_copy::<RawVal>(keys.len() as u64, self.as_budget())?;
if keys.len() != vals.len() {
return Err(self.err_status(ScHostFnErrorCode::InputArgsWrongLength));
}
self.visit_obj(map, |hm: &HostMap| {
if hm.len() != vals.len() {
return Err(self.err_status(ScHostFnErrorCode::InputArgsWrongLength));
}
for (ik, mk) in keys.iter().zip(hm.keys(self)?) {
let sym: Symbol = mk.try_into()?;
self.check_symbol_matches(ik.as_bytes(), sym)?;
}
for (iv, mv) in vals.iter_mut().zip(hm.values(self)?) {
*iv = *mv;
}
Ok(())
})?;
Ok(RawVal::VOID)
}
fn vec_new_from_slice(&self, vals: &[RawVal]) -> Result<VecObject, Self::Error> {
let map = HostVec::from_exact_iter(vals.iter().cloned(), self.budget_ref())?;
self.add_host_object(map)
}
fn vec_unpack_to_slice(
&self,
vec: VecObject,
vals: &mut [RawVal],
) -> Result<Void, Self::Error> {
self.visit_obj(vec, |hv: &HostVec| {
if hv.len() != vals.len() {
return Err(self.err_status(ScHostFnErrorCode::InputArgsWrongLength));
}
metered_clone::charge_shallow_copy::<RawVal>(hv.len() as u64, self.as_budget())?;
vals.copy_from_slice(hv.as_slice());
Ok(())
})?;
Ok(RawVal::VOID)
}
fn symbol_index_in_strs(&self, sym: Symbol, slices: &[&str]) -> Result<U32Val, Self::Error> {
let mut found = None;
self.metered_scan_slice_of_slices(slices, |i, slice| {
if self.symbol_matches(slice.as_bytes(), sym)? && found.is_none() {
found = Some(i)
}
Ok(())
})?;
match found {
None => Err(self.err_status(ScHostFnErrorCode::InputArgsInvalid)),
Some(idx) => Ok(U32Val::from(self.usize_to_u32(idx)?)),
}
}
fn log_static_fmt_val(&self, fmt: &'static str, v: RawVal) -> Result<(), HostError> {
self.record_debug_event(DebugEvent::new().msg(fmt).arg(v))
}
fn log_static_fmt_static_str(
&self,
fmt: &'static str,
s: &'static str,
) -> Result<(), HostError> {
self.record_debug_event(DebugEvent::new().msg(fmt).arg(s))
}
fn log_static_fmt_val_static_str(
&self,
fmt: &'static str,
v: RawVal,
s: &'static str,
) -> Result<(), HostError> {
self.record_debug_event(DebugEvent::new().msg(fmt).arg(v).arg(s))
}
fn log_static_fmt_general(
&self,
fmt: &'static str,
vals: &[RawVal],
strs: &[&'static str],
) -> Result<(), HostError> {
let mut evt = DebugEvent::new().msg(fmt);
for v in vals {
evt = evt.arg(*v)
}
for s in strs {
evt = evt.arg(*s)
}
self.record_debug_event(evt)
}
}
impl VmCallerEnv for Host {
type VmUserState = Host;
fn log_value(&self, _vmcaller: &mut VmCaller<Host>, v: RawVal) -> Result<Void, HostError> {
self.record_debug_event(DebugEvent::new().arg(v))?;
Ok(RawVal::VOID)
}
fn log_fmt_values(
&self,
_vmcaller: &mut VmCaller<Host>,
fmt: StringObject,
args: VecObject,
) -> Result<Void, HostError> {
if cfg!(feature = "hostfn_log_fmt_values") {
let fmt: String = self
.visit_obj(fmt, move |hv: &ScString| {
Ok(String::from_utf8(hv.clone().into()))
})?
.map_err(|_| {
self.err_general("log_fmt_values fmt string contains is invalid utf8")
})?;
let args: HostVec = self.visit_obj(args, move |hv: &HostVec| Ok(hv.clone()))?;
self.record_debug_event(DebugEvent::new().msg(fmt).args(args.iter().cloned()))?;
}
Ok(RawVal::VOID)
}
fn get_invoking_contract(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
let invoking_contract_hash = self.get_invoking_contract_internal()?;
Ok(self
.add_host_object(self.scbytes_from_hash(&invoking_contract_hash)?)?
.into())
}
fn obj_cmp(
&self,
_vmcaller: &mut VmCaller<Host>,
a: RawVal,
b: RawVal,
) -> Result<i64, HostError> {
let res = match unsafe {
match (Object::try_from(a), Object::try_from(b)) {
(Ok(a), Ok(b)) => self.unchecked_visit_val_obj(a, |ao| {
self.unchecked_visit_val_obj(b, |bo| Ok(Some(self.compare(&ao, &bo)?)))
})?,
(Ok(a), Err(_)) => self.unchecked_visit_val_obj(a, |ao| match ao {
None => Ok(Some(Ordering::Less)),
Some(aobj) => aobj.try_compare_to_small(self.as_budget(), b),
})?,
(Err(_), Ok(b)) => self.unchecked_visit_val_obj(b, |bo| match bo {
None => Ok(Some(Ordering::Greater)),
Some(bobj) => Ok(match bobj.try_compare_to_small(self.as_budget(), a)? {
Some(Ordering::Less) => Some(Ordering::Greater),
Some(Ordering::Greater) => Some(Ordering::Less),
other => other,
}),
})?,
(Err(_), Err(_)) => return Err(self.err_general("two non-object args to obj_cmp")),
}
} {
Some(res) => res,
None => {
let atype = a.get_tag().get_scval_type();
let btype = b.get_tag().get_scval_type();
if atype == btype {
return Err(
self.err_general("equal-tagged values rejected by small-value obj_cmp")
);
}
atype.cmp(&btype)
}
};
Ok(match res {
Ordering::Less => -1,
Ordering::Equal => 0,
Ordering::Greater => 1,
})
}
fn contract_event(
&self,
_vmcaller: &mut VmCaller<Host>,
topics: VecObject,
data: RawVal,
) -> Result<Void, HostError> {
self.record_contract_event(ContractEventType::Contract, topics, data)?;
Ok(RawVal::VOID)
}
fn get_current_contract_address(
&self,
_vmcaller: &mut VmCaller<Host>,
) -> Result<AddressObject, HostError> {
self.add_host_object(ScAddress::Contract(
self.get_current_contract_id_internal()?,
))
}
fn obj_from_u64(&self, _vmcaller: &mut VmCaller<Host>, u: u64) -> Result<U64Object, HostError> {
self.add_host_object(u)
}
fn obj_to_u64(&self, _vmcaller: &mut VmCaller<Host>, obj: U64Object) -> Result<u64, HostError> {
self.visit_obj(obj, |u: &u64| Ok(*u))
}
fn obj_from_i64(&self, _vmcaller: &mut VmCaller<Host>, i: i64) -> Result<I64Object, HostError> {
self.add_host_object(i)
}
fn obj_to_i64(&self, _vmcaller: &mut VmCaller<Host>, obj: I64Object) -> Result<i64, HostError> {
self.visit_obj(obj, |i: &i64| Ok(*i))
}
fn obj_from_u128_pieces(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
hi: u64,
lo: u64,
) -> Result<U128Object, Self::Error> {
self.add_host_object(int128_helpers::u128_from_pieces(hi, lo))
}
fn obj_to_u128_lo64(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U128Object,
) -> Result<u64, Self::Error> {
self.visit_obj(obj, move |u: &u128| Ok(int128_helpers::u128_lo(*u)))
}
fn obj_to_u128_hi64(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U128Object,
) -> Result<u64, Self::Error> {
self.visit_obj(obj, move |u: &u128| Ok(int128_helpers::u128_hi(*u)))
}
fn obj_from_i128_pieces(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
hi: i64,
lo: u64,
) -> Result<I128Object, Self::Error> {
self.add_host_object(int128_helpers::i128_from_pieces(hi, lo))
}
fn obj_to_i128_lo64(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I128Object,
) -> Result<u64, Self::Error> {
self.visit_obj(obj, move |i: &i128| Ok(int128_helpers::i128_lo(*i)))
}
fn obj_to_i128_hi64(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I128Object,
) -> Result<i64, Self::Error> {
self.visit_obj(obj, move |i: &i128| Ok(int128_helpers::i128_hi(*i)))
}
fn obj_from_u256_pieces(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
hi_hi: u64,
hi_lo: u64,
lo_hi: u64,
lo_lo: u64,
) -> Result<U256Object, Self::Error> {
self.add_host_object(u256_from_pieces(hi_hi, hi_lo, lo_hi, lo_lo))
}
fn obj_to_u256_hi_hi(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |u: &U256| {
let (hi_hi, _, _, _) = u256_into_pieces(*u);
Ok(hi_hi)
})
}
fn obj_to_u256_hi_lo(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |u: &U256| {
let (_, hi_lo, _, _) = u256_into_pieces(*u);
Ok(hi_lo)
})
}
fn obj_to_u256_lo_hi(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |u: &U256| {
let (_, _, lo_hi, _) = u256_into_pieces(*u);
Ok(lo_hi)
})
}
fn obj_to_u256_lo_lo(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: U256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |u: &U256| {
let (_, _, _, lo_lo) = u256_into_pieces(*u);
Ok(lo_lo)
})
}
fn obj_from_i256_pieces(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
hi_hi: i64,
hi_lo: u64,
lo_hi: u64,
lo_lo: u64,
) -> Result<I256Object, Self::Error> {
self.add_host_object(i256_from_pieces(hi_hi, hi_lo, lo_hi, lo_lo))
}
fn obj_to_i256_hi_hi(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I256Object,
) -> Result<i64, HostError> {
self.visit_obj(obj, move |i: &I256| {
let (hi_hi, _, _, _) = i256_into_pieces(*i);
Ok(hi_hi)
})
}
fn obj_to_i256_hi_lo(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |i: &I256| {
let (_, hi_lo, _, _) = i256_into_pieces(*i);
Ok(hi_lo)
})
}
fn obj_to_i256_lo_hi(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |i: &I256| {
let (_, _, lo_hi, _) = i256_into_pieces(*i);
Ok(lo_hi)
})
}
fn obj_to_i256_lo_lo(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
obj: I256Object,
) -> Result<u64, HostError> {
self.visit_obj(obj, move |i: &I256| {
let (_, _, _, lo_lo) = i256_into_pieces(*i);
Ok(lo_lo)
})
}
fn map_new(&self, _vmcaller: &mut VmCaller<Host>) -> Result<MapObject, HostError> {
self.add_host_object(HostMap::new()?)
}
fn map_put(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
v: RawVal,
) -> Result<MapObject, HostError> {
let mnew = self.visit_obj(m, |hm: &HostMap| hm.insert(k, v, self))?;
self.add_host_object(mnew)
}
fn map_get(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
) -> Result<RawVal, HostError> {
self.visit_obj(m, move |hm: &HostMap| {
hm.get(&k, self)?
.copied()
.ok_or_else(|| self.err_general("map key not found")) })
}
fn map_del(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
) -> Result<MapObject, HostError> {
match self.visit_obj(m, |hm: &HostMap| hm.remove(&k, self))? {
Some((mnew, _)) => Ok(self.add_host_object(mnew)?),
None => Err(self.err_general("map key not found")),
}
}
fn map_len(&self, _vmcaller: &mut VmCaller<Host>, m: MapObject) -> Result<U32Val, HostError> {
let len = self.visit_obj(m, |hm: &HostMap| Ok(hm.len()))?;
self.usize_to_u32val(len)
}
fn map_has(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
) -> Result<Bool, HostError> {
self.visit_obj(m, move |hm: &HostMap| Ok(hm.contains_key(&k, self)?.into()))
}
fn map_prev_key(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
) -> Result<RawVal, HostError> {
self.visit_obj(m, |hm: &HostMap| {
if let Some((pk, _)) = hm.get_prev(&k, self)? {
Ok(*pk)
} else {
Ok(Status::UNKNOWN_ERROR.to_raw())
}
})
}
fn map_next_key(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
k: RawVal,
) -> Result<RawVal, HostError> {
self.visit_obj(m, |hm: &HostMap| {
if let Some((pk, _)) = hm.get_next(&k, self)? {
Ok(*pk)
} else {
Ok(Status::UNKNOWN_ERROR.to_raw())
}
})
}
fn map_min_key(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
) -> Result<RawVal, HostError> {
self.visit_obj(m, |hm: &HostMap| {
match hm.get_min(self)? {
Some((pk, pv)) => Ok(*pk),
None => Ok(Status::UNKNOWN_ERROR.to_raw()), }
})
}
fn map_max_key(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
) -> Result<RawVal, HostError> {
self.visit_obj(m, |hm: &HostMap| {
match hm.get_max(self)? {
Some((pk, pv)) => Ok(*pk),
None => Ok(Status::UNKNOWN_ERROR.to_raw()), }
})
}
fn map_keys(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
) -> Result<VecObject, HostError> {
let vec = self.visit_obj(m, |hm: &HostMap| {
HostVec::from_exact_iter(hm.keys(self)?.cloned(), self.budget_ref())
})?;
self.add_host_object(vec)
}
fn map_values(
&self,
_vmcaller: &mut VmCaller<Host>,
m: MapObject,
) -> Result<VecObject, HostError> {
let vec = self.visit_obj(m, |hm: &HostMap| {
HostVec::from_exact_iter(hm.values(self)?.cloned(), self.budget_ref())
})?;
self.add_host_object(vec)
}
fn map_new_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
keys_pos: U32Val,
vals_pos: U32Val,
len: U32Val,
) -> Result<MapObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
let VmSlice {
vm,
pos: keys_pos,
len,
} = self.decode_vmslice(keys_pos, len)?;
metered_clone::charge_container_bulk_init_with_elts::<Vec<Symbol>, Symbol>(
len as u64,
self.as_budget(),
)?;
let mut key_syms: Vec<Symbol> = Vec::with_capacity(len as usize);
self.metered_vm_scan_slices_in_linear_memory(
vmcaller,
&vm,
keys_pos,
len as usize,
|n, slice| {
self.charge_budget(ContractCostType::VmMemRead, Some(slice.len() as u64))?;
let scsym = ScSymbol(slice.try_into()?);
let sym = Symbol::try_from(self.to_host_val(&ScVal::Symbol(scsym))?)?;
key_syms.push(sym);
Ok(())
},
)?;
let vals_pos: u32 = vals_pos.into();
metered_clone::charge_container_bulk_init_with_elts::<Vec<Symbol>, Symbol>(
len as u64,
self.as_budget(),
)?;
let mut vals: Vec<RawVal> = vec![RawVal::VOID.into(); len as usize];
self.metered_vm_read_vals_from_linear_memory::<8, RawVal>(
vmcaller,
&vm,
vals_pos,
vals.as_mut_slice(),
|buf| RawVal::from_payload(u64::from_le_bytes(*buf)),
)?;
let pair_iter = key_syms
.iter()
.map(|s| s.to_raw())
.zip(vals.iter().cloned());
let map = HostMap::from_exact_iter(pair_iter, self)?;
self.add_host_object(map)
}
}
fn map_unpack_to_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
map: MapObject,
keys_pos: U32Val,
vals_pos: U32Val,
len: U32Val,
) -> Result<Void, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
let VmSlice {
vm,
pos: keys_pos,
len,
} = self.decode_vmslice(keys_pos, len)?;
self.visit_obj(map, |mapobj: &HostMap| {
self.metered_vm_scan_slices_in_linear_memory(
vmcaller,
&vm,
keys_pos,
len as usize,
|n, slice| {
let sym = Symbol::try_from(
mapobj
.map
.get(n)
.ok_or(ScHostObjErrorCode::VecIndexOutOfBound)?
.0,
)?;
self.check_symbol_matches(slice, sym)?;
Ok(())
},
)?;
self.metered_vm_write_vals_to_linear_memory(
vmcaller,
&vm,
vals_pos.into(),
mapobj.map.as_slice(),
|pair| u64::to_le_bytes(pair.1.get_payload()),
)?;
Ok(())
})?;
Ok(RawVal::VOID)
}
}
fn vec_new(&self, _vmcaller: &mut VmCaller<Host>, c: RawVal) -> Result<VecObject, HostError> {
let capacity: usize = if c.is_void() {
0
} else {
self.usize_from_rawval_u32_input("c", c)?
};
self.add_host_object(HostVec::new()?)
}
fn vec_put(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
i: U32Val,
x: RawVal,
) -> Result<VecObject, HostError> {
let i: u32 = i.into();
let vnew = self.visit_obj(v, move |hv: &HostVec| {
self.validate_index_lt_bound(i, hv.len())?;
hv.set(i as usize, x, self.as_budget())
})?;
self.add_host_object(vnew)
}
fn vec_get(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
i: U32Val,
) -> Result<RawVal, HostError> {
let i: u32 = i.into();
self.visit_obj(v, move |hv: &HostVec| {
hv.get(i as usize, self.as_budget()).map(|r| *r)
})
}
fn vec_del(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
i: U32Val,
) -> Result<VecObject, HostError> {
let i: u32 = i.into();
let vnew = self.visit_obj(v, move |hv: &HostVec| {
self.validate_index_lt_bound(i, hv.len())?;
hv.remove(i as usize, self.as_budget())
})?;
self.add_host_object(vnew)
}
fn vec_len(&self, _vmcaller: &mut VmCaller<Host>, v: VecObject) -> Result<U32Val, HostError> {
let len = self.visit_obj(v, |hv: &HostVec| Ok(hv.len()))?;
self.usize_to_u32val(len)
}
fn vec_push_front(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
x: RawVal,
) -> Result<VecObject, HostError> {
let vnew = self.visit_obj(v, move |hv: &HostVec| hv.push_front(x, self.as_budget()))?;
self.add_host_object(vnew)
}
fn vec_pop_front(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
) -> Result<VecObject, HostError> {
let vnew = self.visit_obj(v, move |hv: &HostVec| hv.pop_front(self.as_budget()))?;
self.add_host_object(vnew)
}
fn vec_push_back(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
x: RawVal,
) -> Result<VecObject, HostError> {
let vnew = self.visit_obj(v, move |hv: &HostVec| hv.push_back(x, self.as_budget()))?;
self.add_host_object(vnew)
}
fn vec_pop_back(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
) -> Result<VecObject, HostError> {
let vnew = self.visit_obj(v, move |hv: &HostVec| hv.pop_back(self.as_budget()))?;
self.add_host_object(vnew)
}
fn vec_front(&self, _vmcaller: &mut VmCaller<Host>, v: VecObject) -> Result<RawVal, HostError> {
self.visit_obj(v, |hv: &HostVec| {
hv.front(self.as_budget()).map(|hval| *hval)
})
}
fn vec_back(&self, _vmcaller: &mut VmCaller<Host>, v: VecObject) -> Result<RawVal, HostError> {
self.visit_obj(v, |hv: &HostVec| {
hv.back(self.as_budget()).map(|hval| *hval)
})
}
fn vec_insert(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
i: U32Val,
x: RawVal,
) -> Result<VecObject, HostError> {
let i: u32 = i.into();
let vnew = self.visit_obj(v, move |hv: &HostVec| {
self.validate_index_le_bound(i, hv.len())?;
hv.insert(i as usize, x, self.as_budget())
})?;
self.add_host_object(vnew)
}
fn vec_append(
&self,
_vmcaller: &mut VmCaller<Host>,
v1: VecObject,
v2: VecObject,
) -> Result<VecObject, HostError> {
let vnew = self.visit_obj(v1, |hv1: &HostVec| {
self.visit_obj(v2, |hv2: &HostVec| {
if hv1.len() > u32::MAX as usize - hv2.len() {
Err(self.err_status_msg(ScHostFnErrorCode::InputArgsInvalid, "u32 overflow"))
} else {
hv1.append(hv2, self.as_budget())
}
})
})?;
self.add_host_object(vnew)
}
fn vec_slice(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
start: U32Val,
end: U32Val,
) -> Result<VecObject, HostError> {
let start: u32 = start.into();
let end: u32 = end.into();
let vnew = self.visit_obj(v, move |hv: &HostVec| {
let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
hv.slice(range, self.as_budget())
})?;
self.add_host_object(vnew)
}
fn vec_first_index_of(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
x: RawVal,
) -> Result<RawVal, Self::Error> {
self.visit_obj(v, |hv: &HostVec| {
Ok(
match hv.first_index_of(|other| self.compare(&x, other), self.as_budget())? {
Some(u) => self.usize_to_u32val(u)?.into(),
None => RawVal::VOID.into(),
},
)
})
}
fn vec_last_index_of(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
x: RawVal,
) -> Result<RawVal, Self::Error> {
self.visit_obj(v, |hv: &HostVec| {
Ok(
match hv.last_index_of(|other| self.compare(&x, other), self.as_budget())? {
Some(u) => self.usize_to_u32val(u)?.into(),
None => RawVal::VOID.into(),
},
)
})
}
fn vec_binary_search(
&self,
_vmcaller: &mut VmCaller<Host>,
v: VecObject,
x: RawVal,
) -> Result<u64, Self::Error> {
self.visit_obj(v, |hv: &HostVec| {
let res = hv.binary_search_by(|probe| self.compare(probe, &x), self.as_budget())?;
self.u64_from_binary_search_result(res)
})
}
fn vec_new_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
vals_pos: U32Val,
len: U32Val,
) -> Result<VecObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
let VmSlice { vm, pos, len } = self.decode_vmslice(vals_pos, len)?;
metered_clone::charge_container_bulk_init_with_elts::<Vec<Symbol>, Symbol>(
len as u64,
self.as_budget(),
)?;
let mut vals: Vec<RawVal> = vec![RawVal::VOID.to_raw(); len as usize];
self.metered_vm_read_vals_from_linear_memory::<8, RawVal>(
vmcaller,
&vm,
pos,
vals.as_mut_slice(),
|buf| RawVal::from_payload(u64::from_le_bytes(*buf)),
)?;
self.add_host_object(HostVec::from_vec(vals)?)
}
}
fn vec_unpack_to_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
vec: VecObject,
vals_pos: U32Val,
len: U32Val,
) -> Result<Void, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
let VmSlice { vm, pos, len } = self.decode_vmslice(vals_pos, len)?;
self.visit_obj(vec, |vecobj: &HostVec| {
self.metered_vm_write_vals_to_linear_memory(
vmcaller,
&vm,
vals_pos.into(),
vecobj.as_slice(),
|x| u64::to_le_bytes(x.get_payload()),
)
})?;
Ok(RawVal::VOID)
}
}
fn put_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
v: RawVal,
) -> Result<Void, HostError> {
let key = self.contract_data_key_from_rawval(k)?;
let data = LedgerEntryData::ContractData(ContractDataEntry {
contract_id: self.get_current_contract_id_internal()?,
key: self.from_host_val(k)?,
val: self.from_host_val(v)?,
});
self.0.storage.borrow_mut().put(
&key,
&Host::ledger_entry_from_data(data),
self.as_budget(),
)?;
Ok(RawVal::VOID)
}
fn has_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<Bool, HostError> {
let key = self.storage_key_from_rawval(k)?;
let res = self.0.storage.borrow_mut().has(&key, self.as_budget())?;
Ok(RawVal::from_bool(res))
}
fn get_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<RawVal, HostError> {
let key = self.storage_key_from_rawval(k)?;
let entry = self.0.storage.borrow_mut().get(&key, self.as_budget())?;
match &entry.data {
LedgerEntryData::ContractData(ContractDataEntry {
contract_id,
key,
val,
}) => Ok(self.to_host_val(val)?),
_ => Err(self.err_status_msg(
ScHostStorageErrorCode::ExpectContractData,
"expected contract data",
)),
}
}
fn del_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<Void, HostError> {
let key = self.contract_data_key_from_rawval(k)?;
self.0.storage.borrow_mut().del(&key, self.as_budget())?;
Ok(RawVal::VOID)
}
fn create_contract_from_contract(
&self,
_vmcaller: &mut VmCaller<Host>,
wasm_hash: BytesObject,
salt: BytesObject,
) -> Result<BytesObject, HostError> {
let contract_id = self.get_current_contract_id_internal()?;
let salt = self.uint256_from_bytesobj_input("salt", salt)?;
let code =
ScContractExecutable::WasmRef(self.hash_from_bytesobj_input("wasm_hash", wasm_hash)?);
let id_preimage = self.id_preimage_from_contract(contract_id, salt)?;
self.create_contract_with_id_preimage(code, id_preimage)
}
fn put_tmp_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
v: RawVal,
) -> Result<Void, HostError> {
self.0.temp_storage.borrow_mut().put(
self.get_current_contract_id_internal()?,
k,
v,
self,
)?;
Ok(RawVal::VOID)
}
fn has_tmp_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<Bool, HostError> {
let res = self.0.temp_storage.borrow_mut().has(
self.get_current_contract_id_internal()?,
k,
self,
)?;
Ok(RawVal::from_bool(res))
}
fn get_tmp_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<RawVal, HostError> {
self.0
.temp_storage
.borrow_mut()
.get(self.get_current_contract_id_internal()?, k, self)
}
fn del_tmp_contract_data(
&self,
_vmcaller: &mut VmCaller<Host>,
k: RawVal,
) -> Result<Void, HostError> {
self.0
.temp_storage
.borrow_mut()
.del(self.get_current_contract_id_internal()?, k, self)?;
Ok(RawVal::VOID)
}
fn update_current_contract_wasm(
&self,
_vmcaller: &mut VmCaller<Host>,
hash: BytesObject,
) -> Result<Void, HostError> {
let wasm_hash = self.hash_from_bytesobj_input("wasm_hash", hash)?;
if !self.contract_code_exists(&wasm_hash)? {
return Err(self.err_general("Wasm does not exist"));
}
let curr_contract_id = self.get_current_contract_id_internal()?;
let key = self.contract_executable_ledger_key(&curr_contract_id)?;
let old_executable = self.retrieve_contract_executable_from_storage(&key)?;
let new_executable = ScContractExecutable::WasmRef(wasm_hash);
self.emit_update_contract_event(&old_executable, &new_executable)?;
self.store_contract_executable(new_executable, curr_contract_id, &key)?;
Ok(RawVal::VOID)
}
fn call(
&self,
_vmcaller: &mut VmCaller<Host>,
contract: BytesObject,
func: Symbol,
args: VecObject,
) -> Result<RawVal, HostError> {
let args = self.call_args_from_obj(args)?;
let res = self.call_n(
contract,
func,
args.as_slice(),
ContractReentryMode::Prohibited,
);
if let Err(e) = &res {
let evt = DebugEvent::new()
.msg("contract call invocation resulted in error {}")
.arg::<RawVal>(e.status.into());
self.record_debug_event(evt)?;
}
res
}
fn try_call(
&self,
vmcaller: &mut VmCaller<Host>,
contract: BytesObject,
func: Symbol,
args: VecObject,
) -> Result<RawVal, HostError> {
let args = self.call_args_from_obj(args)?;
let res = self.call_n(
contract,
func,
args.as_slice(),
ContractReentryMode::Prohibited,
);
match res {
Ok(rv) => Ok(rv),
Err(e) => {
let status: RawVal = e.status.into();
let evt = DebugEvent::new()
.msg("contract call invocation resulted in error {}")
.arg(status);
self.record_debug_event(evt)?;
Ok(status)
}
}
}
fn serialize_to_bytes(
&self,
_vmcaller: &mut VmCaller<Host>,
v: RawVal,
) -> Result<BytesObject, HostError> {
let scv = self.from_host_val(v)?;
let mut buf = Vec::<u8>::new();
self.metered_write_xdr(&scv, &mut buf)?;
self.add_host_object(self.scbytes_from_vec(buf)?)
}
fn deserialize_from_bytes(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
) -> Result<RawVal, HostError> {
let scv = self.visit_obj(b, |hv: &ScBytes| {
self.metered_from_xdr::<ScVal>(hv.as_slice())
})?;
self.to_host_val(&scv)
}
fn string_copy_to_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
s: StringObject,
s_pos: U32Val,
lm_pos: U32Val,
len: U32Val,
) -> Result<Void, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
self.memobj_copy_to_linear_memory::<ScString>(vmcaller, s, s_pos, lm_pos, len)?;
Ok(RawVal::VOID)
}
}
fn symbol_copy_to_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
s: SymbolObject,
s_pos: U32Val,
lm_pos: U32Val,
len: U32Val,
) -> Result<Void, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
self.memobj_copy_to_linear_memory::<ScSymbol>(vmcaller, s, s_pos, lm_pos, len)?;
Ok(RawVal::VOID)
}
}
fn bytes_copy_to_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
b: BytesObject,
b_pos: U32Val,
lm_pos: U32Val,
len: U32Val,
) -> Result<Void, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
self.memobj_copy_to_linear_memory::<ScBytes>(vmcaller, b, b_pos, lm_pos, len)?;
Ok(RawVal::VOID)
}
}
fn bytes_copy_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
b: BytesObject,
b_pos: U32Val,
lm_pos: U32Val,
len: U32Val,
) -> Result<BytesObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
self.memobj_copy_from_linear_memory::<ScBytes>(vmcaller, b, b_pos, lm_pos, len)
}
}
fn bytes_new_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
lm_pos: U32Val,
len: U32Val,
) -> Result<BytesObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
self.memobj_new_from_linear_memory::<ScBytes>(vmcaller, lm_pos, len)
}
fn string_new_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
lm_pos: U32Val,
len: U32Val,
) -> Result<StringObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
self.memobj_new_from_linear_memory::<ScString>(vmcaller, lm_pos, len)
}
fn symbol_new_from_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
lm_pos: U32Val,
len: U32Val,
) -> Result<SymbolObject, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
self.memobj_new_from_linear_memory::<ScSymbol>(vmcaller, lm_pos, len)
}
fn symbol_index_in_linear_memory(
&self,
vmcaller: &mut VmCaller<Host>,
sym: Symbol,
lm_pos: U32Val,
len: U32Val,
) -> Result<U32Val, HostError> {
#[cfg(not(feature = "vm"))]
unimplemented!();
#[cfg(feature = "vm")]
{
let VmSlice { vm, pos, len } = self.decode_vmslice(lm_pos, len)?;
let mut found = None;
self.metered_vm_scan_slices_in_linear_memory(
vmcaller,
&vm,
pos,
len as usize,
|i, slice| {
if self.symbol_matches(slice, sym)? {
if found.is_none() {
found = Some(self.usize_to_u32(i)?)
}
}
Ok(())
},
)?;
match found {
None => Err(self.err_status(ScHostFnErrorCode::InputArgsInvalid)),
Some(idx) => Ok(U32Val::from(idx)),
}
}
}
fn bytes_new(&self, _vmcaller: &mut VmCaller<Host>) -> Result<BytesObject, HostError> {
self.add_host_object(self.scbytes_from_vec(Vec::<u8>::new())?)
}
fn bytes_put(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
i: U32Val,
u: U32Val,
) -> Result<BytesObject, HostError> {
let i: u32 = i.into();
let u = self.u8_from_u32val_input("u", u)?;
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
let mut vnew: Vec<u8> = hv.metered_clone(&self.0.budget)?.into();
match vnew.get_mut(i as usize) {
None => Err(self.err_status(ScHostObjErrorCode::VecIndexOutOfBound)),
Some(v) => {
*v = u;
Ok(ScBytes(vnew.try_into()?))
}
}
})?;
self.add_host_object(vnew)
}
fn bytes_get(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
i: U32Val,
) -> Result<U32Val, HostError> {
let i: u32 = i.into();
self.visit_obj(b, |hv: &ScBytes| {
hv.get(i as usize)
.map(|u| Into::<U32Val>::into(Into::<u32>::into(*u)))
.ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
})
}
fn bytes_del(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
i: U32Val,
) -> Result<BytesObject, HostError> {
let i: u32 = i.into();
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
self.validate_index_lt_bound(i, hv.len())?;
let mut vnew: Vec<u8> = hv.metered_clone(&self.0.budget)?.into();
let n_elts = (hv.len() as u64).saturating_sub(i as u64);
metered_clone::charge_shallow_copy::<u8>(n_elts, self.as_budget())?;
vnew.remove(i as usize);
Ok(ScBytes(vnew.try_into()?))
})?;
self.add_host_object(vnew)
}
fn bytes_len(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
) -> Result<U32Val, HostError> {
let len = self.visit_obj(b, |hv: &ScBytes| Ok(hv.len()))?;
self.usize_to_u32val(len)
}
fn string_len(
&self,
_vmcaller: &mut VmCaller<Host>,
b: StringObject,
) -> Result<U32Val, HostError> {
let len = self.visit_obj(b, |hv: &ScString| Ok(hv.len()))?;
self.usize_to_u32val(len)
}
fn symbol_len(
&self,
_vmcaller: &mut VmCaller<Host>,
b: SymbolObject,
) -> Result<U32Val, HostError> {
let len = self.visit_obj(b, |hv: &ScSymbol| Ok(hv.len()))?;
self.usize_to_u32val(len)
}
fn bytes_push(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
u: U32Val,
) -> Result<BytesObject, HostError> {
let u = self.u8_from_u32val_input("u", u)?;
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
let len = hv.len().saturating_add(1);
metered_clone::charge_heap_alloc::<u8>(len as u64, self.as_budget())?;
metered_clone::charge_shallow_copy::<u8>(len as u64, self.as_budget())?;
let mut vnew: Vec<u8> = Vec::with_capacity(len);
vnew.extend_from_slice(hv.as_slice());
vnew.push(u);
Ok(ScBytes(vnew.try_into()?))
})?;
self.add_host_object(vnew)
}
fn bytes_pop(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
) -> Result<BytesObject, HostError> {
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
let mut vnew: Vec<u8> = hv.metered_clone(self.as_budget())?.into();
if vnew.pop().is_none() {
return Err(self.err_status(ScHostObjErrorCode::VecIndexOutOfBound));
}
Ok(ScBytes(vnew.try_into()?))
})?;
self.add_host_object(vnew)
}
fn bytes_front(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
) -> Result<U32Val, HostError> {
self.visit_obj(b, |hv: &ScBytes| {
hv.first()
.map(|u| Into::<U32Val>::into(Into::<u32>::into(*u)))
.ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
})
}
fn bytes_back(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
) -> Result<U32Val, HostError> {
self.visit_obj(b, |hv: &ScBytes| {
hv.last()
.map(|u| Into::<U32Val>::into(Into::<u32>::into(*u)))
.ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
})
}
fn bytes_insert(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
i: U32Val,
u: U32Val,
) -> Result<BytesObject, HostError> {
let i: u32 = i.into();
let u = self.u8_from_u32val_input("u", u)?;
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
self.validate_index_le_bound(i, hv.len())?;
let len = hv.len().saturating_add(1);
metered_clone::charge_heap_alloc::<u8>(len as u64, self.as_budget())?;
metered_clone::charge_shallow_copy::<u8>(len as u64, self.as_budget())?;
let mut vnew: Vec<u8> = Vec::with_capacity(len);
vnew.extend_from_slice(hv.as_slice());
metered_clone::charge_shallow_copy::<u8>(len as u64, self.as_budget())?;
vnew.insert(i as usize, u);
Ok(ScBytes(vnew.try_into()?))
})?;
self.add_host_object(vnew)
}
fn bytes_append(
&self,
_vmcaller: &mut VmCaller<Host>,
b1: BytesObject,
b2: BytesObject,
) -> Result<BytesObject, HostError> {
let vnew = self.visit_obj(b1, |sb1: &ScBytes| {
self.visit_obj(b2, |sb2: &ScBytes| {
if sb2.len() > u32::MAX as usize - sb1.len() {
return Err(
self.err_status_msg(ScHostFnErrorCode::InputArgsInvalid, "u32 overflow")
);
}
let len = sb1.len().saturating_add(sb2.len());
metered_clone::charge_heap_alloc::<u8>(len as u64, self.as_budget())?;
metered_clone::charge_shallow_copy::<u8>(len as u64, self.as_budget())?;
let mut vnew: Vec<u8> = Vec::with_capacity(len);
vnew.extend_from_slice(sb1.as_slice());
vnew.extend_from_slice(sb2.as_slice());
Ok(vnew)
})
})?;
self.add_host_object(ScBytes(vnew.try_into()?))
}
fn bytes_slice(
&self,
_vmcaller: &mut VmCaller<Host>,
b: BytesObject,
start: U32Val,
end: U32Val,
) -> Result<BytesObject, HostError> {
let start: u32 = start.into();
let end: u32 = end.into();
let vnew = self.visit_obj(b, move |hv: &ScBytes| {
let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
metered_clone::charge_heap_alloc::<u8>(range.len() as u64, self.as_budget())?;
metered_clone::charge_shallow_copy::<u8>(range.len() as u64, self.as_budget())?;
Ok(hv.as_slice()[range].to_vec())
})?;
self.add_host_object(self.scbytes_from_vec(vnew)?)
}
fn compute_hash_sha256(
&self,
_vmcaller: &mut VmCaller<Host>,
x: BytesObject,
) -> Result<BytesObject, HostError> {
let hash = self.sha256_hash_from_bytesobj_input(x)?;
self.add_host_object(self.scbytes_from_vec(hash)?)
}
fn verify_sig_ed25519(
&self,
_vmcaller: &mut VmCaller<Host>,
k: BytesObject,
x: BytesObject,
s: BytesObject,
) -> Result<Void, HostError> {
let public_key = self.ed25519_pub_key_from_bytesobj_input(k)?;
let sig = self.signature_from_bytesobj_input("sig", s)?;
let res = self.visit_obj(x, |payload: &ScBytes| {
self.verify_sig_ed25519_internal(payload.as_slice(), &public_key, &sig)
});
Ok(res?.into())
}
fn get_ledger_version(&self, _vmcaller: &mut VmCaller<Host>) -> Result<U32Val, Self::Error> {
self.with_ledger_info(|li| Ok(li.protocol_version.into()))
}
fn get_ledger_sequence(&self, _vmcaller: &mut VmCaller<Host>) -> Result<U32Val, Self::Error> {
self.with_ledger_info(|li| Ok(li.sequence_number.into()))
}
fn get_ledger_timestamp(&self, _vmcaller: &mut VmCaller<Host>) -> Result<U64Val, Self::Error> {
self.with_ledger_info(|li| Ok(self.add_host_object(li.timestamp)?.into()))
}
fn get_ledger_network_id(
&self,
_vmcaller: &mut VmCaller<Host>,
) -> Result<BytesObject, Self::Error> {
self.with_ledger_info(|li| {
self.add_host_object(self.scbytes_from_slice(li.network_id.as_slice())?)
})
}
fn get_current_call_stack(
&self,
_vmcaller: &mut VmCaller<Host>,
) -> Result<VecObject, HostError> {
let frames = self.0.context.borrow();
let get_host_val_tuple = |id: &Hash, function: &Symbol| -> Result<[RawVal; 2], HostError> {
let id_val = self.add_host_object(self.scbytes_from_hash(id)?)?.into();
let function_val = (*function).into();
Ok([id_val, function_val])
};
let mut outer = Vec::with_capacity(frames.len());
for frame in frames.iter() {
let vals = match frame {
#[cfg(feature = "vm")]
Frame::ContractVM(vm, function, _) => {
get_host_val_tuple(&vm.contract_id, &function)?
}
Frame::HostFunction(_) => continue,
Frame::Token(id, function, _) => get_host_val_tuple(id, function)?,
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(tc) => get_host_val_tuple(&tc.id, &tc.func)?,
};
let inner = MeteredVector::from_array(&vals, self.as_budget())?;
outer.push(self.add_host_object(inner)?.into());
}
self.add_host_object(HostVec::from_vec(outer)?)
}
fn fail_with_status(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
status: Status,
) -> Result<Void, Self::Error> {
if status.is_type(ScStatusType::ContractError) {
Err(self.err_status_msg(status, "failing with contract error status code '{}'"))
} else {
Err(self.err_status_msg(
ScHostValErrorCode::UnexpectedValType,
"contract attempted to fail with non-ContractError status code",
))
}
}
fn dummy0(&self, vmcaller: &mut VmCaller<Self::VmUserState>) -> Result<RawVal, Self::Error> {
Ok(().into())
}
fn require_auth_for_args(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
address: AddressObject,
args: VecObject,
) -> Result<RawVal, Self::Error> {
let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?;
Ok(self
.0
.authorization_manager
.borrow_mut()
.require_auth(
self,
address.get_handle(),
addr,
self.call_args_to_scvec(args)?,
)?
.into())
}
fn require_auth(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
address: AddressObject,
) -> Result<RawVal, Self::Error> {
let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?;
let args = self.with_current_frame(|f| {
let args = match f {
#[cfg(feature = "vm")]
Frame::ContractVM(_, _, args) => args,
Frame::HostFunction(_) => {
return Err(self.err_general("require_auth is not suppported for host fns"))
}
Frame::Token(_, _, args) => args,
#[cfg(any(test, feature = "testutils"))]
Frame::TestContract(c) => &c.args,
};
self.rawvals_to_scvec(args.iter())
})?;
Ok(self
.0
.authorization_manager
.borrow_mut()
.require_auth(self, address.get_handle(), addr, args)?
.into())
}
fn get_current_contract_id(
&self,
vmcaller: &mut VmCaller<Self::VmUserState>,
) -> Result<BytesObject, Self::Error> {
let id = self.get_current_contract_id_internal()?;
self.add_host_object(ScBytes(id.0.to_vec().try_into()?))
}
fn account_public_key_to_address(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
pk_bytes: BytesObject,
) -> Result<AddressObject, Self::Error> {
let account_id = self.account_id_from_bytesobj(pk_bytes)?;
self.add_host_object(ScAddress::Account(account_id))
}
fn contract_id_to_address(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
contract_id_bytes: BytesObject,
) -> Result<AddressObject, Self::Error> {
let contract_id = self.hash_from_bytesobj_input("contract_id", contract_id_bytes)?;
self.add_host_object(ScAddress::Contract(contract_id))
}
fn address_to_account_public_key(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
address: AddressObject,
) -> Result<RawVal, Self::Error> {
let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?;
match addr {
ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(pk))) => Ok(self
.add_host_object(ScBytes(pk.0.to_vec().try_into()?))?
.into()),
ScAddress::Contract(_) => Ok(().into()),
}
}
fn address_to_contract_id(
&self,
_vmcaller: &mut VmCaller<Self::VmUserState>,
address: AddressObject,
) -> Result<RawVal, Self::Error> {
let addr = self.visit_obj(address, |addr: &ScAddress| Ok(addr.clone()))?;
match addr {
ScAddress::Account(_) => Ok(().into()),
ScAddress::Contract(Hash(h)) => Ok(self
.add_host_object(ScBytes(h.to_vec().try_into()?))?
.into()),
}
}
}
#[cfg(any(test, feature = "testutils"))]
pub(crate) mod testutils {
use std::cell::Cell;
use std::panic::{catch_unwind, set_hook, take_hook, UnwindSafe};
use std::sync::Once;
pub fn call_with_suppressed_panic_hook<C, R>(closure: C) -> std::thread::Result<R>
where
C: FnOnce() -> R + UnwindSafe,
{
thread_local! {
static TEST_CONTRACT_CALL_COUNT: Cell<u64> = Cell::new(0);
}
static WRAP_PANIC_HOOK: Once = Once::new();
WRAP_PANIC_HOOK.call_once(|| {
let existing_panic_hook = take_hook();
set_hook(Box::new(move |info| {
let calling_test_contract = TEST_CONTRACT_CALL_COUNT.with(|c| c.get() != 0);
if !calling_test_contract {
existing_panic_hook(info)
}
}))
});
TEST_CONTRACT_CALL_COUNT.with(|c| {
let old_count = c.get();
let new_count = old_count.checked_add(1).expect("overflow");
c.set(new_count);
});
let res = catch_unwind(closure);
TEST_CONTRACT_CALL_COUNT.with(|c| {
let old_count = c.get();
let new_count = old_count.checked_sub(1).expect("overflow");
c.set(new_count);
});
res
}
}