Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}