use std::{cell::RefCell, collections::BTreeSet, convert::TryInto, iter::FromIterator, rc::Rc};
use rand::RngCore;
use casper_storage::{
global_state::state::lmdb::LmdbGlobalStateView, tracking_copy::new_temporary_tracking_copy,
AddressGenerator, TrackingCopy,
};
use super::{AllowInstallUpgrade, ExecError, RuntimeContext};
use crate::engine_state::{BlockInfo, EngineConfig, EngineConfigBuilder};
use casper_types::{
account::{
AccountHash, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure, ACCOUNT_HASH_LENGTH,
},
addressable_entity::{ActionType, AssociatedKeys, EntryPoints, Weight},
bytesrepr::ToBytes,
contracts::NamedKeys,
execution::TransformKindV2,
system::{AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT},
AccessRights, AddressableEntity, AddressableEntityHash, BlockGlobalAddr, BlockHash, BlockTime,
ByteCodeHash, CLValue, ContextAccessRights, Digest, EntityAddr, EntityKind, EntryPointType,
Gas, HashAddr, Key, PackageHash, Phase, ProtocolVersion, PublicKey, RuntimeArgs,
RuntimeFootprint, SecretKey, StoredValue, SystemHashRegistry, Tagged, Timestamp,
TransactionHash, TransactionV1Hash, URef, KEY_HASH_LENGTH, U256, U512,
};
use tempfile::TempDir;
const TXN_HASH_RAW: [u8; 32] = [1u8; 32];
const PHASE: Phase = Phase::Session;
const GAS_LIMIT: u64 = 500_000_000_000_000u64;
fn test_engine_config() -> EngineConfig {
EngineConfig::default()
}
fn new_tracking_copy(
account_hash: AccountHash,
init_entity_key: Key,
init_entity: AddressableEntity,
) -> (TrackingCopy<LmdbGlobalStateView>, TempDir) {
let entity_key_cl_value = CLValue::from_t(init_entity_key).expect("must convert to cl value");
let initial_data = [
(init_entity_key, StoredValue::AddressableEntity(init_entity)),
(
Key::Account(account_hash),
StoredValue::CLValue(entity_key_cl_value),
),
];
new_temporary_tracking_copy(initial_data, None, true)
}
fn new_addressable_entity_with_purse(
account_hash: AccountHash,
entity_hash: AddressableEntityHash,
entity_kind: EntityKind,
purse: [u8; 32],
) -> (Key, Key, AddressableEntity) {
let associated_keys = AssociatedKeys::new(account_hash, Weight::new(1));
let entity = AddressableEntity::new(
PackageHash::default(),
ByteCodeHash::default(),
ProtocolVersion::V2_0_0,
URef::new(purse, AccessRights::READ_ADD_WRITE),
associated_keys,
Default::default(),
entity_kind,
);
let account_key = Key::Account(account_hash);
let contract_key = Key::addressable_entity_key(entity_kind.tag(), entity_hash);
(account_key, contract_key, entity)
}
fn new_addressable_entity(
account_hash: AccountHash,
entity_hash: AddressableEntityHash,
) -> (Key, Key, AddressableEntity) {
new_addressable_entity_with_purse(
account_hash,
entity_hash,
EntityKind::Account(account_hash),
[0; 32],
)
}
fn random_account_key<G: RngCore>(entropy_source: &mut G) -> Key {
let mut key = [0u8; 32];
entropy_source.fill_bytes(&mut key);
Key::Account(AccountHash::new(key))
}
fn random_contract_key<G: RngCore>(entropy_source: &mut G) -> Key {
let mut key_hash = [0u8; 32];
entropy_source.fill_bytes(&mut key_hash);
Key::AddressableEntity(EntityAddr::SmartContract(key_hash))
}
fn create_uref_as_key(address_generator: &mut AddressGenerator, rights: AccessRights) -> Key {
let address = address_generator.create_address();
Key::URef(URef::new(address, rights))
}
fn random_hash<G: RngCore>(entropy_source: &mut G) -> Key {
let mut key = [0u8; KEY_HASH_LENGTH];
entropy_source.fill_bytes(&mut key);
Key::Hash(key)
}
fn new_runtime_context<'a>(
addressable_entity: &'a AddressableEntity,
account_hash: AccountHash,
entity_address: Key,
named_keys: &'a mut NamedKeys,
access_rights: ContextAccessRights,
address_generator: AddressGenerator,
) -> (RuntimeContext<'a, LmdbGlobalStateView>, TempDir) {
let (mut tracking_copy, tempdir) =
new_tracking_copy(account_hash, entity_address, addressable_entity.clone());
let mint_hash = HashAddr::default();
let default_system_registry = {
let mut registry = SystemHashRegistry::new();
registry.insert(MINT.to_string(), mint_hash);
registry.insert(HANDLE_PAYMENT.to_string(), HashAddr::default());
registry.insert(STANDARD_PAYMENT.to_string(), HashAddr::default());
registry.insert(AUCTION.to_string(), HashAddr::default());
StoredValue::CLValue(CLValue::from_t(registry).unwrap())
};
tracking_copy.write(Key::SystemEntityRegistry, default_system_registry);
tracking_copy.write(
Key::Account(account_hash),
StoredValue::CLValue(CLValue::from_t(entity_address).expect("must get cl_value")),
);
tracking_copy.write(
entity_address,
StoredValue::AddressableEntity(addressable_entity.clone()),
);
let now = Timestamp::now();
let cl_value = CLValue::from_t(now.millis()).expect("should get cl_value");
let stored_value = StoredValue::CLValue(cl_value);
tracking_copy.write(Key::BlockGlobal(BlockGlobalAddr::BlockTime), stored_value);
let protocol_version = ProtocolVersion::V2_0_0;
let cl_value = CLValue::from_t(protocol_version.destructure()).expect("should get cl_value");
let stored_value = StoredValue::CLValue(cl_value);
tracking_copy.write(
Key::BlockGlobal(BlockGlobalAddr::ProtocolVersion),
stored_value,
);
let cl_value = CLValue::from_t(false).expect("should get cl_value");
let stored_value = StoredValue::CLValue(cl_value);
tracking_copy.write(
Key::BlockGlobal(BlockGlobalAddr::AddressableEntity),
stored_value,
);
let addr = match entity_address {
Key::AddressableEntity(entity_addr) => entity_addr,
Key::Account(account_hash) => EntityAddr::Account(account_hash.value()),
Key::Hash(hash) => EntityAddr::SmartContract(hash),
_ => panic!("unexpected key"),
};
let runtime_footprint = RuntimeFootprint::new_entity_footprint(
addr,
addressable_entity.clone(),
named_keys.clone(),
EntryPoints::new(),
);
let engine_config = {
let config_builder = EngineConfigBuilder::new();
config_builder.with_enable_entity(true).build()
};
let runtime_context = RuntimeContext::new(
named_keys,
Rc::new(RefCell::new(runtime_footprint)),
entity_address,
BTreeSet::from_iter(vec![account_hash]),
access_rights,
account_hash,
Rc::new(RefCell::new(address_generator)),
Rc::new(RefCell::new(tracking_copy)),
engine_config,
BlockInfo::new(
Digest::default(),
BlockTime::new(0),
BlockHash::default(),
0,
ProtocolVersion::V2_0_0,
),
TransactionHash::V1(TransactionV1Hash::from_raw([1u8; 32])),
Phase::Session,
RuntimeArgs::new(),
Gas::new(U512::from(GAS_LIMIT)),
Gas::default(),
Vec::default(),
U512::MAX,
EntryPointType::Caller,
AllowInstallUpgrade::Forbidden,
);
(runtime_context, tempdir)
}
#[allow(clippy::assertions_on_constants)]
fn assert_forged_reference<T>(result: Result<T, ExecError>) {
match result {
Err(ExecError::ForgedReference(_)) => assert!(true),
_ => panic!("Error. Test should have failed with ForgedReference error but didn't."),
}
}
#[allow(clippy::assertions_on_constants)]
fn assert_invalid_access<T: std::fmt::Debug>(
result: Result<T, ExecError>,
expecting: AccessRights,
) {
match result {
Err(ExecError::InvalidAccess { required }) if required == expecting => assert!(true),
other => panic!(
"Error. Test should have failed with InvalidAccess error but didn't: {:?}.",
other
),
}
}
fn build_runtime_context_and_execute<T, F>(
mut named_keys: NamedKeys,
functor: F,
) -> Result<T, ExecError>
where
F: FnOnce(RuntimeContext<LmdbGlobalStateView>) -> Result<T, ExecError>,
{
let secret_key = SecretKey::ed25519_from_bytes([222; SecretKey::ED25519_LENGTH])
.expect("should create secret key");
let public_key = PublicKey::from(&secret_key);
let account_hash = public_key.to_account_hash();
let entity_hash = AddressableEntityHash::new([10u8; 32]);
let deploy_hash = [1u8; 32];
let (_, entity_key, addressable_entity) =
new_addressable_entity(public_key.to_account_hash(), entity_hash);
let address_generator = AddressGenerator::new(&deploy_hash, Phase::Session);
let access_rights = addressable_entity.extract_access_rights(entity_hash, &named_keys);
let (runtime_context, _tempdir) = new_runtime_context(
&addressable_entity,
account_hash,
entity_key,
&mut named_keys,
access_rights,
address_generator,
);
functor(runtime_context)
}
#[track_caller]
fn last_transform_kind_on_addressable_entity(
runtime_context: &RuntimeContext<LmdbGlobalStateView>,
) -> TransformKindV2 {
let key = runtime_context.context_key;
runtime_context
.effects()
.transforms()
.iter()
.rev()
.find_map(|transform| (transform.key() == &key).then(|| transform.kind().clone()))
.unwrap()
}
#[test]
fn use_uref_valid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_as_key = create_uref_as_key(&mut rng, AccessRights::READ_WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_as_key);
let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap());
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_write_gs(uref_as_key, value)
});
result.expect("writing using valid uref should succeed");
}
#[test]
fn use_uref_forged() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref = create_uref_as_key(&mut rng, AccessRights::READ_WRITE);
let named_keys = NamedKeys::new();
let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap());
let result =
build_runtime_context_and_execute(named_keys, |mut rc| rc.metered_write_gs(uref, value));
assert_forged_reference(result);
}
#[test]
fn account_key_not_writeable() {
let mut rng = rand::thread_rng();
let acc_key = random_account_key(&mut rng);
let result = build_runtime_context_and_execute(NamedKeys::new(), |mut rc| {
rc.metered_write_gs(
acc_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert_invalid_access(result, AccessRights::WRITE);
}
#[test]
fn entity_key_readable_valid() {
let result = build_runtime_context_and_execute(NamedKeys::new(), |rc| {
let context_key = rc.get_context_key();
let runtime_footprint = rc.runtime_footprint();
let entity_hash = runtime_footprint.borrow().hash_addr();
let key_hash = context_key.into_entity_hash_addr().expect("must get hash");
assert_eq!(entity_hash, key_hash);
Ok(())
});
assert!(result.is_ok());
}
#[test]
fn account_key_addable_returns_type_mismatch() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_as_key = create_uref_as_key(&mut rng, AccessRights::READ);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_as_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
let account_key: Key = rc.account_hash.into();
let uref_name = "NewURef".to_owned();
let named_key = StoredValue::CLValue(CLValue::from_t((uref_name, uref_as_key)).unwrap());
rc.metered_add_gs(account_key, named_key)
});
assert!(result.is_err());
}
#[test]
fn account_key_addable_invalid() {
let mut rng = rand::thread_rng();
let other_acc_key = random_account_key(&mut rng);
let result = build_runtime_context_and_execute(NamedKeys::new(), |mut rc| {
rc.metered_add_gs(
other_acc_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert_invalid_access(result, AccessRights::ADD);
}
#[test]
fn contract_key_readable_valid() {
let mut rng = rand::thread_rng();
let contract_key = random_contract_key(&mut rng);
let result =
build_runtime_context_and_execute(NamedKeys::new(), |mut rc| rc.read_gs(&contract_key));
assert!(result.is_ok());
}
#[test]
fn contract_key_not_writeable() {
let mut rng = rand::thread_rng();
let contract_key = random_contract_key(&mut rng);
let result = build_runtime_context_and_execute(NamedKeys::new(), |mut rc| {
rc.metered_write_gs(
contract_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert_invalid_access(result, AccessRights::WRITE);
}
#[test]
fn contract_key_addable_valid() {
let account_hash = AccountHash::new([0u8; 32]);
let entity_hash = AddressableEntityHash::new([1u8; 32]);
let (_account_key, entity_key, entity) = new_addressable_entity(account_hash, entity_hash);
let authorization_keys = BTreeSet::from_iter(vec![account_hash]);
let mut address_generator = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let mut rng = rand::thread_rng();
let contract_key = random_contract_key(&mut rng);
let entity_as_stored_value = StoredValue::AddressableEntity(AddressableEntity::default());
let mut access_rights = entity_as_stored_value
.as_addressable_entity()
.unwrap()
.extract_access_rights(AddressableEntityHash::default(), &NamedKeys::new());
let (tracking_copy, _tempdir) = new_tracking_copy(account_hash, entity_key, entity);
let tracking_copy = Rc::new(RefCell::new(tracking_copy));
tracking_copy
.borrow_mut()
.write(contract_key, entity_as_stored_value.clone());
let default_system_registry = {
let mut registry = SystemHashRegistry::new();
registry.insert(MINT.to_string(), HashAddr::default());
registry.insert(HANDLE_PAYMENT.to_string(), HashAddr::default());
registry.insert(STANDARD_PAYMENT.to_string(), HashAddr::default());
registry.insert(AUCTION.to_string(), HashAddr::default());
StoredValue::CLValue(CLValue::from_t(registry).unwrap())
};
tracking_copy
.borrow_mut()
.write(Key::SystemEntityRegistry, default_system_registry);
let uref_as_key = create_uref_as_key(&mut address_generator, AccessRights::WRITE);
let uref_name = "NewURef".to_owned();
let named_uref_tuple =
StoredValue::CLValue(CLValue::from_t((uref_name.clone(), uref_as_key)).unwrap());
let mut named_keys = NamedKeys::new();
named_keys.insert(uref_name, uref_as_key);
access_rights.extend(&[uref_as_key.into_uref().expect("should be a URef")]);
let addr = match contract_key {
Key::AddressableEntity(entity_addr) => entity_addr,
Key::Account(account_hash) => EntityAddr::Account(account_hash.value()),
Key::Hash(hash) => EntityAddr::SmartContract(hash),
_ => panic!("unexpected key"),
};
let runtime_footprint = RuntimeFootprint::new_entity_footprint(
addr,
AddressableEntity::default(),
named_keys.clone(),
EntryPoints::new(),
);
let mut runtime_context = RuntimeContext::new(
&mut named_keys,
Rc::new(RefCell::new(runtime_footprint)),
contract_key,
authorization_keys,
access_rights,
account_hash,
Rc::new(RefCell::new(address_generator)),
Rc::clone(&tracking_copy),
EngineConfig::default(),
BlockInfo::new(
Digest::default(),
BlockTime::new(0),
BlockHash::default(),
0,
ProtocolVersion::V2_0_0,
),
TransactionHash::V1(TransactionV1Hash::from_raw(TXN_HASH_RAW)),
PHASE,
RuntimeArgs::new(),
Gas::new(U512::from(GAS_LIMIT)),
Gas::default(),
Vec::default(),
U512::zero(),
EntryPointType::Caller,
AllowInstallUpgrade::Forbidden,
);
assert!(runtime_context
.metered_add_gs(contract_key, named_uref_tuple)
.is_err())
}
#[test]
fn contract_key_addable_invalid() {
let account_hash = AccountHash::new([0u8; 32]);
let entity_hash = AddressableEntityHash::new([1u8; 32]);
let (_, entity_key, entity) = new_addressable_entity(account_hash, entity_hash);
let authorization_keys = BTreeSet::from_iter(vec![account_hash]);
let mut address_generator = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let mut rng = rand::thread_rng();
let contract_key = random_contract_key(&mut rng);
let other_contract_key = random_contract_key(&mut rng);
let contract = StoredValue::AddressableEntity(AddressableEntity::default());
let mut access_rights = contract
.as_addressable_entity()
.unwrap()
.extract_access_rights(AddressableEntityHash::default(), &NamedKeys::new());
let (tracking_copy, _tempdir) = new_tracking_copy(account_hash, entity_key, entity.clone());
let tracking_copy = Rc::new(RefCell::new(tracking_copy));
tracking_copy.borrow_mut().write(contract_key, contract);
let uref_as_key = create_uref_as_key(&mut address_generator, AccessRights::WRITE);
let uref_name = "NewURef".to_owned();
let named_uref_tuple = StoredValue::CLValue(CLValue::from_t((uref_name, uref_as_key)).unwrap());
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_as_key);
access_rights.extend(&[uref_as_key.into_uref().expect("should be a URef")]);
let addr = match entity_key {
Key::AddressableEntity(entity_addr) => entity_addr,
Key::Account(account_hash) => EntityAddr::Account(account_hash.value()),
Key::Hash(hash) => EntityAddr::SmartContract(hash),
_ => panic!("unexpected key"),
};
let runtime_footprint = RuntimeFootprint::new_entity_footprint(
addr,
AddressableEntity::default(),
named_keys.clone(),
EntryPoints::new(),
);
let mut runtime_context = RuntimeContext::new(
&mut named_keys,
Rc::new(RefCell::new(runtime_footprint)),
other_contract_key,
authorization_keys,
access_rights,
account_hash,
Rc::new(RefCell::new(address_generator)),
Rc::clone(&tracking_copy),
EngineConfig::default(),
BlockInfo::new(
Digest::default(),
BlockTime::new(0),
BlockHash::default(),
0,
ProtocolVersion::V2_0_0,
),
TransactionHash::V1(TransactionV1Hash::from_raw(TXN_HASH_RAW)),
PHASE,
RuntimeArgs::new(),
Gas::new(U512::from(GAS_LIMIT)),
Gas::default(),
Vec::default(),
U512::zero(),
EntryPointType::Caller,
AllowInstallUpgrade::Forbidden,
);
let result = runtime_context.metered_add_gs(contract_key, named_uref_tuple);
assert_invalid_access(result, AccessRights::ADD);
}
#[test]
fn uref_key_readable_valid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::READ);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| rc.read_gs(&uref_key));
assert!(result.is_ok());
}
#[test]
fn uref_key_readable_invalid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| rc.read_gs(&uref_key));
assert_invalid_access(result, AccessRights::READ);
}
#[test]
fn uref_key_writeable_valid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_write_gs(
uref_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert!(result.is_ok());
}
#[test]
fn uref_key_writeable_invalid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::READ);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_write_gs(
uref_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert_invalid_access(result, AccessRights::WRITE);
}
#[test]
fn uref_key_addable_valid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::ADD_WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_write_gs(uref_key, CLValue::from_t(10_i32).unwrap())
.expect("Writing to the GlobalState should work.");
rc.metered_add_gs(uref_key, CLValue::from_t(1_i32).unwrap())
});
assert!(result.is_ok());
}
#[test]
fn uref_key_addable_invalid() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_key = create_uref_as_key(&mut rng, AccessRights::WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert(String::new(), uref_key);
let result = build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_add_gs(
uref_key,
StoredValue::CLValue(CLValue::from_t(1_i32).unwrap()),
)
});
assert_invalid_access(result, AccessRights::ADD);
}
#[test]
fn hash_key_is_not_writeable() {
let functor = |runtime_context: RuntimeContext<LmdbGlobalStateView>| {
let mut rng = rand::thread_rng();
let key = random_hash(&mut rng);
runtime_context.validate_writeable(&key)
};
let result = build_runtime_context_and_execute(NamedKeys::new(), functor);
assert!(result.is_err())
}
#[test]
fn hash_key_is_not_addable() {
let functor = |runtime_context: RuntimeContext<LmdbGlobalStateView>| {
let mut rng = rand::thread_rng();
let key = random_hash(&mut rng);
runtime_context.validate_addable(&key)
};
let result = build_runtime_context_and_execute(NamedKeys::new(), functor);
assert!(result.is_err())
}
#[test]
fn manage_associated_keys() {
let named_keys = NamedKeys::new();
let functor = |mut runtime_context: RuntimeContext<LmdbGlobalStateView>| {
let account_hash = AccountHash::new([42; 32]);
let weight = Weight::new(155);
runtime_context
.add_associated_key(account_hash, weight)
.expect("Unable to add key");
let transform_kind = last_transform_kind_on_addressable_entity(&runtime_context);
let entity = match transform_kind {
TransformKindV2::Write(StoredValue::AddressableEntity(entity)) => entity,
_ => panic!("Invalid transform operation found"),
};
entity
.associated_keys()
.get(&account_hash)
.expect("Account hash wasn't added to associated keys");
let new_weight = Weight::new(100);
runtime_context
.update_associated_key(account_hash, new_weight)
.expect("Unable to update key");
let transform_kind = last_transform_kind_on_addressable_entity(&runtime_context);
let entity = match transform_kind {
TransformKindV2::Write(StoredValue::AddressableEntity(entity)) => entity,
_ => panic!("Invalid transform operation found"),
};
let value = entity
.associated_keys()
.get(&account_hash)
.expect("Account hash wasn't added to associated keys");
assert_eq!(value, &new_weight, "value was not updated");
runtime_context
.remove_associated_key(account_hash)
.expect("Unable to remove key");
let transform_kind = last_transform_kind_on_addressable_entity(&runtime_context);
let entity = match transform_kind {
TransformKindV2::Write(StoredValue::AddressableEntity(entity)) => entity,
_ => panic!("Invalid transform operation found"),
};
let actual = entity.associated_keys().get(&account_hash);
assert!(actual.is_none());
runtime_context
.remove_associated_key(account_hash)
.expect_err("A non existing key was unexpectedly removed again");
Ok(())
};
let _ = build_runtime_context_and_execute(named_keys, functor);
}
#[test]
fn action_thresholds_management() {
let named_keys = NamedKeys::new();
let functor = |mut runtime_context: RuntimeContext<LmdbGlobalStateView>| {
let entity_hash_by_account_hash =
CLValue::from_t(Key::Hash([2; 32])).expect("must convert to cl_value");
runtime_context
.metered_write_gs_unsafe(
Key::Account(AccountHash::new([42; 32])),
entity_hash_by_account_hash,
)
.expect("must write key to gs");
runtime_context
.add_associated_key(AccountHash::new([42; 32]), Weight::new(254))
.expect("Unable to add associated key with maximum weight");
runtime_context
.set_action_threshold(ActionType::KeyManagement, Weight::new(253))
.expect("Unable to set action threshold KeyManagement");
runtime_context
.set_action_threshold(ActionType::Deployment, Weight::new(252))
.expect("Unable to set action threshold Deployment");
let transform_kind = last_transform_kind_on_addressable_entity(&runtime_context);
let mutated_entity = match transform_kind {
TransformKindV2::Write(StoredValue::AddressableEntity(entity)) => entity,
_ => panic!("Invalid transform operation found"),
};
assert_eq!(
mutated_entity.action_thresholds().deployment(),
&Weight::new(252)
);
assert_eq!(
mutated_entity.action_thresholds().key_management(),
&Weight::new(253)
);
runtime_context
.set_action_threshold(ActionType::Deployment, Weight::new(255))
.expect_err("Shouldn't be able to set deployment threshold higher than key management");
Ok(())
};
let _ = build_runtime_context_and_execute(named_keys, functor);
}
#[test]
fn should_verify_ownership_before_adding_key() {
let named_keys = NamedKeys::new();
let functor = |mut runtime_context: RuntimeContext<LmdbGlobalStateView>| {
let entity_hash_by_account_hash =
CLValue::from_t(Key::Hash([2; 32])).expect("must convert to cl_value");
runtime_context
.metered_write_gs_unsafe(
Key::Account(AccountHash::new([84; 32])),
entity_hash_by_account_hash,
)
.expect("must write key to gs");
runtime_context
.metered_write_gs_unsafe(Key::Hash([1; 32]), AddressableEntity::default())
.expect("must write key to gs");
runtime_context.context_key = Key::Hash([1; 32]);
let err = runtime_context
.add_associated_key(AccountHash::new([84; 32]), Weight::new(123))
.expect_err("This operation should return error");
match err {
ExecError::UnexpectedKeyVariant(_) => {
}
ExecError::AddKeyFailure(AddKeyFailure::PermissionDenied) => {}
e => panic!("Invalid error variant: {:?}", e),
}
Ok(())
};
let _ = build_runtime_context_and_execute(named_keys, functor);
}
#[test]
fn should_verify_ownership_before_removing_a_key() {
let named_keys = NamedKeys::new();
let functor = |mut runtime_context: RuntimeContext<LmdbGlobalStateView>| {
runtime_context.context_key = Key::Hash([1; 32]);
let err = runtime_context
.remove_associated_key(AccountHash::new([84; 32]))
.expect_err("This operation should return error");
match err {
ExecError::UnexpectedKeyVariant(_) => {
}
ExecError::RemoveKeyFailure(RemoveKeyFailure::PermissionDenied) => {}
ref e => panic!("Invalid error variant: {:?}", e),
}
Ok(())
};
let _ = build_runtime_context_and_execute(named_keys, functor);
}
#[test]
fn should_verify_ownership_before_setting_action_threshold() {
let named_keys = NamedKeys::new();
let functor = |mut runtime_context: RuntimeContext<LmdbGlobalStateView>| {
runtime_context.context_key = Key::Hash([1; 32]);
let err = runtime_context
.set_action_threshold(ActionType::Deployment, Weight::new(123))
.expect_err("This operation should return error");
match err {
ExecError::UnexpectedKeyVariant(_) => {
}
ExecError::SetThresholdFailure(SetThresholdFailure::PermissionDeniedError) => {}
ref e => panic!("Invalid error variant: {:?}", e),
}
Ok(())
};
let _ = build_runtime_context_and_execute(named_keys, functor);
}
#[test]
fn remove_uref_works() {
let deploy_hash = [1u8; 32];
let mut address_generator = AddressGenerator::new(&deploy_hash, Phase::Session);
let uref_name = "Foo".to_owned();
let uref_key = create_uref_as_key(&mut address_generator, AccessRights::READ);
let account_hash = AccountHash::new([0u8; 32]);
let entity_hash = AddressableEntityHash::new([0u8; 32]);
let mut named_keys = NamedKeys::new();
named_keys.insert(uref_name.clone(), uref_key);
let (_, entity_key, addressable_entity) = new_addressable_entity(account_hash, entity_hash);
let access_rights = addressable_entity.extract_access_rights(entity_hash, &named_keys);
let (mut runtime_context, _tempdir) = new_runtime_context(
&addressable_entity,
account_hash,
entity_key,
&mut named_keys,
access_rights,
address_generator,
);
assert!(runtime_context.named_keys_contains_key(&uref_name));
assert!(runtime_context.remove_key(&uref_name).is_ok());
assert!(runtime_context.validate_key(&uref_key).is_ok());
assert!(!runtime_context.named_keys_contains_key(&uref_name));
let entity_named_keys = runtime_context
.get_named_keys(entity_key)
.expect("must get named keys for entity");
assert!(!entity_named_keys.contains(&uref_name));
let next_session_access_rights = addressable_entity.extract_access_rights(
AddressableEntityHash::new(account_hash.value()),
&entity_named_keys,
);
let address_generator = AddressGenerator::new(&deploy_hash, Phase::Session);
let (runtime_context, _tempdir) = new_runtime_context(
&addressable_entity,
account_hash,
entity_key,
&mut named_keys,
next_session_access_rights,
address_generator,
);
assert!(runtime_context.validate_key(&uref_key).is_err());
}
#[test]
fn an_accounts_access_rights_should_include_main_purse() {
let test_main_purse = URef::new([42u8; 32], AccessRights::READ_ADD_WRITE);
let account_hash = AccountHash::new([0u8; 32]);
let entity_hash = AddressableEntityHash::new([1u8; 32]);
let named_keys = NamedKeys::new();
let (_context_key, _, entity) = new_addressable_entity_with_purse(
account_hash,
entity_hash,
EntityKind::Account(account_hash),
test_main_purse.addr(),
);
assert!(
named_keys.is_empty(),
"Named keys does not contain main purse"
);
let access_rights = entity.extract_access_rights(entity_hash, &named_keys);
assert!(
access_rights.has_access_rights_to_uref(&test_main_purse),
"Main purse should be included in access rights"
);
}
#[test]
fn validate_valid_purse_of_an_account() {
let test_main_purse = URef::new([42u8; 32], AccessRights::READ_ADD_WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert("entry".to_string(), Key::from(test_main_purse));
let deploy_hash = [1u8; 32];
let account_hash = AccountHash::new([0u8; 32]);
let entity_hash = AddressableEntityHash::new([1u8; 32]);
let (context_key, _, entity) = new_addressable_entity_with_purse(
account_hash,
entity_hash,
EntityKind::Account(account_hash),
test_main_purse.addr(),
);
let mut access_rights = entity.extract_access_rights(entity_hash, &named_keys);
access_rights.extend(&[test_main_purse]);
let address_generator = AddressGenerator::new(&deploy_hash, Phase::Session);
let (runtime_context, _tempdir) = new_runtime_context(
&entity,
account_hash,
context_key,
&mut named_keys,
access_rights,
address_generator,
);
assert!(runtime_context.validate_uref(&test_main_purse).is_ok());
let purse = test_main_purse.with_access_rights(AccessRights::READ);
assert!(runtime_context.validate_uref(&purse).is_ok());
let purse = test_main_purse.with_access_rights(AccessRights::ADD);
assert!(runtime_context.validate_uref(&purse).is_ok());
let purse = test_main_purse.with_access_rights(AccessRights::WRITE);
assert!(runtime_context.validate_uref(&purse).is_ok());
let purse = URef::new([53; 32], AccessRights::READ_ADD_WRITE);
assert!(runtime_context.validate_uref(&purse).is_err());
}
#[test]
fn should_meter_for_gas_storage_write() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_as_key = create_uref_as_key(&mut rng, AccessRights::READ_WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert("entry".to_string(), uref_as_key);
let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap());
let expected_write_cost = test_engine_config()
.storage_costs()
.calculate_gas_cost(value.serialized_length());
let (gas_usage_before, gas_usage_after) =
build_runtime_context_and_execute(named_keys, |mut rc| {
let gas_before = rc.gas_counter();
rc.metered_write_gs(uref_as_key, value)
.expect("should write");
let gas_after = rc.gas_counter();
Ok((gas_before, gas_after))
})
.expect("should run test");
assert!(
gas_usage_after > gas_usage_before,
"{} <= {}",
gas_usage_after,
gas_usage_before
);
assert_eq!(
Some(gas_usage_after),
gas_usage_before.checked_add(expected_write_cost)
);
}
#[test]
fn should_meter_for_gas_storage_add() {
let mut rng = AddressGenerator::new(&TXN_HASH_RAW, PHASE);
let uref_as_key = create_uref_as_key(&mut rng, AccessRights::ADD_WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert("entry".to_string(), uref_as_key);
let value = StoredValue::CLValue(CLValue::from_t(43_i32).unwrap());
let expected_add_cost = test_engine_config()
.storage_costs()
.calculate_gas_cost(value.serialized_length());
let (gas_usage_before, gas_usage_after) =
build_runtime_context_and_execute(named_keys, |mut rc| {
rc.metered_write_gs(uref_as_key, value.clone())
.expect("should write");
let gas_before = rc.gas_counter();
rc.metered_add_gs(uref_as_key, value).expect("should add");
let gas_after = rc.gas_counter();
Ok((gas_before, gas_after))
})
.expect("should run test");
assert!(
gas_usage_after > gas_usage_before,
"{} <= {}",
gas_usage_after,
gas_usage_before
);
assert_eq!(
Some(gas_usage_after),
gas_usage_before.checked_add(expected_add_cost)
);
}
#[test]
fn associated_keys_add_full() {
let final_add_result = build_runtime_context_and_execute(NamedKeys::new(), |mut rc| {
let associated_keys_before = rc.runtime_footprint().borrow().associated_keys().len();
for count in 0..(rc.engine_config.max_associated_keys() as usize - associated_keys_before) {
let account_hash = {
let mut addr = [0; ACCOUNT_HASH_LENGTH];
U256::from(count).to_big_endian(&mut addr);
AccountHash::new(addr)
};
let weight = Weight::new(count.try_into().unwrap());
rc.add_associated_key(account_hash, weight)
.unwrap_or_else(|e| panic!("should add key {}: {:?}", count, e));
}
rc.add_associated_key(AccountHash::new([42; 32]), Weight::new(42))
});
assert!(matches!(
final_add_result.expect_err("should error out"),
ExecError::AddKeyFailure(AddKeyFailure::MaxKeysLimit)
));
}