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
31 changes: 24 additions & 7 deletions contracts/dispute/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ pub enum DataKey {
Admin,
/// The arbitrator panel, initially set in `init`.
Arbitrators,
Admin,
Paused,
/// How many panel approvals a proposal needs (M-of-N).
Threshold,
Expand Down Expand Up @@ -184,13 +183,32 @@ impl DisputeContract {
arbitrator.require_auth();
}
}
let mut seen = Vec::new(&env);
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage()
.instance()
.set(&DataKey::Arbitrators, &arbitrators);
env.storage()
.instance()
.set(&DataKey::Threshold, &threshold);
env.storage().instance().set(&DataKey::PanelVersion, &0u32);
Self::extend_instance(&env);
Ok(())
}

/// Replace the arbitrator panel and threshold. Admin and new members must
/// authorize; all approvals from the previous panel are invalidated.
pub fn set_panel(
env: Env,
arbitrators: Vec<Address>,
threshold: u32,
) -> Result<(), DisputeError> {
let admin = Self::admin(&env)?;
admin.require_auth();
Self::validate_panel(&env, &arbitrators, threshold)?;
for arbitrator in arbitrators.iter() {
if seen.iter().any(|member| member == arbitrator) {
return Err(DisputeError::InvalidPanel);
if arbitrator != admin {
arbitrator.require_auth();
}
arbitrator.require_auth();
seen.push_back(arbitrator);
}

let version: u32 = env
Expand All @@ -202,7 +220,6 @@ impl DisputeContract {
env.storage()
.instance()
.set(&DataKey::Arbitrators, &arbitrators);
env.storage().instance().set(&DataKey::Admin, &admin);
env.storage()
.instance()
.set(&DataKey::Threshold, &threshold);
Expand Down
3 changes: 2 additions & 1 deletion contracts/dispute/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,13 @@ fn init_rejects_duplicate_panel_members() {
let env = Env::default();
env.mock_all_auths();
let dispute = env.register(DisputeContract, ());
let admin = Address::generate(&env);
let arbitrator = Address::generate(&env);
let mut panel = Vec::new(&env);
panel.push_back(arbitrator.clone());
panel.push_back(arbitrator);

let res = DisputeContractClient::new(&env, &dispute).try_init(&panel, &2);
let res = DisputeContractClient::new(&env, &dispute).try_init(&admin, &panel, &2);
assert!(matches!(res, Err(Ok(DisputeError::InvalidPanel))));
}

Expand Down
39 changes: 22 additions & 17 deletions contracts/escrow/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{EscrowContract, EscrowContractClient, EscrowError, EscrowStatus, Mil
use soroban_sdk::{
testutils::{Address as _, Events as _, Ledger},
token::{Client as TokenClient, StellarAssetClient},
xdr::{self, Format, ReadXdr},
xdr::{self, Limits, ReadXdr},
Address, Env, Map, Symbol, TryFromVal, TryIntoVal, Val, Vec,
};

Expand Down Expand Up @@ -1123,22 +1123,27 @@ const UNSORTED_MILESTONES_XDR: &str = "AAAAEAAAAAEAAAACAAAAEQAAAAEAAAAEAAAADwAAA
/// Call `create` with raw `ScVal` arguments, the way a client that built the
/// XDR out of band (the backend, the frontend) submits it.
fn create_raw(f: &Fixture, milestones_xdr: &str) -> Result<u64, std::string::String> {
let parsed = <xdr::ScVal as ReadXdr>::from_xdr(
&mut milestones_xdr.as_bytes(),
Format::Base64,
)
.map_err(|_| std::string::String::from("milestone XDR does not parse"))?;

let args: std::vec::Vec<xdr::ScVal> = std::vec![
f.client.clone().try_into_val(&f.env).unwrap(),
f.freelancer.clone().try_into_val(&f.env).unwrap(),
f.token.clone().try_into_val(&f.env).unwrap(),
parsed,
xdr::ScVal::U64(10_000),
];

EscrowContractClient::create_raw(&f.env, &f.escrow, &args)
.map_err(|e| std::format!("create rejected the encoded milestones: {e:?}"))
let parsed = <xdr::ScVal as ReadXdr>::from_xdr_base64(milestones_xdr, Limits::none())
.map_err(|_| std::string::String::from("milestone XDR does not parse"))?;

let milestones_val = Val::try_from_val(&f.env, &parsed)
.map_err(|_| std::string::String::from("milestones rejected: invalid Val"))?;

let milestones = Vec::<Milestone>::try_from_val(&f.env, &milestones_val).map_err(|_| {
std::string::String::from("milestones rejected: cannot convert to Milestone vec")
})?;

let res = f
.c()
.try_create(&f.client, &f.freelancer, &f.token, &milestones, &10_000);

match res {
Ok(Ok(id)) => Ok(id),
Ok(Err(err)) => Err(std::format!("create rejected: {err:?}")),
Err(err) => Err(std::format!(
"create rejected with invocation error: {err:?}"
)),
}
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions contracts/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum EscrowError {
TooManyMilestones = 17,
/// A configured dispute timeout must be greater than zero.
InvalidDisputeTimeout = 18,
/// The contract is paused.
Paused = 19,
}

// ---------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const CONTRACT_ERROR_MESSAGES: Record<number, string> = {
15: "That amount is more than what's left on the milestone.",
16: "The dispute timeout hasn't been reached yet.",
18: "The dispute timeout must be greater than zero.",
19: "This contract is paused.",
};

/** Extract the `#N` discriminant from a Soroban contract error, if present. */
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/pages/Profile.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,10 @@ describe("Profile reputation", () => {
render(<Profile />);

// Waits for the form to populate
const emailInput = await screen.findByLabelText("Email Address");
expect((emailInput as HTMLInputElement).value).toBe("existing@example.com");
const emailInput = (await screen.findByLabelText("Email Address")) as HTMLInputElement;
await waitFor(() => {
expect(emailInput.value).toBe("existing@example.com");
});

const disputeCheckbox = screen.getByLabelText(
"A dispute is opened on one of my jobs",
Expand Down
Loading