diff --git a/packages/confidential/src/lib.rs b/packages/confidential/src/lib.rs index 42b8b367a..9c8abd36a 100644 --- a/packages/confidential/src/lib.rs +++ b/packages/confidential/src/lib.rs @@ -596,6 +596,10 @@ pub enum ConfidentialTokenError { /// boundary to keep stored state and emitted events byte-unique per /// logical value. NonCanonicalEncoding = 3514, + /// Indicates `live_until_ledger` is already in the past + /// (`live_until_ledger < ledger.sequence()`), so the delegation would be + /// expired on arrival. + InvalidLiveUntilLedger = 3515, } // ################## CONSTANTS ################## diff --git a/packages/confidential/src/storage.rs b/packages/confidential/src/storage.rs index 2f894263a..3bb12012c 100644 --- a/packages/confidential/src/storage.rs +++ b/packages/confidential/src/storage.rs @@ -846,6 +846,8 @@ pub fn confidential_transfer_from( /// /// # Errors /// +/// * [`ConfidentialTokenError::InvalidLiveUntilLedger`] - When +/// `live_until_ledger` is less than the current ledger number. /// * [`ConfidentialTokenError::AccountNotRegistered`] - When `account` or /// `spender` is not registered. /// * [`ConfidentialTokenError::DelegationAlreadyExists`] - When a delegation @@ -874,6 +876,13 @@ pub fn set_spender( payload: &SetSpenderPayload, proof: &Bytes, ) { + // Reject a delegation that would be expired on arrival before paying for + // the auditor lookup and proof verification. The bound is inclusive to + // match the liveness check (`ledger.sequence() <= live_until_ledger`). + if live_until_ledger < e.ledger().sequence() { + panic_with_error!(e, ConfidentialTokenError::InvalidLiveUntilLedger); + } + let owner = get_account(e, account); let spender_account = get_account(e, spender); let auditor = ConfidentialAuditorClient::new(e, &get_auditor(e)); diff --git a/packages/confidential/src/test.rs b/packages/confidential/src/test.rs index e5dae3de2..edd68e888 100644 --- a/packages/confidential/src/test.rs +++ b/packages/confidential/src/test.rs @@ -713,6 +713,36 @@ fn set_spender_twice_panics() { h.token.set_spender(&alice, &spender, &1_000u32, &set_spender_data(&h.e)); } +#[test] +#[should_panic(expected = "Error(Contract, #3515)")] +fn set_spender_with_past_live_until_ledger_panics() { + let h = setup(); + let alice = Address::generate(&h.e); + let spender = Address::generate(&h.e); + + h.token.register(&alice, &1u32, ®ister_data(&h.e)); + h.token.register(&spender, &1u32, ®ister_data(&h.e)); + + h.e.ledger().set_sequence_number(100); + h.token.set_spender(&alice, &spender, &99u32, &set_spender_data(&h.e)); +} + +#[test] +fn set_spender_with_current_ledger_is_live() { + let h = setup(); + let alice = Address::generate(&h.e); + let spender = Address::generate(&h.e); + + h.token.register(&alice, &1u32, ®ister_data(&h.e)); + h.token.register(&spender, &1u32, ®ister_data(&h.e)); + + h.e.ledger().set_sequence_number(100); + h.token.set_spender(&alice, &spender, &100u32, &set_spender_data(&h.e)); + + assert_eq!(h.token.get_spender_delegation(&alice, &spender).live_until_ledger, 100); + assert!(h.token.is_spender(&alice, &spender)); +} + #[test] fn revoke_spender_deletes_delegation() { let h = setup();