Root + Impact
Description
Normal Behavior
The secret vault should provide a coherent storage model where users can store and retrieve their secrets using consistent address targeting for both read and write operations.
Issue
The contract has a fundamental read/write address mismatch that makes the storage system completely incoherent. The set_secret function stores data under the caller's address, while get_secret always reads from the hardcoded @owner address. This creates a situation where most users can write data they can never read, and the contract fails to implement any logical storage model.
public entry fun set_secret(caller: &signer, secret: vector<u8>) {
    let secret_vault = Vault{secret: string::utf8(secret)};
    move_to(caller, secret_vault);  
    event::emit(SetNewSecret {});
}
#[view]
public fun get_secret(caller: address): String acquires Vault {
    assert!(caller == @owner, NOT_OWNER);
    let vault = borrow_global<Vault>(@owner);  
    vault.secret
}
Risk
Likelihood:
- Every non-owner user who calls - set_secretcreates unreachable data
 
- The address mismatch occurs on every write operation by non-owners 
- Contract deployment immediately creates this inconsistent state 
- No mechanism exists to resolve the mismatch 
Impact:
- Complete failure of core contract functionality for non-owner users 
- Data loss - users lose access to their stored secrets permanently 
- Business logic breakdown - contract violates basic read/write consistency 
- Unusable contract - primary use case (storing/retrieving secrets) is broken 
- Resource waste - users pay gas for operations that create inaccessible data 
Proof of Concept
The following test demonstrates how the address mismatch breaks the contract's functionality:
#[test(owner = @0xcc, user_a = @0x111, user_b = @0x222)]
fun test_read_write_address_mismatch(owner: &signer, user_a: &signer, user_b: &signer) acquires Vault {
    use aptos_framework::account;
    
    
    account::create_account_for_test(signer::address_of(owner));
    account::create_account_for_test(signer::address_of(user_a));
    account::create_account_for_test(signer::address_of(user_b));
    
    let user_a_addr = signer::address_of(user_a);    
    let user_b_addr = signer::address_of(user_b);    
    
    
    let user_a_secret = b"user_a_private_data";
    set_secret(user_a, user_a_secret);  
    
    let user_b_secret = b"user_b_private_data"; 
    set_secret(user_b, user_b_secret);  
    
    
    let vault_a = borrow_global<Vault>(user_a_addr);
    let vault_b = borrow_global<Vault>(user_b_addr);
    assert!(vault_a.secret == string::utf8(user_a_secret), 301);
    assert!(vault_b.secret == string::utf8(user_b_secret), 302);
    
    
    
    
}
#[test(user = @0x333)]
#[expected_failure] 
fun test_get_secret_fails_on_mismatch(user: &signer) acquires Vault {
    
    set_secret(user, b"my_secret");
    
    
    get_secret(signer::address_of(user)); 
}
Recommended Mitigation
Choose one consistent storage model and implement it properly:
public entry fun set_secret(caller: &signer, secret: vector<u8>) {
+   assert!(signer::address_of(caller) == @owner, NOT_OWNER);  // Only owner can write
    let secret_vault = Vault{secret: string::utf8(secret)};
-   move_to(caller, secret_vault);
+   move_to(@owner, secret_vault);  // Always store under @owner
    event::emit(SetNewSecret {});
}
#[view]
public fun get_secret(caller: address): String acquires Vault {
    assert!(caller == @owner, NOT_OWNER);
    let vault = borrow_global<Vault>(@owner);  // Consistent with storage
    vault.secret
}