Repository navigation
Let a chain bootstrap a market that can take lending offers - #355
Merged
Merged
Conversation
bootstrap-markets only ever granted the SmartCommitmentForwarder, so every market it creates is a pools market. That is the whole reason no chain it has brought up can publish a lending offer: Alpha is trusted nowhere, and TellerV2 reverts with `Forwarder must be trusted by the market`. Arc is the live case — two markets, both pools, offers impossible. The trap is that it cannot be repaired in place. The per-market slot holds one address and setTrustedMarketForwarder overwrites it, so granting Alpha on Arc's `long` would not add offers, it would take the pools off line. Robinhood looks like the exception and is not: its global slot holds Alpha by accident, which leaves each market's own slot free. Everywhere else the two purposes need separate markets, which is what Base does — offers on 22, pools on 18. So a market config gains a `purpose`. `pools` is the default and the existing behaviour; `offers` trusts LenderCommitmentForwarderAlpha instead and has no pools deployed into it, because a pool's initialize() would revert there. Granting moved out of the pools loop into a step of its own, over every market rather than only the ones about to get pools — an offers market needs it just as much and would never have been reached. Still checked before writing, so an earlier run is repaired rather than skipped, and never rewritten when already correct: the slot holds one address, and writing the wrong one to a live market is the failure this is meant to prevent. Arc gets that market, at the same 30 days as `long` so both sides of the chain quote the same loan. No preview catches any of this. preview_pool_launch reports a market that will revert as deployable, because it reads the factory and the oracle route and never simulates the check — which cost three failed launches on Base before the cause was found. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1HkrFB3R5NcaNt5tLfXr7
The purpose split resolved both forwarders at the top of the task, which turns a chain that has no LenderCommitmentForwarderAlpha into a bootstrap that cannot run at all — five of the deployment sets here have no record of it, and none of them declare an offers market. Resolve it on demand instead, and name the missing contract if a market does ask. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1HkrFB3R5NcaNt5tLfXr7
The trust step keyed off the receipt, and a dry run creates no markets, so it printed nothing at all for a market the run was about to create. That leaves the one step this task adds — which forwarder each market gets — out of the plan the dry run exists to show, on exactly the run somebody would read before signing with the deployer key. It now reports the pending grant with the id as `<pending>`, and still throws on a chain whose configured forwarder is not deployed, which is better learned from a dry run than a real one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1HkrFB3R5NcaNt5tLfXr7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
bootstrap-marketsonly ever granted theSmartCommitmentForwarder, so every market it has ever created is a pools market.LenderCommitmentForwarderAlphais trusted nowhere it has been, which is why no chain it brought up can publish a lending offer: TellerV2 reverts withForwarder must be trusted by the market.Arc is the live case. Two markets, both pools, offers impossible.
Why it cannot be repaired in place
The per-market slot holds one address and
setTrustedMarketForwarderoverwrites it. Granting Alpha on Arc'slongwould not add offers, it would take the pools off line.Robinhood looks like the exception and is not: its global forwarder slot happens to hold Alpha, which leaves each market's own slot free for the pool forwarder. That is an accident of its deploy, not a pattern to copy. Everywhere else the two purposes need separate markets, which is what Base already does by hand: offers on market 22, pools on 18.
The change
A market config gains
purpose:poolsis the default and is exactly today's behaviour.offerstrustsLenderCommitmentForwarderAlphainstead, and has no pools deployed into it — a pool'sinitialize()callsapproveMarketForwarderand would revert there.Granting moved out of the pools loop into a step of its own, over every market rather than only the ones about to get pools. An offers market needs the grant just as much and would never have been reached. It is still checked before writing, so a market created by an earlier run is repaired rather than skipped, and never re-granted when already correct — the slot holds one address, and writing the wrong one to a live market is the failure this exists to prevent. The new step honours
--dry-run, which the old inline one did not.Arc gets an offers market at the same 30 days as
long, so both sides of the chain quote the same loan.The Alpha lookup is lazy: five of the deployment sets here (
clarity,goerli,mantle,mantle-testnet,sepolia) have no record of that contract, and resolving it unconditionally at the top of the task would fail the whole bootstrap on chains that never declared an offers market. A market that does ask for it and cannot get it throws with the reason named.Verified
TellerV2.isTrustedMarketForwarder(marketId, forwarder)is a public view, so the premise was read off the chains rather than assumed. Every market Teller Pro's on-demand lending targets was swept against both forwarders; Arc is the only one of the eight with no market that trusts Alpha, and both of its markets trust the pool forwarder — which is what makes the separate-market approach necessary rather than a repair.deployments/arc/LenderCommitmentForwarderAlpha.jsoncarries0x2ac0Cb7b93E9E8c205Ee51552298BB4249CD4136, so the lookup resolves there.Note for whoever runs it
Per the deployer's own rules, a run that writes pushes artifacts and that push has to fast-forward the artifact branch — so this needs to be on
developbefore a realBOOTSTRAP_MARKETSrun, and the deployer image repinned to the merge commit. A--dry-runis safe against any pin.🤖 Generated with Claude Code
https://claude.ai/code/session_01S1HkrFB3R5NcaNt5tLfXr7
Generated by Claude Code