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
19 changes: 19 additions & 0 deletions contracts/contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub enum DataKey {
Token,
Milestones,
IsFunded,
Admin,
Version,
ClientApproval(u32),
FreelancerApproval(u32),
MilestoneDeadline(u32),
Expand Down Expand Up @@ -65,6 +67,7 @@ impl EscrowContract {
/// Initialize the escrow agreement with participant addresses, the payment token, and the milestones.
pub fn initialize(
env: Env,
admin: Address,
client: Address,
freelancer: Address,
arbiter: Address,
Expand All @@ -87,6 +90,8 @@ impl EscrowContract {
}
}

env.storage().instance().set(&DataKey::Admin, &admin);
env.storage().instance().set(&DataKey::Version, &1u32);
env.storage().instance().set(&DataKey::Client, &client);
env.storage().instance().set(&DataKey::Freelancer, &freelancer);
env.storage().instance().set(&DataKey::Arbiter, &arbiter);
Expand Down Expand Up @@ -522,6 +527,20 @@ impl EscrowContract {
env.storage().instance().get(&DataKey::IsFunded).unwrap_or(false)
}

/// Upgrades the contract to a new WASM executable
pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) -> Result<(), Error> {
let admin: Address = env.storage().instance().get(&DataKey::Admin).ok_or(Error::NotInitialized)?;
admin.require_auth();

env.deployer().update_current_contract_wasm(new_wasm_hash);
Ok(())
}

/// Returns the current version of the contract
pub fn version(env: Env) -> u32 {
env.storage().instance().get(&DataKey::Version).unwrap_or(0)
}

/// Check if client has approved a specific milestone
pub fn has_client_approval(env: Env, milestone_id: u32) -> bool {
env.storage().instance().get(&DataKey::ClientApproval(milestone_id)).unwrap_or(false)
Expand Down
59 changes: 47 additions & 12 deletions contracts/contracts/escrow/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct TestSetup {
contract_id: Address,
escrow_client: EscrowContractClient<'static>,
token_address: Address,
admin: Address,
client: Address,
freelancer: Address,
arbiter: Address,
Expand All @@ -25,6 +26,7 @@ fn setup_test() -> TestSetup {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);
let client = Address::generate(&env);
let freelancer = Address::generate(&env);
let arbiter = Address::generate(&env);
Expand All @@ -42,6 +44,7 @@ fn setup_test() -> TestSetup {
contract_id,
escrow_client,
token_address,
admin,
client,
freelancer,
arbiter,
Expand All @@ -56,12 +59,14 @@ fn test_happy_path() {

let milestone_1 = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone 1"),
};
let milestone_2 = Milestone {
id: 2,
deadline: 0,
amount: 200,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone 2"),
Expand All @@ -70,7 +75,7 @@ fn test_happy_path() {
let milestones = vec![&env, milestone_1, milestone_2];

// Initialize
escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);

// Verify getters
assert_eq!(escrow.get_client(), setup.client);
Expand Down Expand Up @@ -125,13 +130,14 @@ fn test_voluntary_refund() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 250,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Project Work"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();

// Freelancer triggers voluntary refund
Expand All @@ -155,13 +161,14 @@ fn test_dispute_and_resolve_to_freelancer() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 400,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "High Value Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand All @@ -188,13 +195,14 @@ fn test_dispute_and_resolve_to_client() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 400,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "High Value Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);

Expand All @@ -221,15 +229,16 @@ fn test_double_initialization_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
// Double initialize should trigger AlreadyInitialized error (error code 1)
escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
}

#[test]
Expand All @@ -241,13 +250,14 @@ fn test_zero_amount_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 0, // Zero amount
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Invalid Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
}

#[test]
Expand All @@ -259,13 +269,14 @@ fn test_unauthorized_release_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand All @@ -276,6 +287,26 @@ fn test_unauthorized_release_fails() {
escrow.release(&1, &stranger);
}

#[test]
fn test_version() {
let setup = setup_test();
let escrow = setup.escrow_client;
let env = setup.env;

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);

assert_eq!(escrow.version(), 1);
}

#[test]
#[should_panic(expected = "HostError: Error(Contract, #9)")]
fn test_release_without_both_approvals_fails() {
Expand All @@ -285,13 +316,14 @@ fn test_release_without_both_approvals_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand All @@ -308,13 +340,14 @@ fn test_double_client_approval_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand All @@ -331,13 +364,14 @@ fn test_double_freelancer_confirmation_fails() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 100,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand All @@ -354,13 +388,14 @@ fn test_dispute_clears_approvals() {

let milestone = Milestone {
id: 1,
deadline: 0,
amount: 400,
status: MilestoneStatus::Pending,
description: String::from_str(&env, "High Value Milestone"),
};
let milestones = vec![&env, milestone];

escrow.initialize(&setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.initialize(&setup.admin, &setup.client, &setup.freelancer, &setup.arbiter, &setup.token_address, &milestones);
escrow.fund();
escrow.submit_milestone(&1);
escrow.approve(&1);
Expand Down
Loading