Skip to content

Latest commit

Β 

History

History
1591 lines (1294 loc) Β· 106 KB

File metadata and controls

1591 lines (1294 loc) Β· 106 KB
type Feature
title Add a clear_sme_collateral_commitment entrypoint to release recorded collateral metadata
labels type:feature, area:collateral, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN
assignees

Implement clear_sme_collateral_commitment to retire stale collateral metadata

Description

record_sme_collateral_commitment in escrow/src/lib.rs writes a metadata-only DataKey::SmeCollateralPledge and emits CollateralRecordedEvt, but there is no way to remove it. Once recorded, the commitment lingers in storage and is surfaced by get_sme_collateral_commitment forever, even after the underlying pledge is released off-chain β€” so indexers and dashboards report stale collateral on a settled or cancelled invoice.

This issue adds an SME-authorized clear_sme_collateral_commitment() that removes the pledge entry and emits a dedicated retirement event, mirroring the existing record path.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add clear_sme_collateral_commitment(env) -> () that loads the escrow via load_escrow_require_sme, asserts a commitment exists (append-only typed error NoCollateralToClear), removes DataKey::SmeCollateralPledge, and emits a new CollateralClearedEvt #[contractevent] carrying invoice_id and the prior amount.
  • Preserve the metadata-only semantics documented on record_sme_collateral_commitment: no token movement, no balance reservation.
  • Keep guard ordering consistent with ADR-002 (read-only existence check, then require_auth, then the storage remove and event).
  • Do not renumber existing EscrowError codes; append the new variant.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-clear-sme-collateral
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” clear_sme_collateral_commitment, CollateralClearedEvt, NoCollateralToClear.
    • Write comprehensive tests in: escrow/src/tests/coverage.rs β€” record then clear, clear-without-record rejection, non-SME caller rejection, event payload.
    • Add documentation: update docs/escrow-sme-collateral.md and the README entrypoint table.
    • Include NatSpec-style /// comments on the new entrypoint and event.
    • Validate security: SME-only auth, no token movement, idempotent removal.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: clear with no prior commitment, wrong caller, clear after settle/cancel.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add clear_sme_collateral_commitment entrypoint to retire collateral metadata with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Expose the configured yield-tier table through a read-only view" labels: type:feature, area:read-api, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement get_yield_tiers read view for the stored tier table

Description

init in escrow/src/lib.rs persists an optional DataKey::YieldTierTable (validated by validate_yield_tiers_table) and fund_with_commitment consumes it via effective_yield_for_commitment, but there is no public getter for the tier table. Investors deciding which committed_lock_secs to pick, and dashboards rendering the tier ladder, cannot read the on-chain tiers β€” they must reconstruct them from the EscrowInitialized event or off-chain config.

This issue adds a pure get_yield_tiers(env) -> Vec<YieldTier> read returning the stored table (empty when none was configured).

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add get_yield_tiers(env) -> Vec<YieldTier> reading DataKey::YieldTierTable, returning an empty Vec when unset (matching the init "empty tiers not stored" behavior).
  • Pure read: no auth, no state change; consistent with the other get_* views.
  • Document that the returned order matches the validated non-decreasing tier ordering enforced at init.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-get-yield-tiers
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: no tiers, single tier, multi-tier ordering, legacy instance.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add get_yield_tiers read view for the configured tier table with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a funding deadline so under-funded escrows can expire and become cancellable" labels: type:feature, area:funding, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement an optional funding deadline gating fund() and unblocking cancel

Description

The escrow in escrow/src/lib.rs has no time limit on the open (status 0) funding window: fund_impl accepts deposits indefinitely while status == 0, and cancel_funding requires the admin to act manually. There is no on-chain signal that a primary issuance has stalled, so investors' principal can sit in an open escrow with no automatic recovery trigger.

This issue adds an optional funding_deadline (ledger timestamp) configured at init: after it passes, new fund calls are rejected and the escrow is eligible for cancellation/refund recovery.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add an Option<u64> funding_deadline parameter to init, validated (0/absent β‡’ no deadline; otherwise must be > now), stored under a new DataKey::FundingDeadline.
  • In fund_impl, after the status/legal-hold checks, reject deposits when a deadline is set and now > deadline with an append-only typed error FundingDeadlinePassed.
  • Add a pure get_funding_deadline(env) -> Option<u64> view and an is_funding_expired(env) -> bool helper.
  • Preserve the funding_deadline == 0 "no deadline" semantics; do not affect already-funded (status 1) escrows.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-funding-deadline
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” DataKey::FundingDeadline, init param/validation, fund gate, views, error.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” fund before/after deadline, no-deadline default, is_funding_expired transitions using Ledger testutils.
    • Add documentation: update docs/escrow-lifecycle.md and docs/escrow-ledger-time.md.
    • Include NatSpec-style /// comments on the new param, views, and error.
    • Validate security: deadline cannot retroactively trap funded escrows; ledger-time trust model documented.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: no deadline, exactly at deadline, after deadline, funded before deadline.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add optional funding deadline gating fund and recovery with tests and docs

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add an admin entrypoint to rebind the off-chain registry reference" labels: type:feature, area:admin, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement set_registry_ref so the registry pointer can be corrected post-init

Description

init in escrow/src/lib.rs optionally stores DataKey::RegistryRef, surfaced by get_registry_ref, but the pointer is write-once at init β€” there is no entrypoint to update it. If the off-chain registry contract is redeployed or the address was set incorrectly, the escrow points at a stale registry for its entire life with no recovery short of redeploying the whole escrow.

This issue adds an admin-gated set_registry_ref(new_registry: Option<Address>) so the reference can be corrected or cleared.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add set_registry_ref(env, new_registry: Option<Address>) gated via load_escrow_require_admin; Some sets DataKey::RegistryRef, None removes it.
  • Emit a new RegistryRefUpdated #[contractevent] carrying invoice_id, the prior registry (if any), and the new value for indexers.
  • The registry is an informational pointer only; document that rebinding does not migrate or revalidate any registry-side state.
  • Keep ADR-002 guard ordering: load escrow + admin require_auth before the storage write.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-set-registry-ref
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” set_registry_ref, RegistryRefUpdated event.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” set, clear, non-admin rejection, get_registry_ref reflects update, event payload.
    • Add documentation: update docs/escrow-data-model.md and the README entrypoint table.
    • Include NatSpec-style /// comments on the entrypoint and event.
    • Validate security: admin-only, no impact on funds or status.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: set from none, overwrite existing, clear to none, non-admin caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add admin set_registry_ref entrypoint to rebind the registry pointer with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a remaining-funding-capacity read view for the open funding window" labels: type:enhancement, area:read-api, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement get_remaining_funding_capacity to surface headroom to the target

Description

Front-ends sizing a deposit must currently read InvoiceEscrow::funding_target and funded_amount separately from get_escrow/get_escrow_summary (escrow/src/lib.rs) and subtract them client-side, re-deriving the saturating semantics. There is no single on-chain view that answers "how much more can be funded before the target is reached?", and over-funding past the target is permitted, which clients frequently mishandle.

This issue adds a pure get_remaining_funding_capacity(env) -> i128 view returning max(0, funding_target - funded_amount).

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add get_remaining_funding_capacity(env) -> i128 returning funding_target.saturating_sub(funded_amount) clamped at 0 so it never goes negative when over-funded.
  • Pure read, no auth, no mutation; reuse the loaded escrow.
  • Document that this is informational only β€” fund may still accept deposits that over-fund past the target while status == 0.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-remaining-capacity-view
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” get_remaining_funding_capacity view.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” capacity at zero/partial/exact/over-funded states.
    • Add documentation: update docs/escrow-read-api.md.
    • Include NatSpec-style /// comments on the view.
    • Validate security: clamped non-negative, no mutation.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: unfunded, partially funded, exactly funded, over-funded.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add get_remaining_funding_capacity read view with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Extend EscrowSummary with collateral commitment and attestation status" labels: type:enhancement, area:read-api, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Enrich get_escrow_summary with collateral and attestation fields

Description

get_escrow_summary in escrow/src/lib.rs bundles core state (escrow, legal hold, snapshot, funder count, allowlist flag, schema version) into a single EscrowSummary host call, but it omits two metadata families that callers must fetch separately: the SME collateral commitment (get_sme_collateral_commitment) and the attestation binding (get_primary_attestation_hash / append-log length). Dashboards therefore make three extra round-trips for a complete view.

This issue extends EscrowSummary (additively) with collateral presence/amount and attestation status so one call returns the full picture.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add fields to EscrowSummary: an optional collateral commitment (Option<SmeCollateralCommitment>), whether a primary attestation hash is bound (bool), and the attestation append-log length (u32).
  • Populate them in get_escrow_summary by reusing existing getters; keep the existing fields and their order stable per the additive-key policy (ADR-007).
  • Pure read, no auth; ensure legacy instances with no collateral/attestation return the unset/zero defaults.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-summary-collateral-attestation
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: no collateral, recorded collateral, no attestation, bound + appended attestations.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: extend EscrowSummary with collateral and attestation status with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Make the dust-sweep per-call ceiling an admin-configurable parameter" labels: type:feature, area:treasury, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement a configurable dust-sweep cap overriding MAX_DUST_SWEEP_AMOUNT

Description

sweep_terminal_dust in escrow/src/lib.rs hard-caps each sweep at the compile-time constant MAX_DUST_SWEEP_AMOUNT (100_000_000 base units), rejecting larger requests with SweepAmountExceedsMax. For high-decimal tokens or large rounding residues this fixed ceiling can be too small, forcing many repeated sweeps; for low-value tokens it may be looser than desired. The cap cannot be tuned per deployment.

This issue adds an optional admin-configured override stored at init/via an admin setter, falling back to MAX_DUST_SWEEP_AMOUNT when unset.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add DataKey::MaxDustSweepOverride (i128); add an admin entrypoint set_max_dust_sweep(env, cap: i128) validated to cap > 0, gated via load_escrow_require_admin, emitting a MaxDustSweepUpdated event.
  • In sweep_terminal_dust, use the override when present, otherwise MAX_DUST_SWEEP_AMOUNT; keep the liability-floor invariant unchanged.
  • Add a get_max_dust_sweep(env) -> i128 view returning the effective cap.
  • Append any new EscrowError codes; never renumber.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-configurable-dust-cap
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” override key, set_max_dust_sweep, getter, sweep cap logic, event.
    • Write comprehensive tests in: escrow/src/tests/integration.rs β€” default cap, raised cap allows larger sweep, lowered cap rejects, non-admin setter rejection, liability floor still holds.
    • Add documentation: update docs/escrow-gas-storage-notes.md and ADR-006.
    • Include NatSpec-style /// comments on the setter, getter, and event.
    • Validate security: admin-only override, positive bound, floor invariant preserved.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: unset default, exactly at override, above override, non-admin caller, floor interaction.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add admin-configurable dust-sweep cap overriding the compile-time max with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add tests for the attestation bind and bounded append-log flow" labels: type:test, area:attestations, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Test bind_primary_attestation_hash and append_attestation_digest end to end

Description

bind_primary_attestation_hash and append_attestation_digest in escrow/src/lib.rs implement a write-once primary hash plus a bounded append-only audit chain (capped at MAX_ATTESTATION_APPEND_ENTRIES = 32), with typed errors PrimaryAttestationAlreadyBound and AttestationAppendLogCapacityReached. These admin-gated funds-adjacent provenance writes need dedicated coverage in escrow/src/tests/attestations.rs to prove write-once, capacity, indexing, and auth boundaries.

This issue adds an exhaustive attestation test suite.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Assert bind_primary_attestation_hash succeeds once and rejects a second bind with PrimaryAttestationAlreadyBound; get_primary_attestation_hash reflects the bound value.
  • Assert append_attestation_digest appends in order, increments the index in AttestationDigestAppended, and rejects the 33rd entry with AttestationAppendLogCapacityReached; get_attestation_append_log returns the full ordered vector.
  • Assert both entrypoints reject non-admin callers via mock_auths.
  • No production change unless a real gap surfaces (then file/fix separately).

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b test/contracts-attestation-flow
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only if a gap surfaces.
    • Write comprehensive tests in: escrow/src/tests/attestations.rs β€” bind, re-bind rejection, append ordering, capacity, auth.
    • Add documentation: cross-link scenarios in docs/escrow-attestations.md.
    • Include NatSpec-style /// comments on shared helpers.
    • Validate security: write-once primary, bounded log growth, admin-only.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: empty log, full log boundary, double bind, non-admin caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

test: add coverage for attestation bind and bounded append-log flow

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add tests for the SME collateral commitment record and replace path" labels: type:test, area:collateral, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Test record_sme_collateral_commitment validation and replacement semantics

Description

record_sme_collateral_commitment in escrow/src/lib.rs is a metadata-only write with non-trivial validation: positive amount (CollateralAmountNotPositive), non-empty asset symbol (CollateralAssetEmpty), monotonic recorded_at on replacement (CollateralTimestampBackwards), SME-only auth, and a CollateralRecordedEvt carrying the prior amount. This path has no dedicated coverage proving each validation branch and the replace-overwrite behavior.

This issue adds a focused collateral-commitment test suite.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Assert a first record succeeds and get_sme_collateral_commitment returns the asset/amount/timestamp; the event's prior_amount is 0.
  • Assert replacement overwrites and emits the prior amount; assert a backwards ledger timestamp is rejected with CollateralTimestampBackwards using Ledger testutils.
  • Assert rejection of zero/negative amount and empty asset symbol, and that a non-SME caller is rejected.
  • Confirm the metadata-only invariant: no token balance changes occur.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b test/contracts-collateral-commitment
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only if a gap surfaces.
    • Write comprehensive tests in: escrow/src/tests/coverage.rs β€” record, replace, validation rejections, auth, no token movement.
    • Add documentation: cross-link scenarios in docs/escrow-sme-collateral.md.
    • Include NatSpec-style /// comments on helpers.
    • Validate security: SME-only, validation completeness, metadata-only.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: zero amount, empty asset, backwards timestamp, replace, non-SME caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

test: add coverage for SME collateral commitment record and replace path

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add dual-authorization tests for the beneficiary rotation entrypoint" labels: type:test, area:beneficiary-rotation, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Test rotate_beneficiary dual SME-plus-admin authorization and state gates

Description

rotate_beneficiary in escrow/src/lib.rs is a funds-routing-critical entrypoint: it changes sme_address (the withdrawal recipient) and uniquely requires both the outgoing SME and the admin to authorize, only in pre-settlement states (status 0 or 1), with a no-op guard (NewSmeSameAsCurrent), a state guard (RotationNotOpen), and a legal-hold gate. This dual-auth path has no dedicated test asserting that a single signer is insufficient.

This issue adds a rotation test suite covering both signers, the guards, and the emitted event.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Assert rotation succeeds only when both SME and admin authorize; assert it fails with only SME, only admin, or neither (via mock_auths).
  • Assert BeneficiaryRotated carries the correct prior/new SME and that a subsequent withdraw would route to the new beneficiary.
  • Assert guards: NewSmeSameAsCurrent (no-op), RotationNotOpen (settled/withdrawn/cancelled), and LegalHoldBlocksBeneficiaryRotation while a hold is active.
  • No production change unless a guard gap surfaces.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b test/contracts-beneficiary-rotation
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only if a gap surfaces.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” dual-auth matrix, guards, event, post-rotation withdraw target.
    • Add documentation: cross-link scenarios in docs/ESCROW_BENEFICIARY_ROTATION.md.
    • Include NatSpec-style /// comments on helpers.
    • Validate security: both signers required; rotation blocked post-settlement and under hold.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: missing one signer, same-address no-op, wrong status, legal hold active.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

test: add dual-auth and guard tests for rotate_beneficiary

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add boundary tests for min-contribution floor and the per-investor and unique caps" labels: type:test, area:investor-caps, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Test the funding cap and floor boundaries enforced in fund_impl

Description

fund_impl in escrow/src/lib.rs enforces three independent limits: a per-call minimum (MinContributionFloor β‡’ FundingBelowMinContribution), a cumulative per-investor cap (MaxPerInvestorCap β‡’ InvestorContributionExceedsCap), and a distinct-funder cap (MaxUniqueInvestorsCap β‡’ UniqueInvestorCapReached), plus their init-time validation (MinContributionNotPositive, MinContributionExceedsAmount, MaxPerInvestorNotPositive, MaxUniqueInvestorsNotPositive). These boundary conditions need exhaustive coverage at the exact limit values.

This issue adds boundary tests for each floor/cap, including the interaction with follow-on deposits.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Floor: deposit below floor rejected, exactly at floor accepted, follow-on below floor still rejected (floor applies per call).
  • Per-investor cap: cumulative deposits exactly at cap accepted, one over rejected (across multiple fund calls).
  • Unique cap: distinct funders up to the cap accepted, the next new funder rejected, while follow-on deposits from existing funders still succeed.
  • Init validation: assert each init-time rejection for non-positive/over-amount configurations.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b test/contracts-caps-and-floor-boundaries
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only if a gap surfaces.
    • Write comprehensive tests in: escrow/src/tests/cap_validation.rs β€” floor, per-investor cap, unique cap, init validation boundaries.
    • Add documentation: cross-link scenarios in docs/escrow-investor-caps.md.
    • Include NatSpec-style /// comments on helpers.
    • Validate security: caps and floor are inclusive/exclusive exactly as documented.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: exact floor, exact per-investor cap, exact unique cap, follow-on deposits, init validation failures.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

test: add boundary tests for min-contribution floor and investor caps

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Convert the tiered second-deposit panic in fund_with_commitment to a typed error" labels: type:security, area:errors, stack:soroban, stack:rust, priority:high, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Harden fund_with_commitment with a typed error for follow-on tiered deposits

Description

fund_impl (reached via fund_with_commitment) in escrow/src/lib.rs still uses a raw assert! with a panic string β€” "Additional principal after a tiered first deposit must use fund(), not fund_with_commitment()" β€” when an investor with an existing contribution (prev != 0) calls the commitment path again. Every other funding guard uses the append-only EscrowError enum, and the contract's SDK contract is that callers "branch on the numeric code rather than legacy panic strings". This one assert breaks that discipline on a funding-critical path.

This issue replaces the assert with a typed error.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add an append-only EscrowError variant (e.g. TieredSecondDepositNotAllowed); never renumber existing codes.
  • Replace the assert!(prev == 0, ...) in the tiered branch with ensure(&env, prev == 0, EscrowError::TieredSecondDepositNotAllowed).
  • Preserve exact behavior and guard ordering β€” only the revert type changes; fund() follow-on deposits remain unaffected.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-tiered-second-deposit-typed-error
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” new error variant and ensure call.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” assert the typed error via try_fund_with_commitment after a prior deposit; assert fund() follow-on still works.
    • Add documentation: update docs/escrow-error-messages.md and ADR-005.
    • Include NatSpec-style /// comments on the new variant.
    • Validate security: identical revert condition, stable numeric codes.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: tiered first deposit then tiered second (rejected), tiered first then fund (accepted).
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: replace tiered second-deposit panic with typed EscrowError in fund_with_commitment with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Bound the commitment lock so an investor claim cannot be locked past maturity" labels: type:security, area:tiered-yield, stack:soroban, stack:rust, priority:high, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Validate committed_lock_secs against settlement maturity in fund_with_commitment

Description

In fund_impl (escrow/src/lib.rs) a tiered deposit derives InvestorClaimNotBefore = now + committed_lock_secs, enforced later in claim_investor_payout via InvestorCommitmentLockNotExpired. The lock is only checked for arithmetic overflow (InvestorClaimTimeOverflow); it is not bounded relative to the escrow's maturity. A tier lock longer than the maturity window means a settled escrow (status 2) holds an investor's payout claim hostage past the point where principal is due β€” funds the investor is entitled to are unclaimable until the lock expires.

This issue rejects, at deposit time, any commitment lock that would push the claim time beyond settlement maturity.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • When committed_lock_secs > 0 and the escrow has a maturity lock (maturity > 0), reject the deposit if now + committed_lock_secs > maturity with a new append-only EscrowError (e.g. CommitmentLockExceedsMaturity).
  • Preserve the committed_lock_secs == 0 (no lock) and maturity == 0 (no maturity lock) semantics β€” only constrain when both are set.
  • Keep the existing InvestorClaimTimeOverflow overflow guard; this is an additional, narrower bound.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-commitment-lock-bound
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” bound check in the tiered branch, new error variant.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” lock within maturity accepted, lock past maturity rejected, no-maturity escrow unaffected, zero-lock unaffected, using Ledger testutils.
    • Add documentation: update ADR-005 and docs/escrow-legal-hold.md cross-reference for claim timing.
    • Include NatSpec-style /// comments on the new bound and error.
    • Validate security: no payout can be locked beyond the funds-due maturity.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: lock exactly at maturity, lock one second past maturity, no maturity, zero lock.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: bound commitment lock to settlement maturity in fund_with_commitment with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Document the SME collateral commitment model and its metadata-only guarantees" labels: type:docs, area:collateral, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Document record_sme_collateral_commitment semantics and limitations

Description

record_sme_collateral_commitment in escrow/src/lib.rs carries an important and easily-misread guarantee: it is metadata-only β€” it writes DataKey::SmeCollateralPledge and emits CollateralRecordedEvt but does not transfer tokens, reserve balances, verify custody, create an on-chain encumbrance, or block any flow. Misreading this as an enforced lien is a material risk for integrators. The existing docs/escrow-sme-collateral.md should be made authoritative and code-accurate.

This issue produces a complete, code-accurate collateral-commitment document.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Document the SME-only auth, the validation rules (positive amount, non-empty asset symbol, monotonic recorded_at on replace), and replacement semantics with the prior_amount event field.
  • Prominently state the metadata-only limitations and contrast with the on-chain custody flows so integrators do not treat it as an enforced encumbrance.
  • Reference the SmeCollateralCommitment struct fields and the CollateralRecordedEvt topic/payload.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b docs/contracts-collateral-model
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only rustdoc clarifications if the inline comment drifts from docs.
    • Write comprehensive tests in: escrow/src/tests/coverage.rs β€” a test asserting no token-balance change accompanies a record, anchoring the metadata-only claim.
    • Add documentation: rewrite/expand docs/escrow-sme-collateral.md; cross-link from README.md.
    • Include NatSpec-style /// comments where clarified.
    • Validate security: documented behavior matches enforced rules.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: record with/without replacement, asset symbol formatting, anchoring no-balance-change test.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

docs: document SME collateral commitment metadata-only model with anchoring test

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Document the beneficiary rotation flow and its dual-authorization requirement" labels: type:docs, area:beneficiary-rotation, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Document rotate_beneficiary dual-auth, state gates, and disbursement impact

Description

rotate_beneficiary in escrow/src/lib.rs changes the SME withdrawal recipient and is the only entrypoint requiring both the outgoing SME and the admin to sign, restricted to pre-settlement states (status 0/1), with a no-op guard and a legal-hold gate. Because it redirects where funded principal is eventually disbursed, its authorization model and timing constraints need a precise, code-accurate operator-facing document; docs/ESCROW_BENEFICIARY_ROTATION.md should be the authoritative reference.

This issue produces a complete rotation document.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Document the dual SME+admin require_auth requirement, why both are needed, and the exact guard ordering (legal hold, status, no-op, dual auth).
  • Document the allowed states (open/funded only) and the rejection codes RotationNotOpen, NewSmeSameAsCurrent, LegalHoldBlocksBeneficiaryRotation.
  • Explain the downstream effect on withdraw (funds route to the new sme_address) and the BeneficiaryRotated event for indexers.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b docs/contracts-beneficiary-rotation
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” only rustdoc corrections if inline docs drift.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” a test asserting post-rotation withdrawal target matches the new SME, anchoring the doc.
    • Add documentation: rewrite/expand docs/ESCROW_BENEFICIARY_ROTATION.md; reconcile with ADR-002.
    • Include NatSpec-style /// comments where clarified.
    • Validate security: documented dual-auth matches enforced auth.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: rotation in open vs funded, blocked post-settlement, hold active, post-rotation withdraw target.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

docs: document beneficiary rotation dual-auth flow with anchoring test

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Extract the repeated legal-hold and terminal-status gate checks into shared guard helpers" labels: type:refactor, area:guards, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Refactor duplicated legal-hold and status-gate checks into named helpers

Description

The legal-hold gate ensure(&env, !Self::legal_hold_active(&env), EscrowError::LegalHoldBlocks*) is repeated across fund_impl, settle, withdraw, claim_investor_payout, cancel_funding, rotate_beneficiary, and sweep_terminal_dust in escrow/src/lib.rs, each with a different error variant. Likewise the terminal-state check (status == 2 || status == 3 || status == 4) and the open-state check (status == 0) recur verbatim. The repeated, hand-written gates are error-prone β€” a future entrypoint can omit the hold check or mis-pick a status.

This issue extracts small named guard helpers parameterized by the error code, with no behavior change.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add private helpers, e.g. guard_not_legal_hold(&env, err: EscrowError), is_terminal_status(status: u32) -> bool, and guard_status_eq(&env, escrow_status, expected, err).
  • Replace the inline checks at each call site, preserving the exact error variant and ADR-002 guard ordering (legal-hold/status checks before require_auth where they already are).
  • No new errors, no behavior change; this is a readability/safety refactor only.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b refactor/contracts-shared-gate-helpers
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” guard helpers and call-site replacement.
    • Write comprehensive tests in: escrow/src/tests/coverage.rs β€” assert each refactored entrypoint still emits the same legal-hold/status error as before.
    • Add documentation: note the helpers in ADR-002 and docs/escrow-security-checklist.md.
    • Include NatSpec-style /// comments on the helpers.
    • Validate security: identical gate conditions and error codes at every site.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: legal-hold-blocked path per entrypoint, terminal vs non-terminal status, open-state guard.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

refactor: extract shared legal-hold and status-gate helpers with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Fix the duplicate EscrowError discriminant shared by FundingDeadlinePassed and NoPendingAdmin" labels: type:security, area:errors, stack:soroban, stack:rust, priority:high, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Resolve the colliding numeric code 163 between FundingDeadlinePassed and NoPendingAdmin

Description

In the EscrowError enum in escrow/src/lib.rs, two distinct variants are assigned the same numeric discriminant: FundingDeadlinePassed = 163 and NoPendingAdmin = 163. The contract's documented SDK contract is that callers "branch on the numeric code rather than legacy panic strings", so two unrelated failures (a funding window that has closed versus an accept_admin with no pending successor) are indistinguishable to clients β€” a real correctness and observability bug. Because the codes are meant to be append-only and stable, simply renumbering one in place would itself break the policy unless done carefully.

This issue assigns a unique, append-only code to one of the colliding variants and proves every error code is distinct.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Keep FundingDeadlinePassed at its existing slot (it is referenced from init) and move NoPendingAdmin to a fresh unused discriminant in the admin-handover range (e.g. the 80s block alongside NewAdminSameAsCurrent = 80), or vice versa, choosing whichever minimizes churn against deployed instances.
  • Add a compile-time or test-time assertion that no two EscrowError variants share a discriminant.
  • Update docs/escrow-error-messages.md to reflect the corrected, collision-free table and note the historical collision in a migration note.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-dedupe-error-code-163
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” reassign the colliding discriminant.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” assert accept_admin with no pending admin and a deadline-passed fund raise distinct, correct codes; add a uniqueness test over all variants.
    • Add documentation: update docs/escrow-error-messages.md.
    • Include NatSpec-style /// comments on the reassigned variant explaining the history.
    • Validate security: no two codes collide; client branching is unambiguous.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: deadline-passed funding, accept-admin with no proposal, full-enum uniqueness check.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: resolve duplicate EscrowError discriminant 163 with uniqueness test and docs

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Convert revoke_attestation_digest panic strings to typed EscrowError codes" labels: type:security, area:errors, stack:soroban, stack:rust, priority:high, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Harden revoke_attestation_digest with stable typed errors

Description

revoke_attestation_digest in escrow/src/lib.rs still validates with raw assert! panic strings β€” "attestation index out of range" and "attestation already revoked at index" β€” while every other attestation path (bind_primary_attestation_hash, append_attestation_digest) uses the append-only EscrowError enum with codes such as PrimaryAttestationAlreadyBound and AttestationAppendLogCapacityReached. This breaks the documented SDK contract that callers branch on numeric codes, leaving the revoke path inconsistent with its sibling entrypoints.

This issue replaces both asserts with typed errors.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add append-only EscrowError variants in the attestation range (alongside 50/51), e.g. AttestationIndexOutOfRange and AttestationAlreadyRevoked; never renumber existing codes.
  • Replace the two assert! calls in revoke_attestation_digest with ensure(&env, cond, EscrowError::...), preserving exact behavior, guard ordering, and the AttestationDigestRevoked event.
  • Keep admin authorization first; no behavior change beyond the revert type.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-revoke-attestation-typed-errors
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: index past log length, already-revoked index, valid revoke, non-admin caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: replace revoke_attestation_digest panic strings with typed EscrowError codes and tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Disambiguate the al_set event symbol shared by single and batch allowlist writes" labels: type:enhancement, area:events, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Give batch allowlist writes a distinct event topic from single writes

Description

Both set_investor_allowlisted and the batch set_investors_allowlisted in escrow/src/lib.rs publish the InvestorAllowlistChanged event with the identical symbol_short!("al_set") name. The batch path's documented invariant is that "the end state and emitted events are identical to calling set_investor_allowlisted individually", which is correct for per-investor accounting, but it leaves indexers unable to distinguish a single administrative change from a bulk operation, and provides no batch-level marker (size, common allowed flag) for audit trails.

This issue adds a dedicated batch-level event while preserving the per-investor events.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Keep emitting one InvestorAllowlistChanged (al_set) per address from the batch path so the documented per-address invariant holds.
  • Add a single additional InvestorAllowlistBatchApplied #[contractevent] (distinct symbol, e.g. al_batch) emitted once per set_investors_allowlisted call carrying invoice_id, batch size, and the common allowed flag.
  • Keep set_investor_allowlisted unchanged; this is purely additive for indexers.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-allowlist-batch-event
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: single-element batch, max-size batch, allow vs disallow flag.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add distinct batch allowlist event topic alongside per-investor events with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add an inbound funding-token transfer helper to external_calls with balance-delta checks" labels: type:feature, area:token-safety, stack:soroban, stack:rust, priority:high, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement transfer_into_escrow_with_balance_checks for inbound custody

Description

escrow/src/external_calls.rs exposes only transfer_funding_token_with_balance_checks, an outbound helper used by refund and sweep_terminal_dust to move tokens from the contract to a recipient with strict pre/post balance-delta conservation. There is no symmetric inbound helper that pulls tokens from an external payer into the contract while applying the same fee-on-transfer / rebasing / hook-token safe-fail invariants. Any future on-chain custody at fund (recording investor principal) must hand-roll the balance checks, duplicating subtle logic and risking divergence from the audited outbound path.

This issue adds a hardened inbound helper mirroring the outbound one.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add transfer_into_escrow_with_balance_checks(env, token, from, to_contract, amount) that records the contract's pre-balance, calls token::Client::transfer, then asserts the recipient delta equals amount and the sender delta is non-positive, reusing the existing typed errors (TransferAmountNotPositive, RecipientBalanceDeltaMismatch, SenderBalanceDeltaMismatch, underflow guards).
  • Do not wire it into fund in this issue (custody activation is tracked separately); deliver the audited primitive and its tests so callers can adopt it safely.
  • Keep the outbound helper untouched; share constants/error codes where applicable.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-inbound-transfer-helper
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: under-delivery, over-credit, no-op transfer, zero amount.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add inbound funding-token transfer helper with balance-delta checks and tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a paginated view enumerating revoked attestation indices" labels: type:feature, area:read-api, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement get_revoked_attestation_indices for the audit chain

Description

Attestation revocation in escrow/src/lib.rs is stored per index under DataKey::AttestationRevoked(u32) and is only queryable one index at a time via is_attestation_revoked(index). There is no way to enumerate which entries in the bounded append-log (capped at MAX_ATTESTATION_APPEND_ENTRIES = 32) have been revoked β€” an indexer or auditor must probe all 32 slots individually, with no single authoritative on-chain answer.

This issue adds a read returning the set of revoked indices for the log.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add get_revoked_attestation_indices(env) -> Vec<u32> that scans 0..get_attestation_append_log().len() and collects indices where DataKey::AttestationRevoked(i) is set.
  • Pure read, no auth, no mutation; bounded by MAX_ATTESTATION_APPEND_ENTRIES.
  • Document that indices align with get_attestation_append_log ordering and that legacy instances with no revocations return an empty Vec.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-revoked-attestation-view
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: empty log, partial revocation, full revocation.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add get_revoked_attestation_indices read view with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add an un-revoke entrypoint to reverse an erroneous attestation revocation" labels: type:feature, area:attestations, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement admin unrevoke_attestation_digest to clear a mistaken revocation

Description

revoke_attestation_digest in escrow/src/lib.rs sets DataKey::AttestationRevoked(index) permanently, and there is no way to undo it. If an admin revokes the wrong index (a fat-finger on a 0-based index), the provenance entry is marked revoked forever even though the underlying digest was legitimate, polluting the audit chain that indexers surface.

This issue adds an admin-gated unrevoke_attestation_digest(index) that clears the flag and emits a dedicated event.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add unrevoke_attestation_digest(env, index: u32) gated by admin auth; assert the index is in range and currently revoked (append-only typed errors, reusing/extending the attestation error range), then remove DataKey::AttestationRevoked(index).
  • Emit a new AttestationDigestUnrevoked #[contractevent] carrying invoice_id and index.
  • Keep ADR-002 guard ordering: range/state checks then admin require_auth consistent with the existing revoke path.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-unrevoke-attestation
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” unrevoke_attestation_digest, event, errors.
    • Write comprehensive tests in: escrow/src/tests/attestations.rs β€” revoke then unrevoke restores state, unrevoke-without-revoke rejection, out-of-range rejection, non-admin rejection.
    • Add documentation: update docs/escrow-attestations.md.
    • Include NatSpec-style /// comments on the entrypoint and event.
    • Validate security: admin-only, idempotency, bounded index.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: unrevoke of a non-revoked index, out-of-range index, double unrevoke, non-admin caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add admin unrevoke_attestation_digest entrypoint with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add an admin entrypoint to cancel a pending admin handover proposal" labels: type:feature, area:admin, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement cancel_pending_admin to withdraw an unaccepted handover

Description

The two-step admin handover in escrow/src/lib.rs writes DataKey::PendingAdmin in propose_admin and only clears it when the successor calls accept_admin. There is no way for the current admin to retract a proposal once made β€” if the wrong successor was proposed, or the handover is abandoned, the pending key lingers and the proposed address can accept at any later time, which is a standing key-rotation risk.

This issue adds an admin-gated cancel_pending_admin() that clears the pending proposal.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add cancel_pending_admin(env) gated via load_escrow_require_admin; require a pending admin to exist (reuse NoPendingAdmin), then remove DataKey::PendingAdmin.
  • Emit a new AdminProposalCancelled #[contractevent] carrying invoice_id and the cancelled pending address.
  • Keep propose_admin/accept_admin semantics unchanged; this only removes an unaccepted proposal.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-cancel-pending-admin
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” cancel_pending_admin, event.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” propose then cancel clears get_pending_admin, accept-after-cancel fails, cancel-without-proposal rejection, non-admin rejection.
    • Add documentation: update docs/OPERATOR_RUNBOOK.md and the README entrypoint table.
    • Include NatSpec-style /// comments on the entrypoint and event.
    • Validate security: admin-only, proposal cannot be accepted after cancel.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: cancel with no proposal, cancel then re-propose, accept blocked after cancel, non-admin caller.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add cancel_pending_admin entrypoint to retract an unaccepted handover with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a settled-coupon read view exposing the total pool owed at settlement" labels: type:enhancement, area:read-api, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement get_settlement_pool returning principal plus base coupon

Description

compute_investor_payout in escrow/src/lib.rs derives a per-investor gross_payout from the FundingCloseSnapshot and the documented formula settle_pool = total_principal + coupon, but the aggregate settle_pool (the total amount the SME must repay to fully satisfy all investors) is never exposed as a view. SME repayment tooling and dashboards must re-derive total_principal Γ— yield_bps / 10_000 off-chain, risking a rounding divergence from the on-chain math.

This issue adds an authoritative aggregate view of the settlement pool.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add get_settlement_pool(env) -> i128 returning total_principal + floor(total_principal Γ— yield_bps / 10_000) computed from DataKey::FundingCloseSnapshot and the escrow's base yield_bps, using the same checked_* arithmetic and ComputePayoutArithmeticOverflow guard as compute_investor_payout.
  • Return 0 when the snapshot is absent (escrow not yet funded), matching compute_investor_payout semantics.
  • Document that this uses the escrow base yield (tier-specific effective yields are per-investor and reflected only in compute_investor_payout).

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-settlement-pool-view
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” get_settlement_pool view reusing the coupon math.
    • Write comprehensive tests in: escrow/src/tests/coverage.rs β€” pool equals principal+coupon, zero before snapshot, rounding floor, overflow guard.
    • Add documentation: update docs/escrow-pro-rata.md and docs/escrow-read-api.md.
    • Include NatSpec-style /// comments on the view.
    • Validate security: pure read, identical rounding to the payout formula, overflow-safe.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: zero yield, max yield, no snapshot, large principal near overflow.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add get_settlement_pool aggregate coupon view with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Emit a dedicated event when an admin handover is proposed via the deprecated transfer_admin shim" labels: type:enhancement, area:admin, stack:soroban, stack:rust, priority:low, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Surface deprecated transfer_admin usage to indexers and operators

Description

transfer_admin in escrow/src/lib.rs is a #[deprecated] shim that silently delegates to propose_admin, so the only on-chain signal of a call is the generic AdminProposedEvent β€” indistinguishable from a direct propose_admin call. Operators migrating off the legacy one-step API have no way to detect that integrations are still calling the deprecated path, so they cannot drive the deprecation to completion.

This issue makes deprecated-shim usage observable without changing the handover behavior.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Keep transfer_admin delegating to propose_admin (no behavior change to the two-step flow).
  • Emit an additional DeprecatedTransferAdminUsed #[contractevent] carrying invoice_id and the proposed address, so indexers can flag legacy callers.
  • Update the deprecation rustdoc to mention the observability event and the intended removal path.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b enhancement/contracts-deprecated-transfer-admin-event
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” emit the new event from transfer_admin.
    • Write comprehensive tests in: escrow/src/tests/admin.rs β€” transfer_admin emits both the proposal and the deprecation event; propose_admin emits only the proposal.
    • Add documentation: update docs/EVENT_SCHEMA.md and docs/OPERATOR_RUNBOOK.md.
    • Include NatSpec-style /// comments on the new event.
    • Validate security: handover behavior unchanged; purely additive event.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: shim vs direct propose, same-address rejection still typed.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: emit deprecation event on transfer_admin shim usage with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a raise-only entrypoint to increase the unique-investor cap before funding closes" labels: type:feature, area:investor-caps, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement raise_max_unique_investors as a counterpart to the existing lower-only setter

Description

lower_max_unique_investors in escrow/src/lib.rs lets an admin only lower the MaxUniqueInvestorsCap while the escrow is open, with guards NewCapNotLower and NewCapBelowCurrentFunderCount. There is no symmetric way to raise the cap: if primary issuance attracts more demand than initially configured, the admin cannot widen participation without redeploying, even though the open state would otherwise permit it.

This issue adds a raise-only counterpart with parallel guards.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add raise_max_unique_investors(env, new_cap: u32) gated via load_escrow_require_admin, allowed only while status == 0, requiring an existing cap (NoInvestorCapConfigured) and new_cap > old_cap (new append-only typed error NewCapNotHigher).
  • Emit a new MaxUniqueInvestorsCapRaised #[contractevent] carrying invoice_id, old_cap, new_cap (parallel to MaxUniqueInvestorsCapLowered).
  • Preserve all funding-cap enforcement in fund_impl; this only widens the ceiling pre-close.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-raise-unique-cap
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” raise_max_unique_investors, event, error.
    • Write comprehensive tests in: escrow/src/tests/cap_validation.rs β€” raise accepted, equal/lower rejected, no-cap rejection, post-close rejection, more funders allowed after raise.
    • Add documentation: update docs/escrow-investor-caps.md.
    • Include NatSpec-style /// comments on the entrypoint, event, and error.
    • Validate security: admin-only, open-state-only, raise-only monotonicity.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: raise from existing cap, equal cap rejected, no configured cap, status != open.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add raise_max_unique_investors entrypoint mirroring the lower-only setter with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Allow an admin to update the funding deadline while the escrow is open" labels: type:feature, area:funding, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement update_funding_deadline for the open funding window

Description

The optional funding_deadline in escrow/src/lib.rs is validated and stored once at init (DataKey::FundingDeadline) and surfaced via get_funding_deadline/is_funding_expired, but it is write-once β€” there is no entrypoint to extend or set a deadline after deployment. An issuer who needs to extend a stalled raise, or who omitted a deadline at init, cannot adjust it without redeploying, unlike update_funding_target and update_maturity which are both adjustable while open.

This issue adds an admin-gated deadline update consistent with the other open-state setters.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add update_funding_deadline(env, new_deadline: Option<u64>) gated via load_escrow_require_admin, allowed only while status == 0; Some(d) requires d > now (reuse the init validation / FundingDeadlinePassed), None clears the deadline.
  • Emit a new FundingDeadlineUpdated #[contractevent] carrying invoice_id, prior, and new deadline.
  • Preserve is_funding_expired semantics and the "no deadline" meaning of an absent key.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-update-funding-deadline
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” update_funding_deadline, event.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” set from none, extend, clear, past-deadline rejection, post-close rejection, is_funding_expired reflects update (Ledger testutils).
    • Add documentation: update docs/escrow-lifecycle.md and docs/escrow-ledger-time.md.
    • Include NatSpec-style /// comments on the entrypoint and event.
    • Validate security: admin-only, open-state-only, deadline must be in the future.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: no prior deadline, extend, clear to none, deadline in the past, status != open.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add admin update_funding_deadline entrypoint for the open window with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a deposit-preview view that simulates a fund call without mutating state" labels: type:feature, area:read-api, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement preview_fund to report whether a deposit would be accepted

Description

A client sizing a deposit against fund in escrow/src/lib.rs must replicate every fund_impl precondition off-chain β€” status open, not legal-held, not deadline-expired, allowlist gate, min-contribution floor, per-investor cap, and unique-funder cap β€” to know whether a given (investor, amount) will succeed. This logic is duplicated in every front-end and drifts from the contract as guards evolve.

This issue adds a pure preview view that runs the same checks read-only and reports the outcome.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add preview_fund(env, investor: Address, amount: i128) -> u32 returning 0 for "would succeed" or the numeric EscrowError code that fund would raise first, evaluating the guards in the exact same order as fund_impl.
  • Pure read: no auth, no state mutation; must not call require_auth.
  • Document that this is advisory β€” fund remains the source of truth and can still revert under racing state changes.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b feature/contracts-preview-fund
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” preview_fund reusing the same guard predicates as fund_impl.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” each rejection reason returns its code, a valid deposit returns 0, ordering matches fund failures.
    • Add documentation: update docs/escrow-read-api.md.
    • Include NatSpec-style /// comments on the view and the code mapping.
    • Validate security: pure read, no mutation, ordering matches enforcement.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: below floor, over per-investor cap, unique cap reached, not allowlisted, deadline passed, legal hold, closed status, valid deposit.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add preview_fund read view reporting the first fund guard failure with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Emit a settlement-coupon snapshot event carrying the computed pool at settle()" labels: type:enhancement, area:events, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Add the realized settlement pool to the settle() event payload

Description

settle() in escrow/src/lib.rs flips status to 2 and emits EscrowSettled with funded_amount, yield_bps, and maturity, but it does not announce the computed settle_pool (principal plus coupon) that investors are collectively owed. Indexers must re-derive the coupon from funded_amount Γ— yield_bps / 10_000, duplicating the on-chain rounding and risking divergence from compute_investor_payout.

This issue adds the realized pool to the settlement event additively.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Extend EscrowSettled (append-only field) with the computed settle_pool derived from the FundingCloseSnapshot.total_principal and base yield_bps, using the same checked_* arithmetic as compute_investor_payout.
  • Keep existing topics and fields stable per the additive policy (ADR-007); compute the pool once during settle.
  • If a get_settlement_pool view exists, reuse its math to guarantee identical rounding.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b enhancement/contracts-settle-pool-event
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: zero yield, max yield, no-maturity escrow, large principal.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add realized settlement pool to EscrowSettled event payload with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Add a view exposing the contract's live funding-token balance for reconciliation" labels: type:feature, area:read-api, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Implement get_token_balance to surface on-chain custody for audits

Description

sweep_terminal_dust and refund in escrow/src/lib.rs read the contract's funding-token balance via TokenClient::balance(this) to enforce the liability floor and move funds, but there is no public view that returns this balance. Auditors reconciling on-chain custody against funded_amount and distributed_principal must construct a token client call themselves and know the funding-token address, with no single contract-level answer.

This issue adds a read returning the contract's current funding-token balance.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add get_token_balance(env) -> i128 reading the bound DataKey::FundingToken and returning TokenClient::balance(env.current_contract_address()); raise FundingTokenNotSet if uninitialized (matching the existing getter behavior).
  • Pure read, no auth, no mutation.
  • Document the reconciliation relationship: balance versus funded_amount - distributed_principal for cancelled escrows.

Suggested execution

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: zero balance, post-mint balance, balance after a sweep, uninitialized escrow.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

feat: add get_token_balance reconciliation view with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Validate the invoice amount against a maximum bound at init to prevent overflow-prone configs" labels: type:security, area:init-validation, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Bound the init amount so downstream coupon math cannot overflow

Description

init in escrow/src/lib.rs validates only amount > 0 and yield_bps in 0..=10_000, but places no upper bound on amount. Because compute_investor_payout later computes total_principal Γ— yield_bps and contribution Γ— settle_pool with i128 checked arithmetic, an extreme amount near i128::MAX makes settlement-time payout computation revert with ComputePayoutArithmeticOverflow for every investor β€” funds become un-claimable through the on-chain view, discovered only after the escrow is fully funded.

This issue rejects implausibly large amounts at init so overflow is impossible by construction.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add a MAX_INVOICE_AMOUNT constant chosen so amount Γ— 10_000 and amount Γ— settle_pool cannot overflow i128 for any valid yield; reject amount > MAX_INVOICE_AMOUNT at init with a new append-only typed error (e.g. AmountExceedsMax).
  • Document the bound's derivation relative to the compute_investor_payout formula.
  • Preserve all existing init validation and ordering; this is an additional guard.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-init-amount-bound
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” constant, validation, error.
    • Write comprehensive tests in: escrow/src/tests/init.rs β€” accept at-bound amount, reject above-bound, and assert a near-bound funded escrow's compute_investor_payout never overflows.
    • Add documentation: update docs/escrow-numeric-model.md.
    • Include NatSpec-style /// comments on the constant and error.
    • Validate security: no overflow path reachable from any valid init.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: exactly at bound, one over bound, max yield with large amount.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: bound init amount to prevent settlement payout overflow with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Bound the legal-hold clear delay at init to prevent an unclearable hold" labels: type:security, area:legal-hold, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Validate legal_hold_clear_delay against an upper bound at init

Description

init in escrow/src/lib.rs accepts an optional legal_hold_clear_delay and stores it in DataKey::LegalHoldClearDelay when > 0, with no upper-bound validation. The clear flow (request_clear_legal_hold then set_legal_hold(false)) gates on now >= clearable_at, where clearable_at = now + delay and the addition is overflow-guarded by LegalHoldClearDelayOverflow. A mistaken delay just below the overflow threshold (e.g. decades) makes a placed legal hold effectively permanent β€” funds frozen with no realistic clear path short of an admin handover.

This issue adds a sane upper bound on the clear delay at init.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Add a MAX_LEGAL_HOLD_CLEAR_DELAY_SECS constant and reject delay > MAX_LEGAL_HOLD_CLEAR_DELAY_SECS at init with a new append-only typed error (e.g. LegalHoldClearDelayTooLarge).
  • Preserve the delay == 0 / absent "immediate clear" semantics and the existing overflow guard.
  • Reference the ledger-time trust model and the legal-hold timing in the docs.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-clear-delay-bound
  • Implement changes
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: zero delay, exactly at bound, above bound.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: bound legal-hold clear delay at init to prevent an unclearable hold with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward. ++++++

type: Feature title: "Reject fund_batch entries containing duplicate investor addresses" labels: type:security, area:funding, stack:soroban, stack:rust, priority:medium, MAYBE REWARDED, GRANTFOX OSS, OFFICIAL CAMPAIGN assignees: ''

Guard fund_batch against intra-batch duplicate addresses

Description

fund_batch in escrow/src/lib.rs applies per-entry funding validation and is bounded by MAX_FUND_BATCH = 50, but it does not reject a batch that lists the same investor address twice. Two entries for one address are processed as sequential deposits, which can silently bypass a caller's intent (a single intended deposit applied twice), interact confusingly with the per-investor cap mid-batch, and complicate auditing the unique-funder count transition. Whether duplicates are valid is undocumented and untested.

This issue makes the duplicate-handling contract explicit and safe.

Requirements and context

  • Repository scope: Liquifact/Liquifact-contracts only.
  • Reject any batch containing a repeated address with a new append-only typed error (e.g. FundingBatchDuplicateInvestor) detected before any state mutation, so the batch is atomic and intent-preserving.
  • Keep MAX_FUND_BATCH bounded so the duplicate scan stays within CPU limits.
  • Document that repeat deposits for one investor must be separate single fund calls, matching the tiered second-deposit discipline.

Suggested execution

  • Fork the repo and create a branch
  • git checkout -b security/contracts-fund-batch-dedupe
  • Implement changes
    • Write code in: escrow/src/lib.rs β€” duplicate detection and error.
    • Write comprehensive tests in: escrow/src/tests/funding.rs β€” duplicate batch rejected with no partial state, unique batch succeeds, boundary at MAX_FUND_BATCH.
    • Add documentation: update docs/escrow-lifecycle.md and the README entrypoint table.
    • Include NatSpec-style /// comments on the guard and error.
    • Validate security: atomic rejection, no partial-state corruption, bounded scan.
  • Test and commit

Test and commit

  • Run cargo fmt --all -- --check, cargo build, and cargo test.
  • Cover edge cases: adjacent duplicates, non-adjacent duplicates, all-unique batch, single-element batch.
  • Include full cargo test output and a short security notes section in the PR.

Example commit message

fix: reject duplicate investor addresses in fund_batch with tests

Guidelines

  • Minimum 95 percent test coverage for impacted modules.
  • Clear, reviewer-focused documentation.
  • Timeframe: 96 hours.

Community & contribution rewards

  • πŸ’¬ Join the Liquifact community on Discord for questions, reviews, and faster merges: https://discord.gg/JrGPH4V3
  • ⭐ This is a GrantFox OSS / Official Campaign task and may be rewarded. When your PR is merged you'll be prompted to rate the project β€” if this issue and the maintainers helped you ship, we'd be grateful for a 5-star rating. Clear questions in Discord and tidy, well-tested PRs are the fastest path to a merge and a reward.