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
4 changes: 4 additions & 0 deletions contracts/invoice/src/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub enum InvoiceError {
AmountPrecision = 12,
/// Merchant nonce has already been used for a previous invoice.
DuplicateNonce = 13,
/// expires_in_seconds exceeds MAX_EXPIRY_SECONDS.
ExpiryTooLong = 14,
/// Provided metadata_hash does not match the stored hash on the invoice.
MetadataMismatch = 15,
}

#[contracttype]
Expand Down
11 changes: 10 additions & 1 deletion contracts/invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ pub use invoice::{DataKey, Invoice, InvoiceError, InvoiceStatus, MaybeAddress, M

use soroban_sdk::{contract, contractimpl, Address, Env, Vec};
use validation::{
require_admin, require_not_paused, require_positive_amount, require_usdc_precision,
require_admin, require_expiry_not_too_long, require_not_paused, require_positive_amount,
require_usdc_precision,
};

#[contract]
Expand Down Expand Up @@ -72,6 +73,7 @@ impl InvoiceContract {
if expires_in_seconds == 0 {
return Err(InvoiceError::ZeroDuration);
}
require_expiry_not_too_long(expires_in_seconds)?;

// #58: reject duplicate merchant nonce
if merchant_nonce != 0 {
Expand Down Expand Up @@ -120,6 +122,7 @@ impl InvoiceContract {
admin: Address,
id: u64,
payer: Address,
provided_metadata_hash: MaybeBytes,
) -> Result<(), InvoiceError> {
require_admin(&env, &admin)?;
require_not_paused(&env)?;
Expand All @@ -134,6 +137,12 @@ impl InvoiceContract {
return Err(InvoiceError::NotPending);
}

if provided_metadata_hash != MaybeBytes::None
&& provided_metadata_hash != invoice.metadata_hash
{
return Err(InvoiceError::MetadataMismatch);
}

// #55: apply grace window — payment is valid up to expires_at + grace_window
let grace: u64 = env
.storage()
Expand Down
11 changes: 11 additions & 0 deletions contracts/invoice/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::invoice::{DataKey, InvoiceError, USDC_FACTOR};
use soroban_sdk::{Address, Env};

/// Maximum allowed expiry duration: 5 years in seconds.
pub const MAX_EXPIRY_SECONDS: u64 = 5 * 365 * 24 * 60 * 60;

pub fn require_not_paused(env: &Env) -> Result<(), InvoiceError> {
let paused: bool = env
.storage()
Expand Down Expand Up @@ -37,3 +40,11 @@ pub fn require_usdc_precision(amount_usdc: i128, gross_usdc: i128) -> Result<(),
}
Ok(())
}

/// Reject expires_in_seconds values that exceed MAX_EXPIRY_SECONDS.
pub fn require_expiry_not_too_long(expires_in_seconds: u64) -> Result<(), InvoiceError> {
if expires_in_seconds > MAX_EXPIRY_SECONDS {
return Err(InvoiceError::ExpiryTooLong);
}
Ok(())
}
Loading