Skip to content

[Certora] add strong stayNotLiquidatable spec#661

Closed
prd-carapulse[bot] wants to merge 3 commits into
mainfrom
certora/not-liquidatable-20260405-1325
Closed

[Certora] add strong stayNotLiquidatable spec#661
prd-carapulse[bot] wants to merge 3 commits into
mainfrom
certora/not-liquidatable-20260405-1325

Conversation

@prd-carapulse
Copy link
Copy Markdown
Contributor

@prd-carapulse prd-carapulse Bot commented Apr 5, 2026

Summary

  • add a new NotLiquidatable Certora spec with a strong callback-level predicate
  • model liquidatable := !liquidationLocked(id, user) && (!isHealthy(...) || postMaturity)
  • prove the tracked borrower stays not liquidatable during callbacks and after successful calls for:
    • generic non-take / non-liquidate entrypoints
    • take when the tracked borrower is the seller on the tracked obligation
    • take when the tracked borrower is not a participant on the tracked obligation (or the obligation differs)
  • include the MulDiv monotonicity axiom needed to show supplyCollateral preserves non-liquidatability
  • align the tracked-seller take proof with the runtime take postcondition by checking isHealthy directly
  • also include the sibling Healthiness.spec take-proof fix on this branch so the shared Certora matrix is green when the PR is tested independently

Test Plan

  • diff -u certora/confs/NotLiquidatable.conf <(jq < certora/confs/NotLiquidatable.conf)
  • PATH="/tmp/jre21/bin:$PATH" certoraCVLFormatter certora/specs/Healthiness.spec > /tmp/Healthiness.formatted.spec && diff -u certora/specs/Healthiness.spec /tmp/Healthiness.formatted.spec
  • PATH="/tmp/jre21/bin:$PATH" certoraCVLFormatter certora/specs/NotLiquidatable.spec > /tmp/NotLiquidatable.formatted.spec && diff -u certora/specs/NotLiquidatable.spec /tmp/NotLiquidatable.formatted.spec
  • forge build
  • PATH="/tmp/jre21/bin:/home/hermes/.svm/0.8.31:$PATH" certoraRun certora/confs/Healthiness.conf --compilation_steps_only
  • PATH="/tmp/jre21/bin:/home/hermes/.svm/0.8.31:$PATH" certoraRun certora/confs/NotLiquidatable.conf --compilation_steps_only

Stacked on #645. Parallel to #659.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f741be803f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +117 to +120
function summaryToId(Midnight.Obligation obligation, uint256 chainId, address morpho) returns (bytes32) {
bytes32 id;
if (equalsGlobalObligation(obligation) && morpho == currentContract) {
require id == globalId, "toId() is deterministic";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Constrain summaryToId with chainId

IdLib.toId in the Solidity code includes chainId in the hash, but this summary enforces determinism/injectivity without considering chainId. In any proof path where the same obligation is evaluated with different chain IDs, this collapses distinct IDs into the same globalId, which can hide aliasing/collision behaviors and make the verification result less faithful to on-chain semantics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8600b795e7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

require globalObligationCollateralLength <= 3, "too many collateralParams for the spec to handle";

Midnight.Obligation globalObligation = getGlobalObligation();
require (takeSeller(taker, offer) != globalBorrower && takeBuyer(taker, offer) != globalBorrower) || !equalsGlobalObligation(offer.obligation), "borrower is not a take participant on the tracked obligation";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add take coverage when tracked borrower is buyer

stayNotLiquidatableTakeOtherBorrower excludes both takeSeller and takeBuyer, and stayNotLiquidatableTakeSameSeller only handles the seller branch. That leaves the case where takeBuyer(taker, offer) == globalBorrower on the tracked obligation unverified, so regressions that make the buyer liquidatable during callbacks or by the end of take would not be detected by this spec.

Useful? React with 👍 / 👎.

// This variable is set to false whenever not liquidatable is violated before a callback. Initially we set it to true.
notLiquidatableBeforeCallback = true;

require globalObligationCollateralLength <= 3, "too many collateralParams for the spec to handle";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expand proof beyond three collateral params

Each rule gates execution with globalObligationCollateralLength <= 3, but the protocol accepts obligations with many more collateral params. This means the spec’s “stays not liquidatable” guarantee is only checked for a bounded subset of valid obligations, so bugs that manifest with 4+ collaterals can pass unnoticed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b56c51ca2f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

require globalObligationCollateralLength <= 3, "too many collateralParams for the spec to handle";

Midnight.Obligation globalObligation = getGlobalObligation();
require borrower != globalBorrower || !equalsGlobalObligation(obligation), "borrower or obligation differs";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add tracked-borrower liquidate coverage

stayNotLiquidatableLiquidateOtherBorrower excludes the borrower == globalBorrower && equalsGlobalObligation(obligation) path, and stayNotLiquidatable filters liquidate out entirely, so the spec never checks successful liquidation of the tracked borrower on the tracked obligation. This leaves a blind spot where regressions in liquidation guards (for example, lock handling or the liquidatable predicate) could allow liquidating a borrower that was assumed not liquidatable without any rule failing.

Useful? React with 👍 / 👎.

@MathisGD MathisGD changed the title certora: add strong stayNotLiquidatable spec [Certora] add strong stayNotLiquidatable spec Apr 6, 2026
Base automatically changed from fix/liquidate-deferred-health-check-20260404-173318 to main April 6, 2026 14:40
@MathisGD MathisGD closed this Apr 14, 2026
@MathisGD MathisGD deleted the certora/not-liquidatable-20260405-1325 branch April 14, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant