Allow Re-Entry for Read-Only (View) Functions #1943
Replies: 2 comments 8 replies
-
|
this looks pretty close to the older read-only invocation thread in #1454, plus stellar/rs-soroban-sdk#971. The part that seems new here is not just "mark a function read-only", it's "permit a read-only sub-invocation back into an ancestor while the parent call is still writable." That probably needs to be spelled out separately from generic |
Beta Was this translation helpful? Give feedback.
-
|
I think the concrete issue of read-only data access is better addressed by the read-only storage access proposal (https://github.com/orgs/stellar/discussions/1958), though that has its own set of issues and we're also not sure if it would make it into protocol. That said, I'm not sure I fundamentally agree with the motivation for not passing the arguments listed here:
I'm not sure why is this an issue. If you own both contracts, that's a non-issue. If some external contracts may be involved, that's still something reasonable to cover in the public API. Basically, I don't see why passing argument X is fine, but passing argument Y would not be fine. (Also, a side note: you probably want to use require_auth for your caller no matter what, it's not really a part of the 'workaround').
That's not true, is it? I'm not even sure what the 3 contracts are in question (given that the example provides just pool and cap manager contracts). If CapManager can be customized, then current_borrowed would just be a part of its interface. A developer that implements it just needs to know the interface and nothing else, which is actually better than the proposed option, where they also need to know how to get the total amount of borrowed token from the caller. That creates unnecessary tight coupling between the contracts and ultimately makes the developer experience worse, not better.
I don't understand this argument. An auditor just needs to review the public interface between the pool and cap manager, as well as both implementations. What do you mean by 'argument-passing correctness' here? Is that simply the fact that the arguments are being passed into calls? If so, then it's of course necessary to pass the correct arguments to every cross-contract call, but it's trivial to test and shouldn't incur any meaningful additional cost for the audit. On the other hand, re-entrance is actually really tricky to audit and argue about, even if it's opt-in and only read-only functions are involved. For example, the caller contract (Pool) may mutate its state before or after making a cross-contract call, and thus the callee may observe inconsistent data that wasn't meant to be observed. The sheer possibility of re-entrance increases the audit surface and the risks dramatically.
That's the only argument I really agree with, but I think the issue here is really not about the approach in general, but specifically about sub-par data-migration support in Soroban. Specifically, it's currently not possible to add a field to a As soon as data migration is supported, new fields can be added to |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
[Discussion] Allow Re-Entry for Read-Only (View) Functions
TL;DR
Soroban blocks all re-entry at the host level, including read-only queries that do not modify state. This prevents a common DeFi pattern: Contract A calls Contract B, and B needs to read back from A to independently verify a value.
We propose allowing read-only (view) functions to be called re-entrantly, enforced by the host at compile time or runtime.
The Problem
The Pattern That Broke
In DeFi protocols, it is common for two contracts to need mutual read access:
This is not a re-entrancy attack. No state is modified. It is two contracts reading each other's data in a single atomic call.
Concrete Example
Consider a lending pool and a cap manager:
get_total_borrowedis a read-only function. It reads one value from persistent storage and returns it. It modifies no state, calls no other contracts, emits no events. It is the exact Soroban equivalent of a Solidityviewfunction.Current Workaround: Trusted Caller + Passed Args
The Stellar team's suggested approach is a combination of two patterns:
require_auth— B checks that the caller is indeed the trusted contract A, so A cannot fabricate valuesThis workaround is fully functional and secure. It works for the single-caller case. The
require_authcheck ensures that the value comes from a trusted source.Why This Workaround Has Downstream Costs
The issue emerges when the protocol grows beyond one caller:
current_borrowed. One omission = one cap bypass.In contrast, if CapManager could make a re-entrant view call to Pool.get_total_borrowed:
The
require_authworkaround is correct — it just distributes responsibility across the codebase rather than centralizing it.Proposal
Core Idea
Allow contract developers to declare that certain functions are read-only and therefore safe for re-entry. The host enforces this guarantee — either at compile time (by validating the function body) or at runtime (by rejecting any call that would mutate state).
Syntax (Strawman)
If
#[view]conflicts with existing semantics, an alternative name:#[allow_reentry].Safety Rules
No state mutation: a
#[view]function must be provably read-only. The host can enforce this:env.storage().*().set(), noenv.events().publish(), noenv.invoke_contract(), noenv.prng()#[view]context with a clear errorNo cross-contract calls: a
#[view]function cannotinvoke_contract. This prevents recursive re-entry through N layers.Default is safe: functions without
#[view]retain the current behavior (no re-entry). Developers must explicitly opt in.Zero runtime cost for non-view functions: the enforcement is a compile-time check plus a flag on the function descriptor. Existing contracts see no performance regression.
What This Enables
Summary of Trade-offs
require_auth+ Pass ArgsBoth approaches are secure. The trade-off is between distributed responsibility (current workaround) and centralized policy (proposed feature).
Precedent
viewandpurefunctions can be called re-entrantly by design. DeFi relies on this extensively.QueryMsgis a separate entry point specifically for read-only operations.#[view]functions are restricted from modifying state and can be safely re-entered.Soroban's current behavior (blocking all re-entry including reads) is more restrictive than all three major platforms.
Scope
This benefits many DeFi patterns:
Request
We request a formal feature: allow read-only functions (declared with
#[view]or similar) to bypass re-entry detection at the host level, with compile-time and/or runtime enforcement that they do not modify state.Beta Was this translation helpful? Give feedback.
All reactions