Semaphore is an opt-in component of Firm protocol which can be used to limit which actions certain modules can perform within an instantiation of the system.

It was originally designed to balance power between what owners of the Safe can do via multisig transactions (board actions) and which actions only Voting can perform.

It is called Semaphore because its intended way of being used is by balancing permissions of specific action pairs, blocking access to one while allowing access to the other.

It is attached to a Safe by setting it as its guard. This will cause the Safe to check with Semaphore before performing any multisig transaction executed by its owners. Firm module transactions (i.e. those coming from Budget or Voting) bypass this check and must explicitly perform their own Semaphore checks (Voting does if it has a Semaphore attached).


Setting semaphore state

Only the Safe can set the global semaphore state for callers using Semaphore:setSemaphoreState(address caller, DefaultMode defaultMode, bool allowDelegateCalls, bool allowValueCalls)

Each caller (i.e. Safe or module performing calls) has a global configuration or semaphore state. It’s comprised of three variables:

  • Default mode: dictates which is the default authorization state for the caller for all calls which do not have an explicit exception.

  • Allow delegatecall: some callers, like Safe, may have the ability to perform delegatecalls (which by their nature makes it really hard to know exactly what their sideeffects will be unless the target is known or trusted). This boolean setting acts as a blanket switch for delegatecalls (exceptions do not apply here.)

  • Allow value calls: some callers may perform calls whose value is greater than zero which will cause the Safe to transfer some amount of the native asset (i.e. ETH on Ethereum). This boolean setting acts as a blanket switch for delegatecalls (exceptions do not apply here.)

Setting exceptions

Only the Safe can add exceptions to the default mode of operation using Semaphore:addExceptions(ExceptionInput[] exceptions)

Exception types:

  • Signature exceptions: applies exception to all calls with that selector for all targets

  • Target exceptions: applies exception to all calls to a specific target

  • Signature and target exceptions: applies exceptions only to the specific signature target pair.

Checking access control

Semaphore exposes two functions to check whether it allows a certain caller to perform a call to a target.

Semaphore:canPerform(address *caller*, address *target*, uint256 *value*, bytes calldata *data*, bool *isDelegateCall*) is used to perform a single check and Semaphore:canPerformMany(address *caller*, address[] calldata *targets*, uint256[] calldata *values*, bytes[] calldata *calldatas*, bool *isDelegateCall*) is the optimized version to check several calls for a single caller.

The base contract SemaphoreAuth is provided for modules that want to perform checks against Semaphore before performing a call.

Last updated