diff --git a/check_output.txt b/check_output.txt new file mode 100644 index 0000000..4fd11a7 Binary files /dev/null and b/check_output.txt differ diff --git a/clippy_out.txt b/clippy_out.txt new file mode 100644 index 0000000..1915604 Binary files /dev/null and b/clippy_out.txt differ diff --git a/engine-core/src/core/proxy.rs b/engine-core/src/core/proxy.rs index a1a471d..1459ffd 100644 --- a/engine-core/src/core/proxy.rs +++ b/engine-core/src/core/proxy.rs @@ -27,6 +27,8 @@ pub struct UpgradeableProxy; impl UpgradeableProxy { /// Initialize the proxy with an admin address and a storage gap. pub fn init(env: Env, admin: Address) { + crate::non_reentrant!(&env); + if env.storage().instance().has(&ADMIN_KEY) { panic_with_error!(&env, ProxyError::AlreadyInitialized); } @@ -42,6 +44,8 @@ impl UpgradeableProxy { /// Upgrade the contract's WASM code. Only the admin can perform this operation. /// This provides a direct admin-controlled upgrade path. pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) { + crate::non_reentrant!(&env); + if !env.storage().instance().has(&ADMIN_KEY) { panic_with_error!(&env, ProxyError::NotInitialized); } @@ -58,6 +62,8 @@ impl UpgradeableProxy { /// ZK-ready integrity check invoked via the audit layer pub fn verify_integrity(env: Env, commitment: StateCommitment, payload: Bytes) { + crate::non_reentrant!(&env); + // Copy bytes to verify transition let mut payload_buf = alloc::vec::Vec::new(); payload_buf.resize(payload.len() as usize, 0); diff --git a/engine-core/src/core/tests.rs b/engine-core/src/core/tests.rs index adce92a..500ec06 100644 --- a/engine-core/src/core/tests.rs +++ b/engine-core/src/core/tests.rs @@ -1,4 +1,5 @@ use super::control_plane::{ControlPlane, ControlPlaneClient}; +use super::proxy::{UpgradeableProxy, UpgradeableProxyClient}; use crate::audit::compute_commitment; use crate::core::zk_hooks; use crate::types::StateCommitment; @@ -231,3 +232,90 @@ fn test_batch_update_param_success() { client.batch_update_param(&admin, ¶ms, &commitment, &payload); } + +fn proxy_initialized_client(env: &Env) -> (UpgradeableProxyClient<'_>, Address) { + env.mock_all_auths(); + let contract_id = env.register_contract(None, UpgradeableProxy); + let client = UpgradeableProxyClient::new(env, &contract_id); + let admin = Address::generate(env); + client.init(&admin); + (client, admin) +} + +fn proxy_setup_client(env: &Env) -> UpgradeableProxyClient<'_> { + let contract_id = env.register_contract(None, UpgradeableProxy); + UpgradeableProxyClient::new(env, &contract_id) +} + +#[test] +fn test_proxy_upgrade_success() { + let env = Env::default(); + let (client, _admin) = proxy_initialized_client(&env); + + let new_wasm_hash = BytesN::from_array(&env, &[1u8; 32]); + client.upgrade(&new_wasm_hash); +} + +#[test] +fn test_proxy_upgrade_rejects_zero_hash() { + let env = Env::default(); + let (client, _admin) = proxy_initialized_client(&env); + + let zero_hash = BytesN::from_array(&env, &[0u8; 32]); + let result = client.try_upgrade(&zero_hash); + assert!(result.is_err()); +} + +#[test] +#[should_panic] +fn test_proxy_upgrade_rejects_pre_init() { + let env = Env::default(); + env.mock_all_auths(); + let client = proxy_setup_client(&env); + + let new_wasm_hash = BytesN::from_array(&env, &[1u8; 32]); + client.upgrade(&new_wasm_hash); +} + +#[test] +#[should_panic] +fn test_proxy_verify_integrity_rejects_pre_init() { + let env = Env::default(); + env.mock_all_auths(); + let client = proxy_setup_client(&env); + + let author = Address::generate(&env); + let payload_bytes = Bytes::from_array(&env, &[1u8; 32]); + let payload_hash = BytesN::from_array(&env, &[1u8; 32]); + let hash = compute_commitment(&[0u8; 32], 0, &payload_hash.to_array()); + let commitment = StateCommitment { + sequence: 0, + state_hash: BytesN::from_array(&env, &hash), + ledger: env.ledger().sequence(), + author: author.clone(), + }; + client.verify_integrity(&commitment, &payload_bytes); +} + +#[test] +fn test_proxy_upgrade_rejects_non_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, UpgradeableProxy); + let client = UpgradeableProxyClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + env.as_contract(&contract_id, || { + env.storage() + .instance() + .set(&soroban_sdk::symbol_short!("ADMIN"), &admin); + let gap: soroban_sdk::Vec = + soroban_sdk::Vec::from_array(&env, [0u64; 50]); + env.storage() + .instance() + .set(&soroban_sdk::symbol_short!("GAP"), &gap); + }); + + let new_wasm_hash = BytesN::from_array(&env, &[2u8; 32]); + let result = client.try_upgrade(&new_wasm_hash); + assert!(result.is_err()); +} diff --git a/final_test.txt b/final_test.txt new file mode 100644 index 0000000..cb7a376 Binary files /dev/null and b/final_test.txt differ diff --git a/test_out2.txt b/test_out2.txt new file mode 100644 index 0000000..63c51b0 Binary files /dev/null and b/test_out2.txt differ diff --git a/test_out3.txt b/test_out3.txt new file mode 100644 index 0000000..fb1e17a Binary files /dev/null and b/test_out3.txt differ diff --git a/test_output.txt b/test_output.txt new file mode 100644 index 0000000..590adb3 Binary files /dev/null and b/test_output.txt differ