Meta — Multi-Owner Stacking¶
TL;DR: MultiOwnable is flat: any owner has full
onlyOwnerspower, no majority, no veto. Adding someone = full trust. Design around that, don't work around it.
Why this matters¶
Dysnomia's access control is MultiOwnable, not OpenZeppelin's single-owner Ownable. Every QING, LAU, WORLD, YUE, and most game contracts inherit it. That's a deliberate architectural choice — many contracts need cross-invocation rights, which require multi-ownership — but it has sharp consequences:
- Adding an owner is a total-trust operation. The new owner can
Withdraw,Rename,setCoverCharge,setGuestlist,removeGuest, and evenrenounceOwnership(you)to evict you. - There's no M-of-N. MultiOwnable is 1-of-anybody. A single rogue owner can do damage instantly.
- There's no on-chain quorum. If you want multisig semantics you have to wrap the contract in an external multisig (Gnosis Safe, etc.) and make the Safe the sole owner.
Many "shared venue" ambitions go bad because teams treat MultiOwnable like a co-signer list. It isn't.
The numbers¶
What MultiOwnable actually provides¶
From MultiOwnable:
| Function | Signature | Effect |
|---|---|---|
addOwner(newOwner) |
public onlyOwners |
Any existing owner can add any address. No cooldown, no approval. |
renounceOwnership(toRemove) |
public onlyOwners |
Any existing owner can remove any address (including other owners). |
owner() |
external view | Returns an owner (a representative). Not the full list. |
Consequences at the game level¶
- QING co-ownership: any owner can
setCoverCharge,setStaff,setNoCROWS,setBouncerDivisor,setGuestlist,removeGuest,Withdraw. See QING. - LAU co-ownership: any owner controls the LAU's Purchase/Redeem rate seeding, Chat routing, and balance. See LAU.
- YUE co-ownership: any owner can Withdraw YUE-held balances. See YUE.
- Contract-contract ownership is common. YANG addOwners itself to YAU, ZHOU, ZHENG, YI during construction (see YANG constructor). The protocol's contracts co-own each other as part of normal operation.
What you cannot do natively¶
- Require 2-of-3 approvals.
- Put an owner on "view-only."
- Schedule time-locked adds/removes.
- Detect owner activity per-address (only
owner()is exposed).
For any of those, you need an external multisig fronting your ownership slot.
The play¶
- Default to solo ownership. Unless you need someone else to have equal control, don't add them. The marginal convenience of shared write-access is almost never worth the blast radius.
- If you must co-own, use a Safe. Deploy a Gnosis Safe (or equivalent),
addOwner(safeAddress), thenrenounceOwnership(yourEOA). Now the only on-chain owner is the Safe, which enforces M-of-N off-chain semantics via its signature threshold. - Never co-own with an unverified contract. Any contract that's an owner can be upgraded or exploited into draining your QING. If you add a contract as owner, read its source.
- For QING venues, prefer the staff/guestlist layer.
setStaff(addr, true)gives bouncer-ish rights without full ownership. You can always revoke. Use staff for operational roles; reserve ownership for treasurers. - Treat
renounceOwnership(yourself)as a nuclear option. Once you're out, you cannot re-add yourself; only another owner can, and if you've just evicted yourself from a bad-faith co-owner, they won't. Test on a throwaway QING first. owner()is not an audit tool. It returns an owner, not the full set. To enumerate owners, read theOwnershipUpdateevent log. Build a dashboard; don't rely on one-shot view calls.- (inferred) Protocol contracts as owners are semi-permanent. YANG's construction adds itself to YAU/ZHOU/ZHENG/YI. Don't try to "clean up" those relationships — they're load-bearing.
Worked example¶
You're a venue operator deploying a new QING with two partners (Alice, Bob). You want: any 2 of 3 must approve withdrawals; all 3 can moderate; you alone can set cover.
Naïve approach (do NOT do this):
Result: all three have full power. Alice canWithdraw unilaterally. Bob can renounceOwnership(you) and run off with the treasury.
Correct approach:
- Deploy a Gnosis Safe with Alice, Bob, you as signers, threshold 2-of-3.
QING.addOwner(safe).- Decide which operational rights are "ops" (everyday) vs "treasury" (high-value):
- For ops:
setStaff(you, true); setStaff(Alice, true); setStaff(Bob, true)— bouncer-style rights without ownership. - For treasury: only the Safe is an owner; withdrawals require 2-of-3.
renounceOwnership(yourEOA)(and Alice's and Bob's EOAs if any were ever added).
Now cover-setting (setCoverCharge) is still onlyBouncers; staff can do it. But Withdraw requires the Safe's 2-of-3.
Result: operational agility + treasury safety, without MultiOwnable being the trust surface.
Gotchas¶
renounceOwnership(toRemove)accepts arbitrary addresses. There is no "remove self" safety — an owner can remove any owner, including the original deployer.addOwnerhas no cooldown. Rotating the ownership set is instant. An attacker with a private key briefly in their hands can rearrange ownership permanently.- No event filter by "current owners." The
OwnershipUpdateevent emits(newOwner, state); to get the current set you replay all events. - CROWS + MultiOwnable interaction at QINGs. If
NoCROWS == false, CROWS holders ≥ 25 act as bouncers — but bouncers aren't owners. They can't add/remove owners. This is a sharp distinction; see QING. - Owner ≠ bouncer. Being an owner doesn't automatically make you a bouncer. Bouncer status is separately derived from CROWS/Staff/Divisor-share logic.
- Protocol owners cascade. If YANG is an owner of YAU (which it is per YANG), then whoever owns YANG's contract indirectly owns YAU. Upgrade paths matter.
- (inferred)
onlyOwner≠onlyOwners. DSS uses singularonlyOwner(one owner); most Dysnomia contracts use pluralonlyOwners(MultiOwnable). Mixing them in a custom extension silently changes semantics. Read the modifier signature, not just the name. - renounceOwnership doesn't burn; it transfers-out. There's no "seal the contract forever" path unless you renounce every owner. Once the last owner renounces, all
onlyOwnerspaths revert — the contract becomes immutable in admin-space but continues to serve its public interface.
Where it cross-connects¶
- Venues: Cover-Charge Tuning — operator-side controls that MultiOwnable enables.
- Venues: Guest-List Rotation —
setStaff+setGuestlistas lower-trust alternatives. - Meta: DSS Bundling — the lone
onlyOwner(singular) outlier. - Meta: Word Games Tactics — ACRONYM round advancement often needs an owner to trigger.
- Timing: First-Mover Tradeoffs — early deployers of QINGs set ownership structure before trust is established.
- Cheat Sheet: Constants — no numeric constants for MultiOwnable, but referenced in the architecture overview.