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

Emulate access to custom account instance and Wasm in recording auth. #1476

Merged
merged 2 commits into from
Nov 5, 2024
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
43 changes: 39 additions & 4 deletions soroban-env-host/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ use super::xdr::Hash;
use crate::{
builtin_contracts::{account_contract::AccountEd25519Signature, base_types::BytesN},
host::error::TryBorrowOrErr,
xdr::PublicKey,
xdr::{ContractExecutable, PublicKey},
};
#[cfg(any(test, feature = "recording_mode"))]
use rand::Rng;
Expand Down Expand Up @@ -2072,9 +2072,44 @@ impl AccountAuthorizationTracker {
// - Return budget error in case if it was suppressed above.
let _ = acc.metered_clone(host.as_budget())?;
}
// Skip custom accounts for now - emulating authentication for
// them requires a dummy signature.
ScAddress::Contract(_) => {}
// We only know for sure that the contract instance and Wasm will be
// loaded.
ScAddress::Contract(contract_id) => {
let instance_key = host.contract_instance_ledger_key(&contract_id)?;
let entry = host
.try_borrow_storage_mut()?
.try_get(&instance_key, host, None)?;
// In test scenarios we often may not have any actual instance, which is fine most
// of the time, so we don't return any errors.
// In simulation scenarios the instance will likely be there, and when it's
// not, we still make our best effort and include at least the necessary instance key
// into the footprint.
let instance = if let Some(entry) = entry {
match &entry.data {
LedgerEntryData::ContractData(e) => match &e.val {
ScVal::ContractInstance(instance) => instance.metered_clone(host)?,
_ => {
return Ok(());
}
},
_ => {
return Ok(());
}
}
} else {
return Ok(());
};

match &instance.executable {
ContractExecutable::Wasm(wasm_hash) => {
let wasm_key = host.contract_code_ledger_key(wasm_hash)?;
let _ = host
.try_borrow_storage_mut()?
.try_get(&wasm_key, host, None)?;
}
ContractExecutable::StellarAsset => (),
}
}
}
Ok(())
}
Expand Down
64 changes: 55 additions & 9 deletions soroban-env-host/src/e2e_testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use crate::xdr::{
ExtensionPoint, HashIdPreimage, HashIdPreimageContractId, HostFunction, InvokeContractArgs,
LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey, LedgerKeyContractCode,
LedgerKeyContractData, Limits, PublicKey, ScAddress, ScBytes, ScContractInstance, ScMapEntry,
ScSymbol, ScVal, SequenceNumber, SorobanAuthorizationEntry, SorobanAuthorizedFunction,
SorobanAuthorizedInvocation, SorobanCredentials, Thresholds, Uint256, WriteXdr,
ScSymbol, ScVal, SequenceNumber, SorobanAddressCredentials, SorobanAuthorizationEntry,
SorobanAuthorizedFunction, SorobanAuthorizedInvocation, SorobanCredentials, Thresholds,
Uint256, WriteXdr,
};
use crate::{Host, LedgerInfo};
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -126,8 +127,27 @@ impl CreateContractData {
wasm: &[u8],
refined_cost_inputs: bool,
) -> Self {
let deployer = get_account_id([123; 32]);
let contract_id_preimage = get_contract_id_preimage(&deployer, &salt);
Self::new_with_refined_contract_cost_inputs_and_deployer(
None,
salt,
wasm,
refined_cost_inputs,
)
}

pub fn new_with_refined_contract_cost_inputs_and_deployer(
deployer_with_nonce: Option<(ScAddress, i64)>,
salt: [u8; 32],
wasm: &[u8],
refined_cost_inputs: bool,
) -> Self {
let source = get_account_id([123; 32]);
let deployer = if let Some((deployer, _)) = &deployer_with_nonce {
deployer.clone()
} else {
ScAddress::Account(source.clone())
};
let contract_id_preimage = get_contract_id_preimage_from_address(&deployer, &salt);

let host_fn = HostFunction::CreateContract(CreateContractArgs {
contract_id_preimage: contract_id_preimage.clone(),
Expand All @@ -143,7 +163,8 @@ impl CreateContractData {
key: ScVal::LedgerKeyContractInstance,
durability: ContractDataDurability::Persistent,
});
let auth_entry = create_contract_auth(&contract_id_preimage, wasm);
let auth_entry =
create_contract_auth_for_address(deployer_with_nonce, &contract_id_preimage, wasm);

let contract_entry = ledger_entry(LedgerEntryData::ContractData(ContractDataEntry {
ext: ExtensionPoint::V0,
Expand All @@ -159,7 +180,7 @@ impl CreateContractData {
let wasm_entry = wasm_entry_with_refined_contract_cost_inputs(wasm, refined_cost_inputs);

Self {
deployer,
deployer: source,
wasm_key: get_wasm_key(wasm),
wasm_entry,
contract_key,
Expand All @@ -184,13 +205,20 @@ pub fn get_account_id(pub_key: [u8; 32]) -> AccountId {
AccountId(PublicKey::PublicKeyTypeEd25519(pub_key.try_into().unwrap()))
}

pub fn get_contract_id_preimage(account_id: &AccountId, salt: &[u8; 32]) -> ContractIdPreimage {
pub fn get_contract_id_preimage_from_address(
address: &ScAddress,
salt: &[u8; 32],
) -> ContractIdPreimage {
ContractIdPreimage::Address(ContractIdPreimageFromAddress {
address: ScAddress::Account(account_id.clone()),
address: address.clone(),
salt: Uint256(*salt),
})
}

pub fn get_contract_id_preimage(account_id: &AccountId, salt: &[u8; 32]) -> ContractIdPreimage {
get_contract_id_preimage_from_address(&ScAddress::Account(account_id.clone()), salt)
}

pub fn get_contract_id_hash(id_preimage: &ContractIdPreimage) -> [u8; 32] {
let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId {
network_id: DEFAULT_NETWORK_ID.try_into().unwrap(),
Expand All @@ -203,8 +231,26 @@ pub fn create_contract_auth(
contract_id_preimage: &ContractIdPreimage,
wasm: &[u8],
) -> SorobanAuthorizationEntry {
create_contract_auth_for_address(None, contract_id_preimage, wasm)
}

pub fn create_contract_auth_for_address(
address_and_nonce: Option<(ScAddress, i64)>,
contract_id_preimage: &ContractIdPreimage,
wasm: &[u8],
) -> SorobanAuthorizationEntry {
let credentials = if let Some((address, nonce)) = address_and_nonce {
SorobanCredentials::Address(SorobanAddressCredentials {
address,
nonce,
signature_expiration_ledger: 0,
signature: ScVal::Void,
})
} else {
SorobanCredentials::SourceAccount
};
SorobanAuthorizationEntry {
credentials: SorobanCredentials::SourceAccount,
credentials,
root_invocation: SorobanAuthorizedInvocation {
function: SorobanAuthorizedFunction::CreateContractV2HostFn(CreateContractArgsV2 {
contract_id_preimage: contract_id_preimage.clone(),
Expand Down
148 changes: 145 additions & 3 deletions soroban-env-host/src/test/e2e_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use crate::{
ContractIdPreimage, ContractIdPreimageFromAddress, CreateContractArgs, DiagnosticEvent,
ExtensionPoint, HashIdPreimage, HashIdPreimageSorobanAuthorization, HostFunction,
InvokeContractArgs, LedgerEntry, LedgerEntryData, LedgerFootprint, LedgerKey,
LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, ScErrorCode,
ScErrorType, ScMap, ScVal, ScVec, SorobanAuthorizationEntry, SorobanCredentials,
SorobanResources, TtlEntry, Uint256, WriteXdr,
LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress,
ScContractInstance, ScErrorCode, ScErrorType, ScMap, ScNonceKey, ScVal, ScVec,
SorobanAuthorizationEntry, SorobanCredentials, SorobanResources, TtlEntry, Uint256,
WriteXdr,
},
Host, HostError, LedgerInfo,
};
Expand Down Expand Up @@ -1201,6 +1202,147 @@ fn test_create_contract_success_in_recording_mode() {
);
}

#[test]
fn test_create_contract_success_in_recording_mode_with_custom_account() {
// We don't try to invoke `__check_auth` in recording mode in order to not output confusing
// side-effects. Thus any Wasm can stand for a custom account.
let custom_account_wasm = CONTRACT_STORAGE;
let custom_account_address = ScAddress::Contract([222; 32].into());
let expected_nonce = 801925984706572462_i64;

let cd = CreateContractData::new_with_refined_contract_cost_inputs_and_deployer(
Some((custom_account_address.clone(), expected_nonce)),
[111; 32],
ADD_I32,
true,
);

let custom_account_instance_entry =
ledger_entry(LedgerEntryData::ContractData(ContractDataEntry {
ext: ExtensionPoint::V0,
contract: custom_account_address.clone(),
key: ScVal::LedgerKeyContractInstance,
durability: ContractDataDurability::Persistent,
val: ScVal::ContractInstance(ScContractInstance {
executable: ContractExecutable::Wasm(
get_wasm_hash(custom_account_wasm).try_into().unwrap(),
),
storage: None,
}),
}));
let ledger_info = default_ledger_info();
let res = invoke_host_function_recording_helper(
true,
&cd.host_fn,
&cd.deployer,
None,
&ledger_info,
vec![
(
cd.wasm_entry.clone(),
Some(ledger_info.sequence_number + 100),
),
(
wasm_entry(custom_account_wasm),
Some(ledger_info.sequence_number + 1000),
),
(
custom_account_instance_entry.clone(),
Some(ledger_info.sequence_number + 1000),
),
],
&prng_seed(),
None,
)
.unwrap();
assert_eq!(
res.invoke_result.unwrap(),
ScVal::Address(cd.contract_address.clone())
);
assert!(res.contract_events.is_empty());

let nonce_key = ScVal::LedgerKeyNonce(ScNonceKey {
nonce: expected_nonce,
});
let nonce_entry_key = LedgerKey::ContractData(LedgerKeyContractData {
contract: custom_account_address.clone(),
key: nonce_key.clone(),
durability: ContractDataDurability::Temporary,
});
assert_eq!(
res.ledger_changes,
vec![
LedgerEntryChangeHelper {
read_only: false,
key: cd.contract_key.clone(),
old_entry_size_bytes: 0,
new_value: Some(cd.contract_entry),
ttl_change: Some(LedgerEntryLiveUntilChange {
key_hash: compute_key_hash(&cd.contract_key),
durability: ContractDataDurability::Persistent,
old_live_until_ledger: 0,
new_live_until_ledger: ledger_info.sequence_number
+ ledger_info.min_persistent_entry_ttl
- 1,
}),
},
LedgerEntryChangeHelper::no_op_change(
&custom_account_instance_entry,
ledger_info.sequence_number + 1000,
),
LedgerEntryChangeHelper {
read_only: false,
key: nonce_entry_key.clone(),
old_entry_size_bytes: 0,
new_value: Some(ledger_entry(LedgerEntryData::ContractData(
ContractDataEntry {
ext: ExtensionPoint::V0,
contract: custom_account_address.clone(),
key: nonce_key.clone(),
durability: ContractDataDurability::Temporary,
val: ScVal::Void,
}
))),
ttl_change: Some(LedgerEntryLiveUntilChange {
key_hash: compute_key_hash(&nonce_entry_key),
durability: ContractDataDurability::Temporary,
old_live_until_ledger: 0,
new_live_until_ledger: ledger_info.sequence_number + ledger_info.max_entry_ttl
- 1,
}),
},
LedgerEntryChangeHelper::no_op_change(
&cd.wasm_entry,
ledger_info.sequence_number + 100
),
LedgerEntryChangeHelper::no_op_change(
&wasm_entry(custom_account_wasm),
ledger_info.sequence_number + 1000
),
]
);
assert_eq!(res.auth, vec![cd.auth_entry]);
assert_eq!(
res.resources,
SorobanResources {
footprint: LedgerFootprint {
read_only: vec![
ledger_entry_to_ledger_key(&custom_account_instance_entry, &Budget::default())
.unwrap(),
cd.wasm_key,
get_wasm_key(custom_account_wasm),
]
.try_into()
.unwrap(),
read_write: vec![cd.contract_key, nonce_entry_key].try_into().unwrap()
},
instructions: 1767122,
read_bytes: 3816,
write_bytes: 176,
}
);
}

#[test]
fn test_create_contract_success_in_recording_mode_with_enforced_auth() {
let cd = CreateContractData::new([111; 32], ADD_I32);
Expand Down
Loading