Scarb 2.21 came out on 12 October with static contract forwarding (in the Scarb docs, under the Starknet contract target). You point a contract at another contract in the same build. Scarb embeds that contract’s class hash at compile time and passes the interface you pick straight through. No storage reads, no setup. It’s still behind Cairo’s unstable forward-impl feature, but it’s a really clean way to drop proxy boilerplate, so I expect people to try it.
It’s easy to miss, when the setup is two lines, that every forwarded function is a library_call_syscall to that class hash. You can see it in the generated wrappers. Cairo has had forward impls since 2.18, with a class hash you return yourself (usually read from storage), and Scarb now fills in a constant. The beacon proxy RFC posted here in March is the dynamic version of the same idea, so most of this applies to both.
Their code, your storage
In a library call the target’s code runs in your context. It reads and writes your storage, and get_contract_address() returns your address. Storage addresses come from variable names, so if your contract stores owner and the target also stores owner, it’s the same slot. Sometimes that’s exactly what you want. Either way I’d put the two storage layouts side by side before shipping.
The target’s constructor never runs for your contract. Whatever it writes at deploy time, an owner or a list of roles, stays empty in your storage unless your own constructor writes it. Read every forwarded function with that in mind. If the interface has a one-time initialize, that’s the first one I’d look at, because on your address nobody has called it yet.
An upgrade function upgrades you
If the forwarded interface includes an upgrade that calls replace_class_syscall, it replaces your contract’s class. In the blockifier, replace_class sets the new class at the call’s storage address, and a library call keeps yours. So check who can reach that function on your address, against your storage, not the target’s.
The hash doesn’t move
This one goes the other way. If the team behind the target finds a bug and upgrades their deployed contract, yours keeps running the old class, because it points at a class hash and not at their address. You only get the fix by rebuilding and redeploying.
The class also has to be declared before any forwarded call works. And the hash Scarb embeds is the hash of its own build of the target. The Sierra program inside a class hash starts with the Sierra and compiler versions, so the same source built with another Cairo version gives another hash. Declare the artifact from that same build.
Before declaring a 2.21 build
The chain info page in the Starknet docs, updated on 7 October for v0.14.4, still lists Cairo 2.0.0 to 2.19.0 for mainnet and Sepolia. Cairo 2.21 emits Sierra 1.9.5. The gateway code in the sequencer repo ignores the patch number when it checks the Sierra version, so that check should pass, but the sequencer also compiles your Sierra to CASM with its own compiler (2.19.6 on the repo’s main branch today). I’d do a test declare on Sepolia before planning a release around it.
Is anyone here already using forward impls, with your own stored hash or with Scarb’s static one? And if someone from StarkWare reads this: is 2.19.0 still the newest compiler you’d recommend for mainnet declares, or is the page just behind?