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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion contracts/escrow/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ propchain-contracts = { path = "../lib", default-features = false }

[dev-dependencies]
ink_e2e = "5.0.0"
proptest = { version = "1.0.0", default-features = false, features = ["std"] }

[lib]
name = "propchain_escrow"
Expand All @@ -35,4 +36,4 @@ std = [
"propchain-contracts/std",
]
ink-as-dependency = []
e2e-tests = []
e2e-tests = []
4 changes: 3 additions & 1 deletion contracts/escrow/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub enum Error {
FeeRateTooHigh,
/// Fee calculation resulted in an invalid amount
InvalidFeeAmount,
/// Contract is paused
Paused,
}

impl core::fmt::Display for Error {
Expand Down Expand Up @@ -159,4 +161,4 @@ impl ContractError for Error {
fn error_category(&self) -> ErrorCategory {
ErrorCategory::Escrow
}
}
}
96 changes: 96 additions & 0 deletions contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ mod propchain_escrow {
pending_admin_rotation: Option<propchain_traits::KeyRotationRequest>,
/// Reentrancy protection guard
reentrancy_guard: ReentrancyGuard,
/// Access control
access_control: AccessControl,
/// Pending large-transfer approval requests: request_id -> LargeTransferRequest
large_transfer_requests: Mapping<u64, LargeTransferRequest>,
/// Counter for large-transfer request IDs
Expand Down Expand Up @@ -227,6 +229,18 @@ mod propchain_escrow {
new_rate: u16,
}

#[ink(event)]
pub struct Paused {
#[ink(topic)]
account: AccountId,
}

#[ink(event)]
pub struct Unpaused {
#[ink(topic)]
account: AccountId,
}

#[ink(event)]
pub struct FeeRecipientUpdated {
#[ink(topic)]
Expand Down Expand Up @@ -306,6 +320,8 @@ mod propchain_escrow {
min_high_value_threshold: u128,
tax_compliance_contract: Option<AccountId>,
) -> Self {
let mut access_control = AccessControl::default();
access_control.bootstrap(Self::env().caller());
Self {
escrows: Mapping::default(),
escrow_summaries: Mapping::default(),
Expand All @@ -324,6 +340,7 @@ mod propchain_escrow {
signer_public_keys: Mapping::default(),
pending_admin_rotation: None,
reentrancy_guard: ReentrancyGuard::new(),
access_control,
large_transfer_requests: Mapping::default(),
large_transfer_request_count: 0,
escrow_active_large_transfer: Mapping::default(),
Expand Down Expand Up @@ -351,6 +368,40 @@ mod propchain_escrow {
}
}

/// Returns true if the contract is paused, and false otherwise.
#[ink(message)]
pub fn paused(&self) -> bool {
self.paused
}

/// Pauses the contract.
///
/// Can only be called by the admin.
#[ink(message)]
pub fn pause(&mut self) -> Result<(), Error> {
self.access_control
.ensure_has_role(self.env().caller(), Role::EscrowAdmin)?;
self.paused = true;
self.env().emit_event(Paused {
account: self.env().caller(),
});
Ok(())
}

/// Unpauses the contract.
///
/// Can only be called by the admin.
#[ink(message)]
pub fn unpause(&mut self) -> Result<(), Error> {
self.access_control
.ensure_has_role(self.env().caller(), Role::EscrowAdmin)?;
self.paused = false;
self.env().emit_event(Unpaused {
account: self.env().caller(),
});
Ok(())
}

/// Create a new escrow with advanced features
#[ink(message)]
pub fn create_escrow_advanced(
Expand All @@ -362,8 +413,10 @@ mod propchain_escrow {
participants: Vec<AccountId>,
required_signatures: u8,
release_time_lock: Option<u64>,
deadline: u64,
jurisdiction: Jurisdiction,
) -> Result<u64, Error> {
self.when_not_paused()?;
let caller = self.env().caller();

// Validate configuration
Expand All @@ -390,6 +443,7 @@ mod propchain_escrow {
created_at: self.env().block_timestamp(),
completed_at: None,
release_time_lock,
deadline,
participants: participants.clone(),
jurisdiction,
total_released: 0,
Expand Down Expand Up @@ -491,6 +545,47 @@ mod propchain_escrow {
Ok(())
}

/// Refund expired escrows when no dispute was opened
#[ink(message)]
pub fn refund_if_expired(&mut self, escrow_id: u64) -> Result<(), Error> {
let mut escrow = self.escrows.get(&escrow_id).ok_or(Error::EscrowNotFound)?;

// Check if the escrow has expired
if self.env().block_timestamp() < escrow.deadline {
return Err(Error::TimeLockActive);
}

// Check if the escrow is in the correct state
if escrow.status != EscrowStatus::Active {
return Err(Error::InvalidStatus);
}

// Check if a dispute is active
if let Some(dispute) = self.disputes.get(&escrow_id) {
if !dispute.resolved {
return Err(Error::DisputeActive);
}
}

// Refund the funds to the buyer
if self.env().transfer(escrow.buyer, escrow.deposited_amount).is_err() {
return Err(Error::InsufficientFunds);
}

// Update the escrow status
escrow.status = EscrowStatus::Refunded;
self.escrows.insert(&escrow_id, &escrow);

// Emit an event
self.env().emit_event(FundsRefunded {
escrow_id,
amount: escrow.deposited_amount,
recipient: escrow.buyer,
});

Ok(())
}

/// Release funds with multi-signature approval.
///
/// If the escrow's deposited amount exceeds the large-transfer threshold,
Expand All @@ -501,6 +596,7 @@ mod propchain_escrow {
/// finalise the transfer.
#[ink(message)]
pub fn release_funds(&mut self, escrow_id: u64) -> Result<(), Error> {
self.when_not_paused()?;
non_reentrant!(self, {
let caller = self.env().caller();
let escrow = self.escrows.get(&escrow_id).ok_or(Error::EscrowNotFound)?;
Expand Down
3 changes: 2 additions & 1 deletion contracts/escrow/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct EscrowData {
pub created_at: u64,
pub completed_at: Option<u64>,
pub release_time_lock: Option<u64>,
pub deadline: u64,
pub participants: Vec<AccountId>,
pub jurisdiction: Jurisdiction,
/// Total amount already released in partial releases
Expand Down Expand Up @@ -222,4 +223,4 @@ pub struct EscrowAnalytics {
pub total_disputes_resolved: u64,
/// Number of unique participants (buyers + sellers)
pub unique_participants: u64,
}
}
3 changes: 2 additions & 1 deletion contracts/traits/src/access_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum Role {
Verifier,
PauseGuardian,
Manager,
EscrowAdmin,
}

#[allow(clippy::cast_possible_truncation)]
Expand Down Expand Up @@ -512,4 +513,4 @@ impl AccessControl {
};
self.audit_log.insert(self.audit_count, &entry);
}
}
}