diff --git a/contracts/invoice/src/invoice.rs b/contracts/invoice/src/invoice.rs index 36e7c7b..b3d61c3 100644 --- a/contracts/invoice/src/invoice.rs +++ b/contracts/invoice/src/invoice.rs @@ -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] diff --git a/contracts/invoice/src/lib.rs b/contracts/invoice/src/lib.rs index 5646c49..03f09ca 100644 --- a/contracts/invoice/src/lib.rs +++ b/contracts/invoice/src/lib.rs @@ -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] @@ -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 { @@ -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)?; @@ -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() diff --git a/contracts/invoice/src/validation.rs b/contracts/invoice/src/validation.rs index 9483350..3ffa4cc 100644 --- a/contracts/invoice/src/validation.rs +++ b/contracts/invoice/src/validation.rs @@ -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() @@ -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(()) +}