Review: the four RI architecture reports, round 3 (comments only, do not merge) - #83
Review: the four RI architecture reports, round 3 (comments only, do not merge)#83pepebndc wants to merge 1 commit into
Conversation
## What this does Adds a GitHub Actions workflow that aligns the review-only PR #83 with `main` on demand. The workflow runs only on manual trigger (`workflow_dispatch`, the **Run workflow** button in the Actions tab). When triggered, it: 1. Rebuilds `review-base/ri-reports-r3` from the `main` tip, with the four RI reports removed. 2. Rebuilds `review/ri-reports-r3` on top of it, with the reports re-added from `main`. 3. Force-pushes both branches, so PR #83 shows the current `main` content of the reports as an added-file diff. The workflow exits without changes when `review/ri-reports-r3` no longer exists, so it becomes a no-op once the review round closes and the branches are deleted. ## Why manual instead of automatic Reviewers left feedback on an outdated version of the reports because the review PR had fallen behind `main`, with no easy way to update it. This gives us a one-click update. An automatic push-triggered sync was considered and rejected: a review can take several days, and an automatic sync would change the content under the reviewer's feet mid-review ([Slack thread](https://openzeppelin.slack.com/archives/C0AENAASAUR/p1786636513166079)). With the manual trigger we sync deliberately, between review passes. ## Trade-off When a synced report line differs from the previous snapshot, inline comments on that line become outdated in PR #83. They stay visible in the conversation tab. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
6399c09 to
fa835c3
Compare
8c89ea0 to
ad35ab1
Compare
fa835c3 to
e9b941a
Compare
ad35ab1 to
3ed5b48
Compare
b117f67 to
9512a8b
Compare
e016867 to
6736262
Compare
9512a8b to
f4a6cce
Compare
| `minCollateralRatio`, and a position that later falls under | ||
| `liquidationRatio` becomes liquidatable. The surplus is the buffer that | ||
| keeps the debt fully covered through collateral price movement. | ||
| - **Permissioned.** Every participant acts under a verified identity: |
There was a problem hiding this comment.
| - **Permissioned.** Every participant acts under a verified identity: | |
| - **Permissioned.** Every party acts under a verified identity: |
|
|
||
| State changes by archive-and-recreate rather than in-place mutation, with every signatory co-authorizing the transition (Daml's propose-and-accept pattern). That is why the design resolves the `Vault`, `PriceOracle`, `PauseState`, and the trusted-attester and trusted-issuer registries by **contract key** (reintroduced in [Canton 3.5.1+](https://github.com/digital-asset/canton/releases/tag/v3.5.1)): a key is the identity that survives each recreate. Keys are not unique - the platform accepts two contracts sharing one - so uniqueness stays an application obligation. The vault creation should perform checks against duplicate positions. | ||
|
|
||
| Keys are the design target, not what runs today. The experiment code sits on the workspace's pinned SDK baseline and is keyless, so each choice takes a caller-supplied registry contract id and asserts it shares the factory's admin. By-key resolution lands with the 3.5.1+ SDK migration. |
There was a problem hiding this comment.
I don't understand why contract keys aren't supported. Contract key support is on Dev/Test/MainNet. See https://docs.canton.network/shared/version-compatibility-dashboard.
There was a problem hiding this comment.
@hrischuk-da To clarify: going forward, all our implementations will use contract keys where applicable, since they are supported and the recommended practice. We will also retrofit the existing code, i.e. the current PauseState template does not use keys yet. That is what the paragraph intends to convey: our production implementations will make full use of contract keys; the code we have experimented with and referenced here as examples simply has not adopted them yet, since they were not supported when all these experiments were created.
There was a problem hiding this comment.
@hrischuk-da changed to Contract keys are supported on the network today and our production implementation will use them throughout. The experiment code referenced here predates the workspace's move to the 3.5.1+ SDK and is still keyless, so choices may taks a caller-supplied registry contract id and assert it shares the factory's admin. The experiments and the existing components (such as PauseState) will migrate to by-key resolution with the SDK upgrade..
| edges are runtime interactions, dashed edges are standing governance or | ||
| trust relationships, and keyed contracts are marked with their key. | ||
|
|
||
| The first diagram shows the actors and the lending application's own |
There was a problem hiding this comment.
Should the Custodian and Registry be shown in the first diagram?
There was a problem hiding this comment.
@hrischuk-da It would be clearer indeed, I added them.
|
|
||
| - **Vault Admin / Stablecoin Issuer (`VAULT_ADMIN`)** - underwrites the **stablecoin (debt) token**: operates the `VaultFactory`, configures `VaultParams`, the `TrustedIssuerRegistry` (accepted KYC issuers), and the `TrustedAttesterRegistry` (accepted compliance attesters), and issues the grants that authorize custodian seizure sweeps. The admin can mint the stablecoin, never the collateral, and only inside the vault choices ([section 3](#3-target-design)). The protocol gives the vault admin no path to issuing unbacked stablecoin. | ||
| - **Borrower (`BORROWER`)** - the entity locking collateral and drawing debt. Only the borrower can lock their own holdings into an allocation. To interact with the protocol, the borrower must hold a valid `KycClaim`, verified at vault creation and fetched live by each value-moving vault choice. Visibility is limited to the borrower's own vaults and the public configuration contracts. | ||
| - **Liquidator (`LIQUIDATOR`)** - a role granted via `openzeppelin-access-control-v1`. Each granted liquidator is placed in the observer set of the vaults it polices, so it can monitor the `PriceOracle` and vault solvency off-ledger from its own projection; authorized to liquidate only after the margin-call grace period has elapsed on a flagged, still-unhealthy vault, and only proportionally to the stablecoin it repays. |
There was a problem hiding this comment.
Please adjust Liquidator because it is incredibly hard to parse.
There was a problem hiding this comment.
@hrischuk-da thank you for spotting this, adjusted.
| - **Borrower (`BORROWER`)** - the entity locking collateral and drawing debt. Only the borrower can lock their own holdings into an allocation. To interact with the protocol, the borrower must hold a valid `KycClaim`, verified at vault creation and fetched live by each value-moving vault choice. Visibility is limited to the borrower's own vaults and the public configuration contracts. | ||
| - **Liquidator (`LIQUIDATOR`)** - a role granted via `openzeppelin-access-control-v1`. Each granted liquidator is placed in the observer set of the vaults it polices, so it can monitor the `PriceOracle` and vault solvency off-ledger from its own projection; authorized to liquidate only after the margin-call grace period has elapsed on a flagged, still-unhealthy vault, and only proportionally to the stablecoin it repays. | ||
| - **Oracle Operator(s) (`ORACLE_PROVIDER`)** - the implementation-defined party set that updates the `PriceOracle`, bound by the interface requirements ([section 4](#43-component-price-oracle-interface)): no single party, not even the vault admin, should be able to move or stall the published price. | ||
| - **Insurance Fund (`INSURANCE_FUND`)** - the party that collects protocol revenue: it mints the interest revenue against the vaults' `feeReceivable` records; the accumulated fund is the first absorber of recognized bad debt. |
There was a problem hiding this comment.
Is the insurance fund collecting a portion or all of the revenue?
There was a problem hiding this comment.
@hrischuk-da in this design, it's all of the revenue. Note it does not include the liquidation bonus, which is a reward for the liquidator.
| the vault's choices both signatures arrive automatically, inherited from the | ||
| `Vault`'s own signatories. | ||
|
|
||
| For roles that need to submit routinely (the insurance fund collecting fees), we envision keeping the confirmation threshold at 1, with each such role's powers bounded on-ledger. |
There was a problem hiding this comment.
The confirmation threshold is about the trust architecture. Are you alright with a malicious agent that captures the insurance fund's key being able to steal all of the funds?
I don't think efficiency is the key driver so you may want to adjust the rationale.
There was a problem hiding this comment.
@hrischuk-da good point, the insurance funds should have a higher confirmation threshold.
|
|
||
| Whatever update mechanism the **oracle operators** run ([section 4](#43-component-price-oracle-interface)), an all-of-M quorum should be deliberately avoided: a single offline member, or one whose participant node has unvetted the protocol DAR, would stall every price update until the staleness guard freezes the protocol. | ||
|
|
||
| The **pause authority** is multi-hosted so the brake is always reachable, but its confirmation threshold stays at 1: an emergency stop must be instant, and a quorum would slow it down. The price of that choice is a griefing window: a malicious pauser can freeze in-flight settlements until their deadlines lapse. This griefing is capped by the authorizer's right to reclaim the allocated funds after the expiration deadline. The pause brings the additional risk of not being solvency-neutral: it freezes liquidation while collateral keeps repricing, an exposure tracked in [section 7](#7-open-design-questions). |
There was a problem hiding this comment.
I don't know why but when I see "griefing window" I read "grieving widow". :)
There was a problem hiding this comment.
@hrischuk-da fair point :) to be fair, griefing usually comes together with grieving, by someone at least.
There was a problem hiding this comment.
This comment can be resolved.
| Taking each element of the codeblock in turn: | ||
|
|
||
| - **Proportional seizure.** `debtRepaid` is the amount the liquidator's own exercise burns in the same transaction, never the vault's full accrued debt, so a liquidator can never take more collateral than their payment (plus bonus) buys. | ||
| - **Restorable vault (`collateralRatio > 1 + liquidationBonus`).** Repaying `x` leaves debt `accruedDebt - x` and collateral value `collateralAmount · price - x · (1 + liquidationBonus)`; `restoreAmount` is the `x` that sets their ratio to exactly `minCollateralRatio`. In this regime every repaid unit improves the ratio, so a payment below the cap partially cures, a payment at the cap fully cures, and nothing beyond it can be taken (no overshoot). The target is `minCollateralRatio`, not `liquidationRatio`, so a cured vault does not restart on the liquidation boundary. |
There was a problem hiding this comment.
This line is unreadable. Please adjust.
There was a problem hiding this comment.
@hrischuk-da noted, I adjusted it to be more clear.
|
|
||
| ### Data and State Flow | ||
|
|
||
| The diagrams below show the four vault flows: **A** collateral deposit, **B** borrow, **C** repay and close, **D** margin call and liquidation. Atomic settlement appears only in the collateral deposit; repayment and liquidation payments burn in place, and everything the protocol releases (minted stablecoin, returned or seized collateral) moves by direct transfer under the vault's joint authority in the same transaction. In each, the `Compliance gate` node stands for the compliance-attestation check and the live KYC-claim fetch ([section 3](#compliance-is-re-checked-on-every-operation)), and keyed contracts are marked with their key. |
There was a problem hiding this comment.
It isn't clear what business operations are atomic. This sentence structure is very strange.
There was a problem hiding this comment.
@hrischuk-da all of the flows are designed to be atomic. The mistake I made here is to say "atomic settlement", where I actually meant "the settlement rail"... the paragraph means to say that only the collateral deposit requires the settlement rail (i.e. create an allocation and settle), the rest can use plain transfers from CIP112.
| Factory -.->|"first deposit:<br/>create the Vault"| Vault | ||
| ``` | ||
|
|
||
| **B. Borrow (mint coupled to debt).** The borrower asks the vault for stablecoin; the vault checks compliance, reads the current price, and assesses whether the locked collateral is worth enough to cover the new debt. If so, it mints the stablecoin to the borrower and records the higher debt, all in one transaction. This flow does not need atomic settlement: the stablecoin's issuer and the borrower both already stand behind the vault, so the vault choice itself carries every signature the mint needs. |
There was a problem hiding this comment.
This flow does not need atomic settlement: the stablecoin's issuer and the borrower both already stand behind the vault, so the vault choice itself carries every signature the mint needs.
But isn't it atomic based on the prior sentence? If so, why have this sentence?
There was a problem hiding this comment.
@hrischuk-da It is atomic, I meant it does not need the settlement rail. I made adjustments for here and the above comment.
| Coin -->|"to borrower"| Borrower | ||
| ``` | ||
|
|
||
| **C. Repay and close.** The borrower pays down debt: the vault checks compliance, burns the whole payment out of the borrower's wallet (the borrower and the stablecoin issuer both already stand behind the vault, so no settlement is needed), records the lower debt, and adds the interest portion to the insurance fund's fee receivable. The insurance fund mints its accumulated fees on its own schedule, backed by that record. On close, the vault hands the remaining collateral back to the borrower in the same transaction. |
There was a problem hiding this comment.
I would think that two operations are needed for this:
- Pay down some debt.
- Close the debt fully, including calculated fees and interest.
These seem separate to me because I would think 2. has the step of the borrower asking for what repayment is needed to fully close the account.
Am I missing something?
There was a problem hiding this comment.
@hrischuk-da we have two operations indeed:
- repayment, which decreases debt.
- close, which only works if all the debt is repaid. Closing the position will return all the collateral inside. I changed the text to clarify these two steps.
I would think 2. has the step of the borrower asking for what repayment is needed to fully close the account.
As we envision it, the borrower does not need to ask how much repayment is needed - he has visibility into the vault, and the backend app/wallet can compute this for him, and initiate the payment.
| 3. **Repay and close.** `Vault_BurnStablecoin` burns the payment via [`TokenRules_Burn`](https://github.com/OpenZeppelin/canton-contracts/blob/7696749737885e25cd88422847105f890f03b00d/experiments/token/tokenCIP112-v1/daml/OpenZeppelin/TokenCIP112V1/Registry.daml#L170), reduces `debtAmount`, and increments `feeReceivable`. The insurance fund collects via `Vault_CollectFees`; `Vault_Close` winds the position down, parking any uncollected receivable in an admin-signed contract for later collection. | ||
| 4. **Margin call and liquidation.** `Vault_FlagForLiquidation` opens the grace window; once it elapses on a still-unhealthy vault, `Vault_Liquidate` names `debtRepaid` (capped by the health-restore formula), consumes a compliance attestation checking the liquidator, burns the payment, releases the proportional collateral, and recreates the residual `Vault`. | ||
|
|
||
| The sequence diagram below traces the margin-call and liquidation flow end to end: |
There was a problem hiding this comment.
This sequence diagram is a work of art.
There was a problem hiding this comment.
@hrischuk-da thank you for the kind words! Let me know if you see any other places that could benefit from a diagram.
There was a problem hiding this comment.
This comment can be resolved
| Assumptions and important notes: | ||
|
|
||
| - Between a deposit's allocate and its settle the borrower's collateral is | ||
| locked; the lock is time-bounded and the borrower always has a unilateral |
There was a problem hiding this comment.
Why is the lock time bound? Does that mean the collateral asset is returned to the borrower before the borrower has repaid?
There was a problem hiding this comment.
@hrischuk-da this lock refers to the lock on the collateral allocation that goes through the settlement rail. Once settled, the only way for the borrower to recuperate the collateral is to pay any outstanding debt and close. I changed the text so this is clear.
Please note all the changes are on a separate PR that we didn't merge yet, so you will not see them in your ongoing review, yet. Thanks Curtis!
|
|
||
| The debt paid on repay, close, or liquidation is `principal + accrued interest`; all flows burn the full payment via the registry's `TokenRules_Burn` and record the interest portion in the vault's `feeReceivable`; the insurance fund realises that revenue later by exercising `Vault_CollectFees`, which mints up to the recorded receivable. Those fees are protocol revenue, the on-ledger analogue of interest paid to the lender, and the accumulated fees are the first absorber of any liquidation shortfall. | ||
|
|
||
| Interest creates a structural liquidity gap: the protocol mints only principal, yet borrowers owe principal plus interest, so aggregate debt always exceeds circulating supply by the accrued interest. The stablecoin that pays interest must come from other borrowers' minted principal or from the insurance fund's re-minted fees re-entering circulation. |
There was a problem hiding this comment.
How is the interest paid for with the stablecoin? How does that stablecoin enter the liquidity of this app? I'm confused by this paragraph.
There was a problem hiding this comment.
Hey @hrischuk-da! In the current design, the protocol mints only principal, however borrowers owe principal plus interest. This means that aggregate debt always exceeds the protocol-minted supply by the accrued interest. A borrower sources the interest portion from stablecoin already in circulation: other borrowers' spent principal, or fees the insurance fund collected and re-spent.
I am not entirely sure if this is an economically-sound model, hence left it as an open-question for the implementation phase. I am also rewriting the above paragraph to clarify.
There was a problem hiding this comment.
This seems very strange. Do you know of any lending platforms that behave like this in production?
This seems unstable and will have startup issues.
Can't a borrower can transfer in stablecoin to cover their interest?
| ### 2.2 Privacy and Visibility | ||
|
|
||
| Target visibility per record. Every record belongs to the wTOK registry | ||
| unless the row says otherwise. A party outside a row sees that record only |
There was a problem hiding this comment.
What is this row thing?
There was a problem hiding this comment.
Sorry, will update to make it clear that this is referring to the rows of the table right below the paragraph.
| unless the row says otherwise. A party outside a row sees that record only | ||
| transiently, when a transaction it witnesses divulges it. | ||
|
|
||
| | Record | Signatories | Observers | |
There was a problem hiding this comment.
is a record a contract? If so, replace everywhere please?
There was a problem hiding this comment.
Yes, I missed this when I was aligning the AI-generated terminology. Will update the whole document where appropriate to use "contract"
| membership is therefore a privacy decision as well as a compliance one. | ||
| - **The Custodian sees nothing until a seizure.** The seizure mark carries the | ||
| custodian destination as a data field and not as an observer entry. | ||
| - **A gate makes the party that runs it a stakeholder.** A fetch needs |
There was a problem hiding this comment.
is that gate or gateway?
This paragraph is confusing and needs clarification please.
There was a problem hiding this comment.
"gate" sometimes refers to "checks" (e.g. "D1 gate"). I agree it can seem confusing, will update to avoid using it unnecessarily. Will make the paragraph clearer.
| ([section 6](#6-open-design-questions)). The submitting relayer still | ||
| witnesses the credential transiently, because a fetch divulges to whoever | ||
| witnesses the exercise. | ||
| - **Settlement outcomes arrive as events, not as a queryable record.** The event |
There was a problem hiding this comment.
This is confusing because the terminology is non-starndard:
- update or continuation stream
- not sure what the 'host' is
- what 'exercise node'
There was a problem hiding this comment.
Fair points, these really are non-standard.
- "transfer-events stream" referred to Ledger API's update stream
- "host" is definitely very ambiguous, it referred to the
EventLogcontract - "exercise node" referred to the transaction node in which
EventLog_HoldingsChangewas invoked (i.e. exercised).
There was a problem hiding this comment.
Found other expressions that have equivalent Canton-native counterparts, updating them too
| | Role | Target posture | Why | | ||
| |---|---|---| | ||
| | Attesters | Several independent parties in the attester registry, threshold N-of-M, never all-of-M | One unavailable or unvetted attester must not halt the rail, and one malicious attester must not mint | | ||
| | Bridge relayer | Multi-hosted on several participants, confirmation threshold 1 | It holds no minting trust and is the most submission-heavy role in the design. Integrity comes from the attester split, and relay should ultimately be permissionless, so no single party gates liveness | |
There was a problem hiding this comment.
The Bridge relayer would need to manage the dedup of requests given this approach. How is this considered?
There was a problem hiding this comment.
If you're referring to the case where multiple relayer participants submit the same lock attestation, this is an off-ledger concern. The off-ledger relayer backend should have a way to partition the locks or elect a "leader" or something else.
I agree that the document should say something about this case, will add it.
|
|
||
| **Rejected alternative: lock-and-unlock.** It pays the recipient from liquidity | ||
| held on the destination side, which adds a liquidity-provider role and an | ||
| inventory-imbalance surface that a reference rail does not need. The messaging |
There was a problem hiding this comment.
What does that last sentence add to the thought? I'm not sure.
There was a problem hiding this comment.
It says the rejected alternative (lock-and-unlock) can be implemented behind the messaging gateway without changing the gateway itself.
Arguably, the sentence here adds no new information, as this has been mentioned before, so will remove it anyway.
| A message that re-drives a credit from the external chain would need | ||
| multi-round message passing, with its own delay, cost, and failure surface. What | ||
| remains is structural and fail-closed. Command deduplication over 24 hours makes | ||
| the three relayer commands safe to resubmit after a crash, and a stall blocks |
There was a problem hiding this comment.
But it must be resubmitted to the same validator. Dedup is only for a given validator and not across the synchronizer.
|
|
||
| The inbound flow of [section 3.1](#31-inbound-credit) settles a payment | ||
| privately. This section binds that mint to the backing locked on the external | ||
| chain. That binding makes the rail a bridge. |
There was a problem hiding this comment.
I don't understand why this is here?
That binding makes the rail a bridge.
| off the same lock, which is what lets a quorum sign one message | ||
| ([section 2.3](#23-decentralization-and-trust-topology)). | ||
|
|
||
| The registry therefore records credits. A flow that stalls or lapses leaves the |
There was a problem hiding this comment.
This paragraph is not clear.
| The mint reads the credited-lock registry on-ledger, so the check holds even | ||
| when the whole attester quorum signs that second message. | ||
|
|
||
| **Reserve invariant.** Each lock attestation states the amount that the source |
There was a problem hiding this comment.
I'm sorry but I don't understand this paragraph.
| Chain->>Chain: Verify the quorum with the redemption verifier,<br/>then refund the originator | ||
| ``` | ||
|
|
||
| ### 3.2 Reserve and Lock Attestation |
There was a problem hiding this comment.
I was not able to understand this section.
| Relayer->>Redeem: Archive the claim | ||
| ``` | ||
|
|
||
| 1. **Burn on Canton.** The holder asks for redemption and names the |
There was a problem hiding this comment.
Am I right in assuming that the locked amount is released in full and there are no partial release amounts? For example, I lock 100 ETH and I cannot unlock 50 ETH but only the whole 100 ETH.
| external-chain destination. The burn destroys the wrapped holding and | ||
| produces a typed **redemption attestation** that carries three fields: | ||
|
|
||
| - the instrument the burn removed supply from; |
There was a problem hiding this comment.
Would additional information be needed for audit purposes? For example, the canton party that is having the amount released?
|
|
||
| ### 3.4 Registry Uniqueness Under Non-Unique Keys | ||
|
|
||
| The pause state, the trusted-issuer list, the credited-lock registry, and the |
There was a problem hiding this comment.
I don't understand this paragraph. This sentences is really stragne "A credited-lock registry that lacks an entry lets a lock that already credited credit again"
| instrument comes from the attested message. The caller supplies no part of the | ||
| key. | ||
|
|
||
| Uniqueness then rests on authority. A key's maintainer signs the contract, so |
There was a problem hiding this comment.
Is it possible for a malicious party to manufacture the key by intelligent guessing and use it? Is there some entropy to avoid this?
|
|
||
| | Record | Key | Maintainer | | ||
| |---|---|---| | ||
| | Credited-lock registry | The admin, and the instrument | wTOK admin | |
There was a problem hiding this comment.
Please identify which 'admin' in each row.
| An upgrade can neither add nor remove a key field, so each key carries every | ||
| scope field the rail can ever need ([section 3.7](#37-upgrade-path)). | ||
|
|
||
| **Rotation.** A new version of a record arrives through an action on the live |
| The attester set keeps its observer entry on the credited-lock registry for its | ||
| own reads. | ||
|
|
||
| **Residual.** Nothing stops a maintainer from holding two live versions of a |
There was a problem hiding this comment.
This needs to be more specific because there are several key maintainers identified.
| against | ||
| ([section 3.4](#34-registry-uniqueness-under-non-unique-keys)). The check sits | ||
| on the only path that reaches a settlement, so a settle that omits the | ||
| attestation fails. The attester registry's admin must be the wTOK admin, |
There was a problem hiding this comment.
Do you expect the wTOK admin to be a multi-hosted/decentralized party to avoid a single malicious user?
| attestation names, so a relayer cannot route a credit to an account that holds | ||
| no credential. | ||
|
|
||
| The check runs under the authority of the party that runs it, and that party has |
There was a problem hiding this comment.
Is this first sentence correct?
| | Custodian and lawful-process authority | Sweep only under a bounded mark and, past the settlement deadline, only under a lawful-process order. A colluding pair can move locked value to the preset account inside the deadline window. | | ||
| | KYC issuers | Bind a credential to the recipient and maintain expiry and revocation. The trusted-issuer list is only as strict as its most permissive issuer. | | ||
| | Pause authority | Sets the pause state for an incident, and not to grief. A malicious pause authority stalls inbound settlement until the deadlines lapse, and the senders then reclaim. | | ||
| | Gateway admin | Operates the gateway and reads the records its own checks depend on. Its authority covers the gateway transaction, so a faulty gateway admin delays inbound credits and leaves a credited lock closed. | |
There was a problem hiding this comment.
The term record is ambiguous. Please replace with the concrete data.
| | Unbacked mint | A relayer, or anyone without attester authorization, mints wTOK with no real external-chain lock. | The wTOK admin co-authorizes every mint, so a relayer cannot mint at all. Two sources of unbacked supply remain: an attester quorum that signs a lock which never happened, and the admin key, which signs every holding of its own instrument and can create one directly. | | ||
| | Fabricated redemption claim | The wTOK admin creates a redemption attestation with no burn behind it, names real lock attestations, and drains that backing on the external chain while Canton supply stays untouched. | The holder is a signatory of the attestation, so an admin-only create carries no authority and only the gateway's burn-and-create transaction produces a claim ([section 3.3](#33-outbound-redemption)). The residual is a holder that colludes, which costs that holder its own holding. | | ||
| | Replay of a used lock | A consumed message, or a second message for the same lock, is submitted again to mint twice. | One-time message consumption, and then the credited-lock registry that the mint writes as it credits. A nonce the registry already holds is rejected even if the attesters misbehave. | | ||
| | Shadowing registry duplicate | Two versions of one keyed record are live under the same key, and the submitter presents whichever suits it. The record may be a credited-lock registry, a trusted-issuer list, or an attester registry. | A key names the party that maintains it, so no other party creates a second version, and a rotation archives the version it replaces. | |
There was a problem hiding this comment.
What is this key? Is it a contract key? Is it a key that signs external transactions? This is not clear.
| The credited-lock registry serializes every inbound mint of the rail, because | ||
| each record archives and recreates that one contract. Its key scopes it to one | ||
| instrument ([section 3.4](#34-registry-uniqueness-under-non-unique-keys)), so | ||
| the rail has one shard, and that shard is the throughput ceiling. Splitting an |
5cd564d to
c82b7b0
Compare
2f413f3 to
51f8517
Compare
What this is
A review-only PR to collect a fresh round of external comments on the four OpenZeppelin Canton Reference Implementation (RI) architecture reports, as they currently stand on
main. The base branch is a temporary copy ofmainwith the reports removed, so the full text of each report appears here as an added-file diff and supports inline comments.Do not merge. The reports already live on
mainunderdocs/reference-architectures/. When this review round is done the PR will be closed and the temp branches deleted.The previous round was collected in #40.
Files under review
docs/reference-architectures/dex.md(Privacy-Preserving DEX)docs/reference-architectures/lending.md(Lending Protocol)docs/reference-architectures/cross-chain-stablecoin.md(Cross-Chain Stablecoin)docs/reference-architectures/confidential-auction.md(Confidential Auction Launchpad)How to review
Open the Files changed tab and leave inline comments directly on the report lines. General remarks are welcome as regular PR comments.