Skip to content
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
20 changes: 13 additions & 7 deletions contracts/dispute_resolution_contract/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,19 @@ impl DisputeResolutionContract {
.driver
.unwrap_or_else(|| panic_with_error!(&env, SwiftChainError::ProviderNotFound));

let reputation_addr = Self::get_identity_reputation_contract(env.clone());
let reputation_client = IdentityReputationContractClient::new(&env, &reputation_addr);
reputation_client.decrease_reputation(
&env.current_contract_address(),
&driver,
&DISPUTE_REPUTATION_PENALTY,
);
if let Some(reputation_addr) = env
.storage()
.instance()
.get::<DataKey, Address>(&DataKey::IdentityReputationContract)
{
let reputation_client =
IdentityReputationContractClient::new(&env, &reputation_addr);
reputation_client.decrease_reputation(
&env.current_contract_address(),
&driver,
&DISPUTE_REPUTATION_PENALTY,
);
}

let escrow_addr = Self::get_escrow_contract(env.clone());

Expand Down
5 changes: 3 additions & 2 deletions contracts/dispute_resolution_contract/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,9 @@ fn test_raise_dispute_delivered_exceeds_time_limit() {
fn test_resolve_dispute_refund_sender_by_admin() {
let (env, admin, sender, recipient, driver, delivery_id, escrow_id, dispute_client) = setup_test();

// Setup mock delivery
let delivery_record = create_mock_delivery_record(&env, did(4), sender.clone(), recipient.clone(), delivery_contract::DeliveryStatus::Active, None);
// Setup mock delivery with driver assigned (required for reputation penalty on resolve)
let mut delivery_record = create_mock_delivery_record(&env, did(4), sender.clone(), recipient.clone(), delivery_contract::DeliveryStatus::Active, None);
delivery_record.driver = Some(driver.clone());
set_mock_delivery(&env, &delivery_id, did(4), &delivery_record);

// Setup mock escrow as Paused (representing escrow paused after dispute raised)
Expand Down
14 changes: 9 additions & 5 deletions contracts/fleet_management_contract/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,21 @@ impl FleetManagementContract {

/// Invite a driver to a fleet. Only the fleet owner may call this.
///
/// Stores a `Pending` invite for `driver` under this fleet. The driver
/// must later call `accept_fleet_invite` to become active.
pub fn add_driver_to_fleet(env: Env, fleet_id: FleetId, driver: Address) {
/// `caller` must be the registered fleet owner and must sign the
/// transaction. Stores a `Pending` invite for `driver` under this fleet.
/// The driver must later call `accept_fleet_invite` to become active.
pub fn add_driver_to_fleet(env: Env, caller: Address, fleet_id: FleetId, driver: Address) {
caller.require_auth();

let profile: FleetProfile = env
.storage()
.persistent()
.get(&DataKey::Fleet(fleet_id))
.unwrap_or_else(|| panic_with_error!(&env, FleetError::FleetNotFound));

// Require fleet-owner authorisation.
profile.owner.require_auth();
if profile.owner != caller {
panic_with_error!(&env, FleetError::Unauthorized);
}

let invite_key = DataKey::DriverFleet(fleet_id, driver.clone());

Expand Down
113 changes: 71 additions & 42 deletions contracts/fleet_management_contract/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ fn test_update_fleet_treasury_rejects_non_owner() {
#[test]
fn test_add_driver_stores_pending_invite() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);

let status = client.get_driver_fleet_status(&fleet_id, &driver);
assert_eq!(status, Some(DriverFleetStatus::Pending));
Expand All @@ -162,10 +162,10 @@ fn test_add_driver_stores_pending_invite() {
#[test]
fn test_add_driver_emits_driver_invited_event() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);

let events = env.events().all();
let last_event = events.last().unwrap();
Expand All @@ -182,31 +182,60 @@ fn test_add_driver_emits_driver_invited_event() {
#[should_panic(expected = "Error(Contract, #5)")]
fn test_add_driver_twice_panics() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
// Second invite to the same driver must panic.
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
}

#[test]
#[should_panic(expected = "Error(Contract, #4)")]
fn test_add_driver_to_unknown_fleet_panics() {
let (env, client, _admin) = setup_test();
let caller = Address::generate(&env);
let driver = Address::generate(&env);
client.add_driver_to_fleet(&caller, &999, &driver);
}

// Issue #74 — Fleet Owner Authorization ─────────────────────────────────────

#[test]
#[should_panic(expected = "Error(Contract, #3)")]
fn test_add_driver_non_owner_is_rejected() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);

let attacker = Address::generate(&env);
let driver = Address::generate(&env);
// attacker is not the fleet owner — must panic with Unauthorized.
client.add_driver_to_fleet(&attacker, &fleet_id, &driver);
}

#[test]
fn test_add_driver_only_owner_can_invite() {
let (env, client, _admin) = setup_test();
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&999, &driver);
// Fleet owner successfully invites a driver.
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
assert_eq!(
client.get_driver_fleet_status(&fleet_id, &driver),
Some(DriverFleetStatus::Pending)
);
}

// ── Issue #69 tests — accept_fleet_invite ────────────────────────────────────

#[test]
fn test_accept_invite_promotes_driver_to_active() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

let status = client.get_driver_fleet_status(&fleet_id, &driver);
Expand All @@ -216,13 +245,13 @@ fn test_accept_invite_promotes_driver_to_active() {
#[test]
fn test_accept_invite_increments_active_driver_count() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver_a = Address::generate(&env);
let driver_b = Address::generate(&env);

client.add_driver_to_fleet(&fleet_id, &driver_a);
client.add_driver_to_fleet(&fleet_id, &driver_b);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_a);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_b);

client.accept_fleet_invite(&fleet_id, &driver_a);
let profile = client.get_fleet(&fleet_id);
Expand All @@ -236,10 +265,10 @@ fn test_accept_invite_increments_active_driver_count() {
#[test]
fn test_accept_invite_emits_event() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

let events = env.events().all();
Expand All @@ -264,10 +293,10 @@ fn test_accept_invite_without_prior_invite_panics() {
#[should_panic(expected = "Error(Contract, #7)")]
fn test_accept_invite_twice_panics() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);
// Accepting again must panic.
client.accept_fleet_invite(&fleet_id, &driver);
Expand All @@ -281,7 +310,7 @@ fn test_remove_active_driver_decrements_count() {
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

// Owner removes the driver.
Expand All @@ -300,7 +329,7 @@ fn test_remove_pending_driver_does_not_affect_active_count() {
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
// Driver has NOT accepted — still Pending.

client.remove_driver_from_fleet(&fleet_id, &owner, &driver);
Expand All @@ -315,10 +344,10 @@ fn test_remove_pending_driver_does_not_affect_active_count() {
#[test]
fn test_driver_can_remove_themselves() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

// Driver removes themselves (caller == driver).
Expand All @@ -334,7 +363,7 @@ fn test_remove_driver_emits_event() {
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.remove_driver_from_fleet(&fleet_id, &owner, &driver);

let events = env.events().all();
Expand Down Expand Up @@ -368,10 +397,10 @@ fn test_remove_driver_not_in_fleet_panics() {
#[should_panic(expected = "Error(Contract, #3)")]
fn test_remove_driver_unauthorized_caller_panics() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);

let random_caller = Address::generate(&env);
// random_caller is neither owner nor driver — must panic.
Expand All @@ -388,7 +417,7 @@ fn test_roster_full_lifecycle_add_accept_remove() {
let driver = Address::generate(&env);

// Add: driver starts as Pending.
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
assert_eq!(
client.get_driver_fleet_status(&fleet_id, &driver),
Some(DriverFleetStatus::Pending)
Expand Down Expand Up @@ -417,9 +446,9 @@ fn test_roster_multiple_drivers_independent_states() {
let driver_b = Address::generate(&env);
let driver_c = Address::generate(&env);

client.add_driver_to_fleet(&fleet_id, &driver_a);
client.add_driver_to_fleet(&fleet_id, &driver_b);
client.add_driver_to_fleet(&fleet_id, &driver_c);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_a);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_b);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_c);

// Accept only a and b.
client.accept_fleet_invite(&fleet_id, &driver_a);
Expand Down Expand Up @@ -447,10 +476,10 @@ fn test_roster_multiple_drivers_independent_states() {
#[test]
fn test_roster_driver_can_leave_voluntarily() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

// Driver removes themselves.
Expand All @@ -466,12 +495,12 @@ fn test_roster_re_invite_after_removal() {
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);
client.remove_driver_from_fleet(&fleet_id, &owner, &driver);

// Should be possible to invite the same driver again after removal.
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
assert_eq!(
client.get_driver_fleet_status(&fleet_id, &driver),
Some(DriverFleetStatus::Pending)
Expand All @@ -483,10 +512,10 @@ fn test_roster_re_invite_after_removal() {
#[test]
fn test_get_payout_address_returns_treasury_for_active_driver() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, treasury) = register_fleet(&env, &client);
let (fleet_id, owner, treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

let payout = client.get_payout_address(&driver, &fleet_id);
Expand All @@ -507,10 +536,10 @@ fn test_get_payout_address_returns_driver_when_not_in_fleet() {
#[test]
fn test_get_payout_address_returns_driver_for_pending_invite() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, _treasury) = register_fleet(&env, &client);
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
// Invite is Pending — not yet accepted.

let payout = client.get_payout_address(&driver, &fleet_id);
Expand All @@ -523,7 +552,7 @@ fn test_get_payout_address_returns_driver_after_removal() {
let (fleet_id, owner, _treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);
client.remove_driver_from_fleet(&fleet_id, &owner, &driver);

Expand All @@ -538,7 +567,7 @@ fn test_get_payout_address_treasury_updates_are_reflected() {
let (fleet_id, owner, _old_treasury) = register_fleet(&env, &client);

let driver = Address::generate(&env);
client.add_driver_to_fleet(&fleet_id, &driver);
client.add_driver_to_fleet(&owner, &fleet_id, &driver);
client.accept_fleet_invite(&fleet_id, &driver);

let new_treasury = Address::generate(&env);
Expand All @@ -551,15 +580,15 @@ fn test_get_payout_address_treasury_updates_are_reflected() {
#[test]
fn test_get_payout_address_multiple_drivers_same_fleet() {
let (env, client, _admin) = setup_test();
let (fleet_id, _owner, treasury) = register_fleet(&env, &client);
let (fleet_id, owner, treasury) = register_fleet(&env, &client);

let driver_a = Address::generate(&env);
let driver_b = Address::generate(&env);
let driver_c = Address::generate(&env);

client.add_driver_to_fleet(&fleet_id, &driver_a);
client.add_driver_to_fleet(&fleet_id, &driver_b);
client.add_driver_to_fleet(&fleet_id, &driver_c);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_a);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_b);
client.add_driver_to_fleet(&owner, &fleet_id, &driver_c);

// Only a and b accept; c stays pending.
client.accept_fleet_invite(&fleet_id, &driver_a);
Expand Down
3 changes: 3 additions & 0 deletions contracts/settlement_contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ path = "lib.rs"

[dependencies]
soroban-sdk = { workspace = true }

[dev-dependencies]
soroban-sdk = { workspace = true, features = ["testutils"] }
Loading
Loading