Skip to content

[CO-07] Fix off-by-one expiry in loyalty_token approve() causing immediate silent expiration #285

Description

@Leothosine

Problem

contracts/loyalty_token/src/lib.rs has inconsistent boundary conditions between approve() and get_allowance():

  • approve() rejects only if expiration_ledger < env.ledger().sequence() (strict less-than)
  • get_allowance() returns 0 when env.ledger().sequence() > d.expiration_ledger (strict greater-than)

A caller who sets expiration_ledger = current_sequence + 1 gets approval valid for only ~5 seconds (one ledger). There is no minimum validity window enforced, no maximum cap, and the semantics of expiration_ledger are undocumented - making it easy for integrators to create approvals that expire before they can be used.

Proposed Solution

  1. Add MIN_APPROVAL_VALIDITY_LEDGERS: u32 = 17280 constant (~24 hours at 5s/ledger)
  2. Change approve() to require expiration_ledger >= env.ledger().sequence() + MIN_APPROVAL_VALIDITY_LEDGERS
  3. Add a MAX_APPROVAL_VALIDITY_LEDGERS cap (~1 year) to prevent indefinite approvals
  4. Document in code comments: expiration_ledger is the LAST ledger at which the approval is valid (inclusive)

Acceptance Criteria

  • Approving with expiration_ledger < current + MIN_APPROVAL_VALIDITY_LEDGERS panics with "expiration too soon"
  • A valid approval is usable up to and including expiration_ledger
  • A valid approval returns 0 on expiration_ledger + 1
  • Unit test matrix covers: too-short expiry (panics), minimum valid expiry, at-expiry-ledger (valid), past-expiry (returns 0)
  • Code comment documents the inclusive expiry semantics

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions