Composability vs. Security: Rethinking contract-caller Restrictions in Stacks DeFi

The Tension

As builders shipping production code on Stacks, we’re increasingly running into a pattern that deserves ecosystem-wide discussion: contract-caller gating as the default security posture.

This isn’t a critique of any specific protocol or auditor. It’s an invitation to collectively examine a tradeoff that will compound as our ecosystem grows.

The Pattern

Many audited Stacks protocols enforce strict contract-caller checks—ensuring only direct calls (or whitelisted contracts) can interact with core functions. The rationale is sound: it eliminates proxy contract attack vectors where a malicious intermediary could manipulate state or exploit tx-sender assumptions.

Security auditors—rightfully conservative—often recommend this approach. And for a protocol’s base layer, especially at launch, “better safe than sorry” makes sense.

The Cost

Here’s what this means for builders creating composable layers:

Instead of 1 extension contract serving N smart wallets, you embed the same logic into all N smart wallet contracts.

Want to build a smart wallet contract that interacts with Protocol A? You need Protocol A’s blessing or you duplicate their interface internally. Want to add the same functionality for a different collateral type (e.g., stSTX instead of sBTC)? New contract. New audit. Same code.

The ecosystem pays a compounding tax in duplicated code every time someone builds on top of restricted protocols.

This friction isn’t theoretical. Builders working on smart wallets, aggregators, and composable DeFi layers are hitting this wall today.

The Core Question

Should individual protocols absorb responsibility for proxy contract attacks, or should that responsibility live elsewhere?

Some possibilities worth exploring:

  1. Clarity 4 post-conditions: Contract-level post-conditions and contract hash verification may provide sufficient security guarantees without sacrificing composability. Are we underutilizing these primitives?

  2. Whitelisting patterns: Similar to PoX’s approach—protocols could allow trait-based whitelisting where wallets register approved extensions. Not perfect, but more flexible than blanket restrictions.

  3. Layered security postures: Conservative base layer + more permissive “advanced” entry points for verified integrators. Let protocols offer both.

  4. Ecosystem-wide standards: If every protocol independently gates on contract-caller, we’ve collectively decided composability isn’t a priority. Is that the ecosystem we want?

Why This Matters Now

Stacks is positioning for exponential growth. The decisions we encode today—in contracts and in audit recommendations—will shape what’s possible tomorrow.

Smart wallet contracts, account abstraction, DeFi aggregators, automated strategy vaults… these all depend on contracts being able to compose. If our default security posture makes composition painful, we’re limiting our own ceiling.

Not Asking for Less Security

To be clear: proxy contract attacks are real. tx-sender auth has genuine edge cases. Auditors recommending caution aren’t wrong.

But courageous product decisions that push back on excessive caution—when the tradeoff is ecosystem-wide composability—can pay off long-term.

Maybe there’s a path that preserves both.

Discussion

  • What’s your experience building composable layers on Stacks?

  • Are Clarity 4 post-conditions sufficient to relax contract-caller restrictions safely?

  • Should we develop ecosystem standards for “composability-friendly” security patterns?

  • How do other ecosystems handle this tension?

Curious to hear from protocol teams, auditors, and builders who’ve felt this friction firsthand.


This post reflects conversations with multiple builders and is intended to spark productive discussion, not criticize any specific team’s decisions. Security-first launches are valid. The question is what comes next.

5 Likes

1 Like

Sensitive function calls can be guarded with minting and burning a fungible token. Then no proxy attack possible thanks to post-conditions.

This comes with a runtime cost and brings composability.

3 Likes

This is a great discussion.

2 Likes

thank you for your kind word sir.

1 Like

Update: Good progress on the composability front.

Spoke with a lending protocol on Stacks — they’re planning to open up modularity paths, allowing third-party contracts to integrate more freely. Details TBD but promising direction.

Also flagged the contract-caller gating pattern to a BTC yield-bearing vault — they use it in their deposit function.

This ties into the broader Security Template discussion here: https://forum.stacks.org/t/security-trait-standards-for-clarity-access-control-pausable-contracts-more/18640 @Terese

The tension is real: gating to contract-caller is a sensible security default, but when every protocol adopts it, we end up with siloed DeFi that can’t compose. Ethereum’s strength wasn’t just smart contracts — it was permissionless composability. We should aim for that on Stacks while finding security patterns that don’t sacrifice it.

Friedger’s suggestion about using FT mint/burn as a guard (leveraging post-conditions) is interesting. Besides the xBTC retiring contract and Pillar’s smart wallet for extension calls, has anyone else implemented this pattern in production? @friedger

1 Like

To protocol teams using contract-caller gating in deposit/transfer functions:

If your contract does something like this:

(define-public (deposit (amount uint))
(begin
(try! (contract-call? .token transfer amount contract-caller (as-contract tx-sender) none))
;; mint shares, update state, etc.
)
)

This is a sensible security default - but it prevents composability.

What breaks:

  • Zap contracts (swap + deposit in one tx)
  • Multi-protocol routers
  • DAO/multisig flows without custodying funds first
  • Any stateless extension that wants to call your protocol as part of a larger flow (smart wallets using extensions as modularity)

The flow that fails:
User → Extension Contract → YourProtocol.deposit()

contract-caller = Extension

Transfer tries to pull from Extension, not User :cross_mark:

Patterns to consider:

  1. deposit-for function - Accept a sender param, use tx-sender for authorization, allow approved contracts to deposit on behalf of users
  2. Permit/signature pattern - User signs off-chain, any contract can submit with the signature as proof
  3. Friedger’s mint/burn guard - Use FT mint/burn, leveraging post-conditions for
    safety. See xBTC retiring contract and Pillar’s smart wallet extension pattern for production examples

The goal isn’t to remove security - it’s to find patterns that maintain safety while enabling the permissionless composability that makes DeFi powerful.

If you’ve implemented a composable pattern in production, share it here.