diff --git a/README.md b/README.md index e0f12504..8a6e842a 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/error-codes.md b/docs/error-codes.md index 06c08772..5301b393 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -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 | |------|---------|-------------|--------------|--------------------------| @@ -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 contract’s 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 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. | +| 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 contract’s 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. | @@ -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 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. | +| 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 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. | +| 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. diff --git a/docs/storage-layout.md b/docs/storage-layout.md index dc2d1e36..031b0021 100644 --- a/docs/storage-layout.md +++ b/docs/storage-layout.md @@ -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. ---