Skip to content
Open
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ target
# Local settings
.soroban
.stellar
temp_auto_push.bat
contracts/predictify-hybrid/test_snapshots

# Environment variables (never commit secrets!)
.env
.env.*
*.env
*.env.gitignore
branch_structure.json
9 changes: 9 additions & 0 deletions contracts/predictify-hybrid/src/event_topic_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub fn get_event_topic_catalog(env: &Env) -> Vec<EventTopicEntry> {
("accumulate_dispute_fee", "dispute_fee_accumulated", "Fired when a dispute fee is accumulated"),
("set_governance_min_bet_bps", "governance_min_bet_updated", "Fired when governance updates min bet bps"),
("update_rate_limits", "rate_limits_updated", "Fired when an admin updates the rate-limit configuration"),
("upgrade", "contract_upgraded", "Fired when the contract WASM is upgraded"),
];

for (entrypoint, topic, description) in entries {
Expand Down Expand Up @@ -80,4 +81,12 @@ mod tests {
let found = catalog.iter().any(|e| e.topic == String::from_str(&env, "rate_limits_updated"));
assert!(found);
}

#[test]
fn test_catalog_contains_contract_upgraded() {
let env = Env::default();
let catalog = get_event_topic_catalog(&env);
let found = catalog.iter().any(|e| e.topic == String::from_str(&env, "contract_upgraded"));
assert!(found);
}
}
176 changes: 176 additions & 0 deletions contracts/predictify-hybrid/src/slim_upgrade_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#![allow(dead_code)]
#![allow(unused_imports)]

use soroban_sdk::{
testutils::{Address as _, MockAuth, MockAuthInvoke},
Address, BytesN, Env, IntoVal,
};

use crate::{Error, PredictifyHybrid, PredictifyHybridClient};

/// Upload a tiny test WASM and return its hash. The `test_wasm/hello_world.wasm`
/// fixture is a minimal valid WASM module used as a real, deployable upgrade
/// target.
fn test_wasm_hash(env: &Env) -> BytesN<32> {
let wasm: &[u8] = include_bytes!("../test_wasm/hello_world.wasm");
env.deployer().upload_contract_wasm(wasm)
}

fn setup(env: &Env) -> (PredictifyHybridClient<'_>, Address) {
let admin = Address::generate(env);
let contract_id = env.register(PredictifyHybrid, ());
let client = PredictifyHybridClient::new(env, &contract_id);
client.initialize(&admin);
(client, admin)
}

/// Row 1: admin `upgrade` with the correct predecessor and a single
/// `MockAuth` succeeds, the stored hash is updated, and the event is emitted.
#[test]
fn slim_upgrade_admin_succeeds() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);

let new_hash = test_wasm_hash(&env);

env.mock_auths(&[MockAuth {
address: &admin,
invoke: &MockAuthInvoke {
contract: &client.address,
fn_name: "upgrade",
args: (admin.clone(), new_hash.clone(), genesis.clone()).into_val(&env),
sub_invokes: &[],
},
}]);

client.upgrade(&admin, &new_hash, &genesis);
assert_eq!(client.get_current_wasm_hash(), new_hash);
}

/// Row 2: a non-admin upgrade is rejected with `Unauthorized`.
#[test]
fn slim_upgrade_non_admin_unauthorized() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);
let attacker = Address::generate(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);
let new_hash = test_wasm_hash(&env);

let res = client.try_upgrade(&attacker, &new_hash, &genesis);
assert_eq!(res, Err(Ok(Error::Unauthorized)));
}

/// Row 3: a wrong expected predecessor is rejected by the chain check.
#[test]
fn slim_upgrade_wrong_predecessor_rejected() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);
let new_hash = test_wasm_hash(&env);
let wrong = BytesN::from_array(&env, &[9u8; 32]);

let res = client.try_upgrade(&admin, &new_hash, &wrong);
assert!(res.is_err());
}

/// Row 4: a zero predecessor is rejected once the current hash has been seeded.
#[test]
fn slim_upgrade_zero_predecessor_after_seed_rejected() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);
let new_hash = test_wasm_hash(&env);
let zero = BytesN::from_array(&env, &[0u8; 32]);

let res = client.try_upgrade(&admin, &new_hash, &zero);
assert!(res.is_err());
}

/// Row 5: `set_initial_wasm_hash` is one-shot; the second call is rejected.
#[test]
fn set_initial_wasm_hash_twice_rejected() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);

let res = client.try_set_initial_wasm_hash(&admin, &genesis);
assert!(res.is_err());
}

/// Row 6: the full `upgrade_contract` path authenticates exactly once.
#[cfg(feature = "full")]
#[test]
fn full_upgrade_contract_single_auth() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);
let new_hash = test_wasm_hash(&env);

env.mock_auths(&[MockAuth {
address: &admin,
invoke: &MockAuthInvoke {
contract: &client.address,
fn_name: "upgrade_contract",
args: (admin.clone(), new_hash.clone(), genesis.clone()).into_val(&env),
sub_invokes: &[],
},
}]);

client.upgrade_contract(&admin, &new_hash, &genesis);
assert_eq!(client.get_current_wasm_hash(), new_hash);
}

/// Row 7: the full `rollback_upgrade` path authenticates exactly once.
#[cfg(feature = "full")]
#[test]
fn full_rollback_upgrade_single_auth() {
let env = Env::default();
env.mock_all_auths();
let (client, admin) = setup(&env);

let genesis = test_wasm_hash(&env);
client.set_initial_wasm_hash(&admin, &genesis);
let new_hash = test_wasm_hash(&env);

env.mock_auths(&[MockAuth {
address: &admin,
invoke: &MockAuthInvoke {
contract: &client.address,
fn_name: "upgrade_contract",
args: (admin.clone(), new_hash.clone(), genesis.clone()).into_val(&env),
sub_invokes: &[],
},
}]);
client.upgrade_contract(&admin, &new_hash, &genesis);

env.mock_auths(&[MockAuth {
address: &admin,
invoke: &MockAuthInvoke {
contract: &client.address,
fn_name: "rollback_upgrade",
args: (admin.clone(), genesis.clone(), new_hash.clone()).into_val(&env),
sub_invokes: &[],
},
}]);
client.rollback_upgrade(&admin, &genesis, &new_hash);
assert_eq!(client.get_current_wasm_hash(), genesis);
}
24 changes: 24 additions & 0 deletions public/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Fonts Directory

This directory contains custom fonts for the Blockchain Explorer application.

## Required Font Files

The application expects the following font files:

1. **BlockchainFont-Regular.woff2** and **BlockchainFont-Regular.woff**
- Regular weight font for the main UI

2. **BlockchainFont-Bold.woff2** and **BlockchainFont-Bold.woff**
- Bold weight font for headings

3. **TechMono-Regular.woff2** and **TechMono-Regular.woff**
- Monospace font for code and hash displays

## Note

If you don't have custom fonts, the application will fall back to system fonts:
- BlockchainFont β†’ system sans-serif fonts
- TechMono β†’ system monospace fonts (Courier New, etc.)

The fonts are referenced in `public/index.html` and will be loaded automatically when available.
Binary file added public/fonts/fa-brands-400.eot
Binary file not shown.
Loading
Loading