reentrancy.gno
3.03 Kb · 62 lines
1package commondao
2
3// executing is this realm's GLOBAL re-entrancy latch, the outer of two
4// complementary layers. It is set while a proposal executor is running
5// (inside the public Execute) and rejects any nested Execute — an executor
6// must never trigger another execution, whether of the same DAO or a
7// different one, directly or by crossing back in as its DAO.
8//
9// The two layers guard different invariants and neither subsumes the other:
10//
11// - The /p/ CommonDAO carries per-DAO `executing` / `proposing` FIELDS
12// (see the package's commondao.gno). Being fields on the owning DAO
13// object, they are writable where a mutable /p/ global is not, so they
14// travel with a standalone /p/ consumer that never wrote a realm-global
15// latch: they give every consumer same-DAO re-entrancy protection (a
16// nested Execute or Propose on the SAME DAO).
17// - This realm-global bool adds what a per-DAO field structurally cannot:
18// it enforces one-executor-per-transaction across ALL DAOs, closing the
19// cross-DAO straddle a per-DAO latch misses — the ancestor-hosted
20// dissolution/clawback case, where an executor running under DAO A's
21// Execute nests an Execute that deletes a DIFFERENT DAO out from under
22// A. A latch keyed on the executing DAO never fires there; a target
23// latch would demand a scattered check at every DAO-mutating executor.
24// The single global bool makes "one executor per tx" a local,
25// unforgettable invariant instead, and also shuts the conditional-
26// execution "free option" (nest a different DAO's Execute, observe
27// in-tx, panic to revert).
28//
29// It is realm module state: a mutable /p/ package global is forbidden
30// (writing one panics the borrow/stamping rule), and the latch must be
31// writable. This global is the realm's, complementing — not replacing — the
32// /p/ per-DAO fields.
33//
34// Vote / Create* / Withdraw / Resign are deliberately NOT latched: no executor
35// re-enters them, legitimate executors need them (sub-DAO creation, the
36// dissolution sweep), and a proposal executor acting as its DAO in ANOTHER DAO
37// (e.g. casting a council vote) rides those paths.
38//
39// A rejected nested Execute panics. Thrown across the cross() boundary the
40// re-entrant call arrived through, that aborts the whole transaction.
41//
42// The deferred leaveExecute lowers the flag on normal returns (and on any
43// same-transaction recovered panic). Cross-transaction safety does NOT rely on
44// that defer — a rejected re-entry aborts the transaction, and the aborted
45// transaction's realm writes (including this flag) are never committed, so a
46// later transaction can never observe it stuck true.
47var executing bool
48
49// enterExecute raises the re-entrancy latch, panicking if it is already raised
50// (i.e. this Execute is nested inside another). Pair with a deferred
51// leaveExecute.
52func enterExecute() {
53 if executing {
54 panic("commondao: re-entrant Execute is not allowed")
55 }
56 executing = true
57}
58
59// leaveExecute lowers the re-entrancy latch.
60func leaveExecute() {
61 executing = false
62}