Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jay code review #1045

Merged
merged 4 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ publish:
publish-dry-run:
./publish-dry-run.sh

# Requires: cargo-install llvm-cov
# Requires: `cargo install cargo-llvm-cov`
coverage:
rm -f lcov.info
cargo llvm-cov test --all-features --tests --lcov --output-path=lcov.info
62 changes: 10 additions & 52 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2220,32 +2220,11 @@ impl VmCallerEnv for Host {
high_expiration_watermark: U32Val,
) -> Result<Void, HostError> {
let contract_id = self.get_current_contract_id_internal()?;
let key = self.contract_instance_ledger_key(&contract_id)?;
self.try_borrow_storage_mut()?
.bump(
self,
key.metered_clone(self)?,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)
.map_err(|e| self.decorate_contract_instance_storage_error(e, &contract_id))?;
match self
.retrieve_contract_instance_from_storage(&key)?
.executable
{
ContractExecutable::Wasm(wasm_hash) => {
let key = self.contract_code_ledger_key(&wasm_hash)?;
self.try_borrow_storage_mut()?
.bump(
self,
key,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)
.map_err(|e| self.decorate_contract_code_storage_error(e, &wasm_hash))?;
}
ContractExecutable::Token => {}
}
self.bump_contract_instance_and_code_from_contract_id(
&contract_id,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)?;
Ok(Val::VOID)
}

Expand All @@ -2257,32 +2236,11 @@ impl VmCallerEnv for Host {
high_expiration_watermark: U32Val,
) -> Result<Void, Self::Error> {
let contract_id = self.contract_id_from_address(contract)?;
let key = self.contract_instance_ledger_key(&contract_id)?;
self.try_borrow_storage_mut()?
.bump(
self,
key.metered_clone(self)?,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)
.map_err(|e| self.decorate_contract_instance_storage_error(e, &contract_id))?;
match self
.retrieve_contract_instance_from_storage(&key)?
.executable
{
ContractExecutable::Wasm(wasm_hash) => {
let key = self.contract_code_ledger_key(&wasm_hash)?;
self.try_borrow_storage_mut()?
.bump(
self,
key,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)
.map_err(|e| self.decorate_contract_code_storage_error(e, &wasm_hash))?;
}
ContractExecutable::Token => {}
}
self.bump_contract_instance_and_code_from_contract_id(
&contract_id,
low_expiration_watermark.into(),
high_expiration_watermark.into(),
)?;
Ok(Val::VOID)
}

Expand Down
41 changes: 38 additions & 3 deletions soroban-env-host/src/host/data_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use core::cmp::min;
use std::rc::Rc;

use soroban_env_common::xdr::{
BytesM, ContractDataDurability, ContractIdPreimage, ExtensionPoint, HashIdPreimageContractId,
ScAddress, ScContractInstance, ScErrorCode, ScErrorType,
BytesM, ContractDataDurability, ContractExecutable, ContractIdPreimage, ExtensionPoint,
HashIdPreimageContractId, ScAddress, ScContractInstance, ScErrorCode, ScErrorType,
};
use soroban_env_common::{AddressObject, Env, U32Val};

Expand Down Expand Up @@ -110,7 +110,7 @@ impl Host {
let (current, expiration_ledger) = self
.try_borrow_storage_mut()?
.get_with_expiration(key, self.as_budget())?;
let mut current = (*current).metered_clone(self.as_budget())?;
let mut current = (*current).metered_clone(self)?;

match current.data {
LedgerEntryData::ContractData(ref mut entry) => {
Expand Down Expand Up @@ -153,6 +153,41 @@ impl Host {
Ok(())
}

pub(crate) fn bump_contract_instance_and_code_from_contract_id(
&self,
contract_id: &Hash,
low_expiration_watermark: u32,
high_expiration_watermark: u32,
) -> Result<(), HostError> {
let key = self.contract_instance_ledger_key(&contract_id)?;
self.try_borrow_storage_mut()?
.bump(
self,
key.metered_clone(self)?,
low_expiration_watermark,
high_expiration_watermark,
)
.map_err(|e| self.decorate_contract_instance_storage_error(e, &contract_id))?;
match self
.retrieve_contract_instance_from_storage(&key)?
.executable
{
ContractExecutable::Wasm(wasm_hash) => {
let key = self.contract_code_ledger_key(&wasm_hash)?;
self.try_borrow_storage_mut()?
.bump(
self,
key,
low_expiration_watermark,
high_expiration_watermark,
)
.map_err(|e| self.decorate_contract_code_storage_error(e, &wasm_hash))?;
}
ContractExecutable::Token => {}
}
Ok(())
}

// metering: covered by components
pub fn get_full_contract_id_preimage(
&self,
Expand Down
6 changes: 4 additions & 2 deletions soroban-env-host/src/host/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl Host {
// instead of snapshotting it and rolling it back, we just flush the
// changes only when rollback is not needed.
if orp.is_none() {
self.flush_instance_storage()?;
self.persist_instance_storage()?;
}
self.try_borrow_context_mut()?
.pop()
Expand Down Expand Up @@ -787,7 +787,9 @@ impl Host {
Ok(())
}

fn flush_instance_storage(&self) -> Result<(), HostError> {
// Make the in-memory instance storage persist into the `Storage` by writing
// its updated contents into corresponding `ContractData` ledger entry.
fn persist_instance_storage(&self) -> Result<(), HostError> {
let updated_instance = self.with_current_context_mut(|ctx| {
if let Some(storage) = &ctx.storage {
if !storage.is_modified {
Expand Down
164 changes: 6 additions & 158 deletions soroban-env-host/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use std::rc::Rc;

use soroban_env_common::xdr::{ScErrorCode, ScErrorType};
use soroban_env_common::{Compare, Env, Val};
use soroban_env_common::{Env, Val};

use crate::budget::Budget;
use crate::xdr::{LedgerEntry, LedgerKey};
Expand All @@ -19,6 +19,10 @@ use crate::{host::metered_map::MeteredOrdMap, HostError};

pub type FootprintMap = MeteredOrdMap<Rc<LedgerKey>, AccessType, Budget>;
pub type StorageMap = MeteredOrdMap<Rc<LedgerKey>, Option<(Rc<LedgerEntry>, Option<u32>)>, Budget>;

/// The in-memory instance storage of the current running contract. Initially
/// contains entries from the `ScMap` of the corresponding `ScContractInstance`
/// contract data entry.
#[derive(Clone)]
pub(crate) struct InstanceStorageMap {
pub(crate) map: MeteredOrdMap<Val, Val, Host>,
Expand Down Expand Up @@ -47,22 +51,6 @@ pub enum AccessType {
ReadWrite,
}

impl Compare<AccessType> for Host {
type Error = HostError;

fn compare(&self, a: &AccessType, b: &AccessType) -> Result<core::cmp::Ordering, Self::Error> {
Ok(a.cmp(b))
}
}

impl Compare<AccessType> for Budget {
type Error = HostError;

fn compare(&self, a: &AccessType, b: &AccessType) -> Result<core::cmp::Ordering, Self::Error> {
Ok(a.cmp(b))
}
}

/// A helper type used by [FootprintMode::Recording] to provide access
/// to a stable read-snapshot of a ledger.
pub trait SnapshotSource {
Expand Down Expand Up @@ -224,7 +212,7 @@ impl Storage {
///
/// In [FootprintMode::Enforcing] mode, succeeds only if the read
/// [LedgerKey] has been declared in the [Footprint].
pub fn get_with_expiration(
pub(crate) fn get_with_expiration(
&mut self,
key: &Rc<LedgerKey>,
budget: &Budget,
Expand Down Expand Up @@ -418,143 +406,3 @@ impl Storage {
Ok(())
}
}

#[cfg(test)]
mod test_footprint {

use soroban_env_common::xdr::ScAddress;

use super::*;
use crate::budget::Budget;
use crate::xdr::{ContractDataDurability, LedgerKeyContractData, ScVal};

#[test]
fn footprint_record_access() -> Result<(), HostError> {
let budget = Budget::default();
budget.reset_unlimited()?;
let mut fp = Footprint::default();
// record when key not exist
let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract([0; 32].into()),
key: ScVal::I32(0),
durability: ContractDataDurability::Persistent,
}));
fp.record_access(&key, AccessType::ReadOnly, &budget)?;
assert_eq!(fp.0.contains_key::<LedgerKey>(&key, &budget)?, true);
assert_eq!(
fp.0.get::<LedgerKey>(&key, &budget)?,
Some(&AccessType::ReadOnly)
);
// record and change access
fp.record_access(&key, AccessType::ReadWrite, &budget)?;
assert_eq!(
fp.0.get::<LedgerKey>(&key, &budget)?,
Some(&AccessType::ReadWrite)
);
fp.record_access(&key, AccessType::ReadOnly, &budget)?;
assert_eq!(
fp.0.get::<LedgerKey>(&key, &budget)?,
Some(&AccessType::ReadWrite)
);
Ok(())
}

#[test]
fn footprint_enforce_access() -> Result<(), HostError> {
let budget = Budget::default();
let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract([0; 32].into()),
key: ScVal::I32(0),
durability: ContractDataDurability::Persistent,
}));

// Key not in footprint. Only difference is type_
let key2 = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract([0; 32].into()),
key: ScVal::I32(0),
durability: ContractDataDurability::Temporary,
}));

let om = [(Rc::clone(&key), AccessType::ReadOnly)].into();
let mom = MeteredOrdMap::from_map(om, &budget)?;
let mut fp = Footprint(mom);
assert!(fp
.enforce_access(&key2, AccessType::ReadOnly, &budget)
.is_err());
fp.enforce_access(&key, AccessType::ReadOnly, &budget)?;
fp.0 =
fp.0.insert(Rc::clone(&key), AccessType::ReadWrite, &budget)?;
fp.enforce_access(&key, AccessType::ReadOnly, &budget)?;
fp.enforce_access(&key, AccessType::ReadWrite, &budget)?;
Ok(())
}

#[test]
fn footprint_enforce_access_not_exist() -> Result<(), HostError> {
let budget = Budget::default();
let mut fp = Footprint::default();
let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract([0; 32].into()),
key: ScVal::I32(0),
durability: ContractDataDurability::Persistent,
}));
let res = fp.enforce_access(&key, AccessType::ReadOnly, &budget);
assert!(HostError::result_matches_err(
res,
(ScErrorType::Storage, ScErrorCode::ExceededLimit)
));
Ok(())
}

#[test]
fn footprint_attempt_to_write_readonly_entry() -> Result<(), HostError> {
let budget = Budget::default();
let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
contract: ScAddress::Contract([0; 32].into()),
key: ScVal::I32(0),
durability: ContractDataDurability::Persistent,
}));
let om = [(Rc::clone(&key), AccessType::ReadOnly)].into();
let mom = MeteredOrdMap::from_map(om, &budget)?;
let mut fp = Footprint(mom);
let res = fp.enforce_access(&key, AccessType::ReadWrite, &budget);
assert!(HostError::result_matches_err(
res,
(ScErrorType::Storage, ScErrorCode::ExceededLimit)
));
Ok(())
}
}

#[cfg(test)]
pub(crate) mod test_storage {
use std::collections::BTreeMap;

use soroban_env_common::Error;

use super::*;
#[allow(dead_code)]
pub(crate) struct MockSnapshotSource(BTreeMap<Rc<LedgerKey>, (Rc<LedgerEntry>, Option<u32>)>);
#[allow(dead_code)]
impl MockSnapshotSource {
pub(crate) fn new() -> Self {
Self(BTreeMap::<Rc<LedgerKey>, (Rc<LedgerEntry>, Option<u32>)>::new())
}
}
impl SnapshotSource for MockSnapshotSource {
fn get(&self, key: &Rc<LedgerKey>) -> Result<(Rc<LedgerEntry>, Option<u32>), HostError> {
if let Some(val) = self.0.get(key) {
Ok((Rc::clone(&val.0), val.1))
} else {
Err(
Error::from_type_and_code(ScErrorType::Storage, ScErrorCode::MissingValue)
.into(),
)
}
}

fn has(&self, key: &Rc<LedgerKey>) -> Result<bool, HostError> {
Ok(self.0.contains_key(key))
}
}
}
Loading