Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ flowchart TD
| **`iln_governance`** | `contracts/iln_governance/` | On-chain governance: proposals, voting, delegation, quorum, and admin veto |
| **`iln_distribution`** | `contracts/iln_distribution/` | Yield and incentive distribution for LPs, freelancers, and payers (linked to governance token) |
| **`reputation_bonus`** | `contracts/reputation_bonus/` | Reputation-based discount bonuses and related invoice hooks |
| **`insurance_pool`** | `contracts/insurance_pool/` | Default-protection insurance pool for LPs |
| **`iln_fuzz`** | `contracts/fuzz/` | Property-based fuzz tests against core invoice flows |
| **Integration tests** | `contracts/tests/` | Cross-contract tests with mock tokens and oracles |

Expand Down
76 changes: 63 additions & 13 deletions docs/error-codes.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Contract Error Codes

This reference documents the current `ContractError` enum used by `invoice_liquidity`.
Source of truth: [`contracts/invoice_liquidity/src/errors.rs`](../contracts/invoice_liquidity/src/errors.rs)
This reference documents all error codes returned by the ILN contracts, grouped by crate.

## `invoice_liquidity` — `ContractError`

If you change the enum, update this page at the same time so integrators can keep their error handling in sync.
Source of truth: [`contracts/invoice_liquidity/src/errors.rs`](../contracts/invoice_liquidity/src/errors.rs)

> Note: the current source assigns code `33` to two variants: `FeeOnTransferToken` and `PayerUnverified`.
> This document mirrors the enum exactly so callers can map the runtime value back to the right failure mode.
> **Note:** codes 33 is shared by two variants (`FeeOnTransferToken` and `PayerUnverified`) due to a historical renumbering. Both map to the same numeric value at runtime.

| Code | Variant | Description | Common cause | Recommended remediation |
|------|---------|-------------|--------------|--------------------------|
Expand All @@ -17,13 +17,13 @@ If you change the enum, update this page at the same time so integrators can kee
| 5 | `Unauthorized` | The caller does not have the required role or authorization for the action. | Wrong account signed the transaction, or the contract has not been configured with the expected admin/role mapping. | Verify the signing account and required role, then retry with the correct address or permissions. |
| 6 | `InvalidAmount` | The provided amount is not acceptable to the contract. | Zero, negative, or otherwise malformed payment/funding amount. | Send a positive amount that matches the invoice rules and token decimals. |
| 7 | `InvalidDiscountRate` | The discount rate is outside the allowed range. | Admin or caller supplied a rate above the contract maximum or in the wrong units. | Use the documented basis-point format and keep the value within the configured bounds. |
| 8 | `InvalidDueDate` | The due date is not valid for invoice creation or update. | Due date is in the past, malformed, or violates contract invariants. | Provide a future due date that satisfies the contracts validation rules. |
| 8 | `InvalidDueDate` | The due date is not valid for invoice creation or update. | Due date is in the past, malformed, or violates contract invariants. | Provide a future due date that satisfies the contract's validation rules. |
| 9 | `InvoiceDefaulted` | The invoice has already defaulted. | Caller tried to fund, pay, cancel, or otherwise act on an invoice that is already in default. | Use the default/appeal flows instead of settlement or funding flows. |
| 10 | `NothingToClaim` | There is no yield or claimable amount available. | LP tried to claim before yield accrued or before funds became claimable. | Wait until the invoice has generated claimable yield, then retry the claim. |
| 11 | `NotYetDefaulted` | The invoice has not reached the default threshold yet. | A default-claim or default-handling function was called too early. | Wait until the invoice is actually defaulted before using the default recovery flow. |
| 12 | `OverfundingRejected` | The funding attempt would exceed the invoices remaining amount. | LP sent more than the unpaid principal or attempted to top up beyond the cap. | Fund only the remaining unpaid amount, or read the remaining balance first. |
| 12 | `OverfundingRejected` | The funding attempt would exceed the invoice's remaining amount. | LP sent more than the unpaid principal or attempted to top up beyond the cap. | Fund only the remaining unpaid amount, or read the remaining balance first. |
| 13 | `InvoiceExpired` | The invoice has expired and cannot proceed through normal settlement. | Caller tried to fund or pay after the invoice passed its allowed lifecycle window. | Create a fresh invoice or use the appropriate default/closure flow if supported. |
| 14 | `BatchTooLarge` | The submitted batch exceeds the contracts maximum batch size. | Bulk action included too many invoices in one call. | Split the request into smaller batches and retry. |
| 14 | `BatchTooLarge` | The submitted batch exceeds the contract's maximum batch size. | Bulk action included too many invoices in one call. | Split the request into smaller batches and retry. |
| 15 | `AlreadyCancelled` | The invoice was already cancelled. | A caller retried a cancel flow or attempted another action after cancellation. | Treat the invoice as terminal and stop sending state-changing actions for it. |
| 16 | `AlreadyInitialized` | The contract was initialized more than once. | A deployment or setup script ran initialization again after state already existed. | Run initialization only once per deployment and guard scripts against duplicate setup. |
| 17 | `AlreadyAppealed` | An appeal already exists for this invoice. | The payer submitted a second appeal for the same defaulted invoice. | Check whether an appeal is already open before creating another one. |
Expand All @@ -36,20 +36,70 @@ If you change the enum, update this page at the same time so integrators can kee
| 24 | `NotDisputed` | The invoice is not in a disputed state. | A dispute-resolution function was called before a dispute existed. | Open a dispute first, or call the correct function for the current invoice state. |
| 25 | `InvoiceDisputed` | The invoice is under dispute and cannot proceed through normal settlement. | A user attempted to fund, pay, or finalize an invoice while a dispute is active. | Resolve or dismiss the dispute before retrying normal invoice actions. |
| 26 | `ContractPaused` | The contract is currently paused. | An admin paused the protocol for maintenance, incident response, or governance action. | Wait until the contract is unpaused, or ask the admin/governance process to resume it. |
| 27 | `DueDateTooSoon` | The due date is earlier than the minimum allowed horizon. | Invoice due date was set too close to the current ledger time. | Choose a later due date that satisfies the contracts minimum lead time. |
| 28 | `DueDateTooFar` | The due date is later than the maximum allowed horizon. | Invoice due date was set too far in the future. | Reduce the due date to fall within the contracts configured maximum range. |
| 27 | `DueDateTooSoon` | The due date is earlier than the minimum allowed horizon. | Invoice due date was set too close to the current ledger time. | Choose a later due date that satisfies the contract's minimum lead time. |
| 28 | `DueDateTooFar` | The due date is later than the maximum allowed horizon. | Invoice due date was set too far in the future. | Reduce the due date to fall within the contract's configured maximum range. |
| 29 | `SelfInvoice` | The payer and invoice creator are the same address. | A caller attempted to create an invoice against themselves. | Use distinct payer and submitter addresses, or fix the invoice data before resubmitting. |
| 30 | `OverpaymentRejected` | The payment amount exceeds the remaining amount due. | Payer attempted to pay more than the invoice balance. | Pay exactly the remaining amount or query the outstanding balance first. |
| 31 | `PayerReputationTooLow` | The payers reputation is below the configured minimum threshold. | Reputation gate is enabled and the payer score does not meet the contract requirement. | Improve the payers reputation score, or adjust the minimum threshold through the approved governance/admin path. |
| 31 | `PayerReputationTooLow` | The payer's reputation is below the configured minimum threshold. | Reputation gate is enabled and the payer score does not meet the contract requirement. | Improve the payer's reputation score, or adjust the minimum threshold through the approved governance/admin path. |
| 32 | `ArithmeticOverflow` | A checked arithmetic operation overflowed. | Large amounts, counters, or computed values exceeded `u64`/`i128` limits during processing. | Re-check inputs for unreasonable values and investigate the caller data or contract math path. |
| 33 | `FeeOnTransferToken` | The token charges a transfer fee, so the received amount differs from the amount sent. | An unsupported fee-on-transfer asset was added or used for settlement. | Use a standard token that transfers the full amount, or remove the fee-on-transfer asset from configuration. |
| 33 | `PayerUnverified` | The oracle did not verify the payer when verification was required. | Oracle verification is enabled, but the payer is not present or not verified in the oracle response. | Use a verified payer account, or disable payer verification if that policy is not required. |
| 34 | `OracleDataStale` | The oracle response is older than the configured freshness window. | The payer-verification oracle data has exceeded `max_oracle_age_ledgers`. | Refresh oracle data and retry, or increase the freshness window only if that tradeoff is acceptable. |
| 35 | `InvoiceNftAlreadyExists` | An NFT has already been minted for this invoice. | Attempted to mint a duplicate NFT for an invoice that already has one. | Check `invoice_nft_exists` before minting. |
| 36 | `InvoiceNftNotFound` | No NFT exists for the requested invoice. | Querying or transferring an NFT that was never minted (or was burned). | Verify the invoice was funded and the NFT was minted before querying. |
| 37 | `InvoiceNftNotOwned` | The caller is not the current owner of this NFT. | A non-owner attempted to transfer or burn an NFT. | Verify ownership via `invoice_nft_owner` before attempting transfer or burn. |
| 38 | `AmountTooSmall` | The invoice amount is below the configurable minimum threshold. | The submitted invoice amount is too low to be economically viable. | Increase the invoice amount to meet the minimum required by the contract configuration. |

---

## `insurance_pool` — `InsuranceError`

Source of truth: [`contracts/insurance_pool/src/lib.rs`](../contracts/insurance_pool/src/lib.rs)

| Code | Variant | Description | Common cause | Recommended remediation |
|------|---------|-------------|--------------|--------------------------|
| 1 | `NotInitialized` | Contract has not been initialised with an admin. | `initialize()` was never called, or was called and failed. | Call `initialize()` with a valid admin address and positive coverage cap. |
| 2 | `AlreadyClaimed` | A claim has already been processed for this invoice. | Duplicate claim attempt for the same invoice ID. | Check `is_claimed()` before filing; each invoice can only be claimed once. |
| 3 | `InvalidAmount` | Premium / coverage amount must be positive. | Zero or negative amount passed to `deposit_premium` or `initialize`. | Send a positive stroop amount. |
| 4 | `PoolEmpty` | Pool has no balance available to pay a claim. | All premiums have been paid out or the pool was never funded. | LPs must deposit premiums before claims can be paid. |
| 5 | `AlreadyInitialized` | Contract is already initialised. | `initialize()` called more than once. | Initialisation is one-shot; redeploy if a fresh pool is needed. |
| 6 | `NoPendingProposal` | No pending proposal exists for the requested admin action. | `execute_coverage_change` / `execute_admin_transfer` / `cancel_*` called with no queued proposal. | Queue a proposal first via `propose_coverage_change` or `propose_admin_transfer`. |
| 7 | `TimelockNotExpired` | The proposal's timelock has not yet expired. | Attempted to execute a timelocked action before the 3-day delay elapsed. | Wait until `env.ledger().timestamp() >= eta` and retry. |

---

## `iln_governance` — `GovernanceError`

Source of truth: [`contracts/iln_governance/src/lib.rs`](../contracts/iln_governance/src/lib.rs)

| Code | Variant | Description | Common cause | Recommended remediation |
|------|---------|-------------|--------------|--------------------------|
| 1 | `AlreadyInitialized` | The contract was initialized more than once. | Deployment script ran initialization twice. | Run initialization only once per deployment. |
| 2 | `ProposalNotFound` | The specified proposal ID does not exist. | Invalid proposal ID or the proposal was never created. | Verify the proposal ID via `get_proposal`. |
| 3 | `VotingEnded` | Voting period for this proposal has ended. | Attempted to cast a vote after the voting window closed. | Vote within the configured voting period. |
| 4 | `ProposalNotActive` | The proposal is not in the `Active` state required by this action. | Action requires an active proposal (e.g. voting) but the proposal is in a different state. | Check proposal status before acting. |
| 5 | `NoVotingPower` | The voter has no governance token balance at the snapshot block. | Voter held no tokens when the proposal was created. | Acquire governance tokens before the proposal's snapshot block. |
| 6 | `AlreadyVoted` | The address has already voted on this proposal. | Double-vote attempt on the same proposal. | Each address may vote once per proposal. |
| 7 | `VotingOngoing` | The proposal's voting period is still in progress. | Attempted to finalize or execute a proposal before voting ended. | Wait for the voting period to end. |
| 8 | `QuorumNotReached` | The proposal did not meet the minimum participation threshold. | Insufficient total votes relative to governance token supply. | Encourage more token holders to vote. |
| 9 | `ProposalRejected` | The proposal was rejected (more votes against than for). | More weight voted against the proposal. | Revise the proposal and resubmit. |
| 10 | `AlreadyResolved` | The proposal has already been resolved (passed/rejected/executed/vetoed). | Action on a proposal that is already in a terminal state. | No further action is possible on a resolved proposal. |
| 11 | `CannotDelegateToSelf` | Delegating to self is not allowed. | `delegate_votes` called with `to == caller`. | Delegate to a different address. |
| 12 | `DelegationCyclePrevented` | Delegation would create a cycle. | Delegating A → B → A (directly or transitively). | Break the delegation chain before delegating. |
| 13 | `TimelockNotExpired` | Execution timelock has not yet expired. | Attempted to execute a passed proposal before the governance timelock delay elapsed. | Wait for the timelock delay, then call `execute_proposal`. |
| 14 | `Unauthorized` | Caller does not have the required role. | Wrong account signed the transaction. | Verify the signing account matches the required role (admin, proposer, etc.). |
| 15 | `InvalidQuorumBps` | Invalid quorum basis points (must be 1..=10,000). | Admin set quorum outside the valid range. | Set quorum between 1 and 10,000 basis points. |
| 16 | `NotAdmin` | Caller is not the admin. | Non-admin attempted an admin-only action (e.g. `veto_proposal`). | Use the admin account. |
| 17 | `NotVetoable` | Proposal cannot be vetoed in its current status. | Admin attempted to veto a proposal that is not in `Active` status. | Veto only active proposals. |
| 18 | `VetoPowerDisabled` | Admin veto power has been disabled by governance. | Admin veto was disabled via a governance proposal. | Re-enable veto via governance before using it. |
| 19 | `InsufficientProposerBalance` | Proposer does not hold the minimum required token balance. | Proposer's token balance is below `MinProposalBalance`. | Acquire enough governance tokens to meet the proposal threshold. |

---

## Keeping This Doc Current

When you add, remove, or renumber variants in [`contracts/invoice_liquidity/src/errors.rs`](../contracts/invoice_liquidity/src/errors.rs):
When you add, remove, or renumber variants in any contract's error enum:

1. Update this table.
1. Update the relevant table above.
2. Update any client-side error mapping in SDKs or examples.
3. Keep the README link below pointing here so the reference remains easy to find.
5 changes: 4 additions & 1 deletion docs/storage-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ value — a `u64` invoice id and an `Address` can never serialize identically.
| `Claimed(u64)` | Persistent | `bool` | Whether a claim has been processed for an invoice id |
| `PendingCoverage` | Instance | `i128` | Proposed new coverage cap awaiting timelock (Issue #542) |
| `PendingAdmin` | Instance | `Address` | Proposed new admin awaiting timelock (Issue #542) |
| `TimelockEta(TimelockAction)` | Instance | `u64` | Ledger timestamp at which a pending admin action becomes executable (Issue #542) |
| `CoverageEta` | Instance | `u64` | Ledger timestamp at which pending coverage change becomes executable (Issue #542) |
| `AdminEta` | Instance | `u64` | Ledger timestamp at which pending admin transfer becomes executable (Issue #542) |

> **TTL note:** Persistent keys (`Enrolled`, `Premiums`, `Claimed`) use Soroban's default persistent TTL (min 1,000,000 / max 2,000,000 ledgers). Instance keys live for the contract's lifetime and are removed when the contract instance is deleted.

---

Expand Down
Loading