Skip to content

Commit

Permalink
Add wrappers for live_until_ledger getters. (#1267)
Browse files Browse the repository at this point in the history
### What

Add wrappers for `live_until_ledger` getters.

Also cover the emulation of state expiration with some tests.

### Why

Improving test utilities:
#1162

### Known limitations

N/A
  • Loading branch information
dmkozh committed May 2, 2024
1 parent 1aea75d commit 2325682
Show file tree
Hide file tree
Showing 11 changed files with 752 additions and 25 deletions.
26 changes: 9 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ soroban-token-sdk = { version = "21.0.1-preview.1", path = "soroban-token-sdk" }

[workspace.dependencies.soroban-env-common]
version = "=21.0.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "8867a5d5b05681bde5eb8cf2031f615b1e8d8889"
git = "https://github.com/stellar/rs-soroban-env"
rev = "df2c5f7cdcf7a323319872f7967014f3d7716d69"

[workspace.dependencies.soroban-env-guest]
version = "=21.0.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "8867a5d5b05681bde5eb8cf2031f615b1e8d8889"
git = "https://github.com/stellar/rs-soroban-env"
rev = "df2c5f7cdcf7a323319872f7967014f3d7716d69"

[workspace.dependencies.soroban-env-host]
version = "=21.0.1"
# git = "https://github.com/stellar/rs-soroban-env"
# rev = "8867a5d5b05681bde5eb8cf2031f615b1e8d8889"
git = "https://github.com/stellar/rs-soroban-env"
rev = "df2c5f7cdcf7a323319872f7967014f3d7716d69"

[workspace.dependencies.stellar-strkey]
version = "=0.0.8"
Expand Down
2 changes: 1 addition & 1 deletion soroban-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ rand = "0.8.5"
ctor = "0.2.1"
hex = "0.4.3"
arbitrary = { version = "1.3.0", features = ["derive"] }
proptest = "1.2.0"
proptest = "1.2.0"
proptest-arbitrary-interop = "0.1.0"

[features]
Expand Down
29 changes: 29 additions & 0 deletions soroban-sdk/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,32 @@ impl DeployerWithAsset {
.into_val(&self.env)
}
}

#[cfg(any(test, feature = "testutils"))]
#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
mod testutils {
use crate::deploy::Deployer;
use crate::Address;

impl crate::testutils::Deployer for Deployer {
fn get_contract_instance_ttl(&self, contract: &Address) -> u32 {
self.env
.host()
.get_contract_instance_live_until_ledger(contract.to_object())
.unwrap()
.checked_sub(self.env.ledger().sequence())
.unwrap()
+ 1
}

fn get_contract_code_ttl(&self, contract: &Address) -> u32 {
self.env
.host()
.get_contract_code_live_until_ledger(contract.to_object())
.unwrap()
.checked_sub(self.env.ledger().sequence())
.unwrap()
+ 1
}
}
}
30 changes: 30 additions & 0 deletions soroban-sdk/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,18 +617,48 @@ mod testutils {
}
panic!("contract instance for current contract address not found");
}

fn get_ttl(&self) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_instance_live_until_ledger(env.current_contract_address().to_object())
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
+ 1
}
}

impl testutils::storage::Persistent for Persistent {
fn all(&self) -> Map<Val, Val> {
all(&self.storage.env, xdr::ContractDataDurability::Persistent)
}

fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_data_live_until_ledger(key.into_val(env), StorageType::Persistent)
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
+ 1
}
}

impl testutils::storage::Temporary for Temporary {
fn all(&self) -> Map<Val, Val> {
all(&self.storage.env, xdr::ContractDataDurability::Temporary)
}

fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
let env = &self.storage.env;
env.host()
.get_contract_data_live_until_ledger(key.into_val(env), StorageType::Temporary)
.unwrap()
.checked_sub(env.ledger().sequence())
.unwrap()
+ 1
}
}

fn all(env: &Env, d: xdr::ContractDataDurability) -> Map<Val, Val> {
Expand Down
122 changes: 122 additions & 0 deletions soroban-sdk/src/tests/storage_testutils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::testutils::{Deployer, Ledger};
use crate::{
self as soroban_sdk,
testutils::storage::{Instance as _, Persistent as _, Temporary as _},
Expand Down Expand Up @@ -40,3 +41,124 @@ fn all() {
);
});
}

#[test]
fn ttl_getters() {
let e = Env::default();
e.ledger().set_sequence_number(1000);
e.ledger().set_min_persistent_entry_ttl(100);
e.ledger().set_min_temp_entry_ttl(10);

let contract_a = e.register_contract(None, Contract);
let contract_b = e.register_contract(None, Contract);
let setup = || {
e.storage().persistent().set(&1, &3);
e.storage().temporary().set(&2, &4);
};
e.as_contract(&contract_a, setup);
e.as_contract(&contract_b, setup);

// Initial TTLs are defined by min persistent/temp entry TTL settings for the
// persistent/temp entries respectively.
let test_initial_storage_ttls = || {
assert_eq!(e.storage().instance().get_ttl(), 100);
assert_eq!(e.storage().persistent().get_ttl(&1), 100);
assert_eq!(e.storage().temporary().get_ttl(&2), 10);
};
e.as_contract(&contract_a, test_initial_storage_ttls);
e.as_contract(&contract_b, test_initial_storage_ttls);

// Instance and code have the same initial TTL as any other persistent entry.
for from_contract in [&contract_a, &contract_b] {
e.as_contract(from_contract, || {
assert_eq!(e.deployer().get_contract_instance_ttl(&contract_a), 100);
assert_eq!(e.deployer().get_contract_code_ttl(&contract_a), 100);
assert_eq!(e.deployer().get_contract_instance_ttl(&contract_b), 100);
assert_eq!(e.deployer().get_contract_code_ttl(&contract_b), 100);
});
}

// Extend instance, code and entry TTLs for contract A.
// Contract A and B share the code, so this also extends code (but not instance) for B.
e.as_contract(&contract_a, || {
e.storage().instance().extend_ttl(100, 1000);
e.deployer()
.extend_ttl_for_code(contract_a.clone(), 1000, 2000);
e.storage().persistent().extend_ttl(&1, 100, 500);
e.storage().temporary().extend_ttl(&2, 10, 300);
});

// Contract A has TTL extended for its entries.
// When TTL is extended, the current ledger is not included in `extend_to`
// parameter, so e.g. extending an entry to live for 1000 ledgers from now
// means that the TTL becomes 1001 (current ledger + 1000 ledgers of extension).
e.as_contract(&contract_a, || {
assert_eq!(e.storage().instance().get_ttl(), 1001);
assert_eq!(e.storage().persistent().get_ttl(&1), 501);
assert_eq!(e.storage().temporary().get_ttl(&2), 301);
});
// Contract B has no TTLs extended for its own storage.
e.as_contract(&contract_b, test_initial_storage_ttls);

for from_contract in [&contract_a, &contract_b] {
e.as_contract(from_contract, || {
assert_eq!(e.deployer().get_contract_instance_ttl(&contract_a), 1001);
assert_eq!(e.deployer().get_contract_code_ttl(&contract_a), 2001);
// Instance hasn't been extended for B.
assert_eq!(e.deployer().get_contract_instance_ttl(&contract_b), 100);
// Code has been extended for B.
assert_eq!(e.deployer().get_contract_code_ttl(&contract_b), 2001);
});
}
}

#[test]
fn temp_entry_expiration() {
let e = Env::default();
e.ledger().set_sequence_number(1000);
e.ledger().set_min_temp_entry_ttl(100);
let contract = e.register_contract(None, Contract);
e.as_contract(&contract, || {
e.storage().temporary().set(&1, &2);

// Temp entry acts as if it doesn't exist after expiration.
e.ledger().set_sequence_number(1100);
assert!(!e.storage().temporary().has(&1));

// Bump the ledger sequence back - the entry would exist again - test environment
// doesn't *actually* delete anything. Normally ledger sequence can never decrease
// though.
e.ledger().set_sequence_number(1099);
assert!(e.storage().temporary().has(&1));

// Bump the ledger sequence past expiration and set the new value for the entry.
e.ledger().set_sequence_number(2000);
assert!(!e.storage().temporary().has(&1));
e.storage().temporary().set(&1, &3);
// The entry is written and the new TTL is set based on min temp entry TTL
// setting.
assert_eq!(e.storage().temporary().get(&1), Some(3));
assert_eq!(e.storage().temporary().get_ttl(&1), 100);
});
}

#[test]
#[should_panic(expected = "[testing-only] Accessed contract data key key that has been archived")]
fn test_persistent_entry_expiration() {
let e = Env::default();
e.ledger().set_sequence_number(1000);
e.ledger().set_min_persistent_entry_ttl(100);

let contract = e.register_contract(None, Contract);
e.as_contract(&contract, || {
e.storage().persistent().set(&1, &2);

e.ledger().set_sequence_number(1100);
// Persistent entries are archived when they expire and they no longer can be accessed
// by the contracts at all.
// In actual networks the contract interaction won't happen at all if the footprint
// has any archived entries, but in the tests the closest thing we can do to that is
// to just panic (even if the value doesn't need to be accessed).
let _ = e.storage().persistent().has(&1);
});
}
20 changes: 20 additions & 0 deletions soroban-sdk/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,23 @@ pub trait Address {
/// the underlying Address value.
fn generate(env: &Env) -> crate::Address;
}

pub trait Deployer {
/// Gets the TTL of the given contract's instance.
///
/// TTL is the number of ledgers left until the instance entry is considered
/// expired including the current ledger.
///
/// Panics if there is no instance corresponding to the provided address,
/// or if the instance has expired.
fn get_contract_instance_ttl(&self, contract: &crate::Address) -> u32;

/// Gets the TTL of the given contract's Wasm code entry.
///
/// TTL is the number of ledgers left until the contract code entry
/// is considered expired, including the current ledger.
///
/// Panics if there is no contract instance/code corresponding to
/// the provided address, or if the instance/code has expired.
fn get_contract_code_ttl(&self, contract: &crate::Address) -> u32;
}
24 changes: 23 additions & 1 deletion soroban-sdk/src/testutils/storage.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
use crate::{Map, Val};
use crate::{Env, IntoVal, Map, Val};

/// Test utilities for [`Persistent`][crate::storage::Persistent].
pub trait Persistent {
/// Returns all data stored in persistent storage for the contract.
fn all(&self) -> Map<Val, Val>;

/// Gets the TTL for the persistent storage entry corresponding to the provided key.
///
/// TTL is the number of ledgers left until the persistent entry is considered
/// expired, including the current ledger.
///
/// Panics if there is no entry corresponding to the key, or if the entry has expired.
fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32;
}

/// Test utilities for [`Temporary`][crate::storage::Temporary].
pub trait Temporary {
/// Returns all data stored in temporary storage for the contract.
fn all(&self) -> Map<Val, Val>;

/// Gets the TTL for the temporary storage entry corresponding to the provided key.
///
/// TTL is the number of ledgers left until the temporary entry is considered
/// non-existent, including the current ledger.
///
/// Panics if there is no entry corresponding to the key.
fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32;
}

/// Test utilities for [`Instance`][crate::storage::Instance].
pub trait Instance {
/// Returns all data stored in Instance storage for the contract.
fn all(&self) -> Map<Val, Val>;

/// Gets the TTL for the current contract's instance entry.
///
/// TTL is the number of ledgers left until the instance entry is considered
/// expired, including the current ledger.
fn get_ttl(&self) -> u32;
}

0 comments on commit 2325682

Please sign in to comment.