Docs · Concepts

05 · No pause on transfer

No switch can stop transfers after they are open. A switch that can only open them is fine.

The question

Can transfers be turned off?

A paused flag with a setter is a stop button on every holder's exit. The common honest cousin is tradingOpen: false at deployment so the pool can be set up, flipped to true once, never back. The check tells the two apart.

How it decides

A candidate is a state variable of type bool that has a guard read in the transfer path, the same definition of guard and transfer path as check 04.

For each candidate the check determines the blocking value: the value of the switch that makes the guard revert. It counts the ! operators between the read and the guard.

Guard Blocks when
require(open) or require(open || x) open is false
require(!paused) paused is true
if (!open) revert open is false
if (paused) revert paused is true

Then it looks at every assignment to the switch in functions reachable from an entry point, constructor excluded. If every such assignment writes the literal that is not the blocking value, the switch can only be moved toward "open": pass. Any assignment that writes the blocking value, or writes a non-literal (paused = v), is a fail.

  • tradingOpen can only be switched to let transfers through. : pass
  • paused can stop _transfer, and setPaused can flip it. : fail
  • No switch in the transfer path: pass

What passes

bool public tradingOpen;
function openTrading() external onlyOwner { tradingOpen = true; }
function _transfer(...) internal { require(tradingOpen || from == owner, "not open"); ... }

One direction only. Once open, it stays open, and the code proves it.

What fails

bool public paused;
function setPaused(bool v) external onlyOwner { paused = v; }
function pause() external onlyOwner { paused = true; }
function unpause() external onlyOwner { paused = false; }

Both directions exist, so the stop button exists. OpenZeppelin's Pausable with whenNotPaused on _update fails for the same reason, and should: it is a pause.

Known edges

A switch held in a uint (tradingBlock, launchTime) rather than a bool is not seen by this check. A time-based lock (require(block.timestamp > openAt)) is not a switch anyone can flip, and is not flagged. The review reads both.