Mezz Guards

Overview

Guards are an important component of safes, which are multi-signature wallets designed by Safe (formerly Gnosis Safe). A safe can set another contract to arbitrarily block certain transactions. Departments and treasuries in Mezzanine directly inherit from safe, enabling them to have multi-signature functionality. As multi-signature wallets, they can execute arbitrary transactions so long as a sufficient number of signatures are provided. Guards in Mezzanine are key in blocking both departments and treasuries from managing their signers, resetting their guard, changing their threshold, or executing an arbitrary delegate call. Guards are atomically deployed with departments and treasuries by the Mezz Deployer.

Safe's standard functionality

A standard safe manages its signers (known as owners in Safe's source code) and guard by executing an external call on itself via its multi-signature functionality. This guarantees that the threshold is reached when changing its state.

Safe's SelfAuthorized base contract
Code Snippet from Safe's OwnerManager, which safes inherit. Notice that the authorized modifier is used here.

A guard's checkTransaction function is called each time that a safe uses its mult-signature functionality:

Code snippet from a safe's execTransaction, which is responsible for executing multi-signature, arbitrary transactions

The blocking of calls by a guard is completely bespoke and up to the implementation. It should be noted that checkTransaction is called via a static call and does not change state.

Mezz Guards

Guards in Mezzanine, known as Mezz Guards, are used to block direct calls made to manage a treasury or department's signers, its threshold, or its guard:

Code snippet from the MezzGuard base contract. SafeTxValidation is a library to validate that 'data' does not correspond to managing a safe's signers, setting a guard, or enabling a safe module

These functions can only be called via other functions, which enable strict access control. For example, only an ancestor can swap the guard for a department:

A department's swapGuard function. The onlyAncestor modifier will cause this function to revert if not called by an ancestor
TeamLogic's swapGuard function, which is called above. Deploys a MezzGuard via the MezzDeployer and calls on the contract itself, passing the Safe's authorized check

A Mezzanine treasury or department cannot change their guards to user-created ones. Guards must be deployed via the Mezz Deployer, which deploys implementations that the Mezzanine team sets.

There are three different types of Guards in Mezzanine: a whitelist guard, a blacklist guard, and a shareholder guard. Treasuries and departments can use any of these three guards. By default, all departments and treasuries use a blacklist, since it is much less heavy-handed. Whitelists should only be used when the restriction of a department's capabilities is of the utmost importance.

A Mezz Guard keeps also track of two lists which can be used as either a whitelist or blacklist depending on the implementation. One list is an enumerable set of contract addresses, while the other ‘list’ is a dynamic array of guard selectors. A guard selector is the pairing of a contract address and a function selector.

Code Snippet from Mezzanin'es DataTypes.sol

For example, assume a department uses a guard that is a blacklist. The guard's contract list contains the billing router, and its list of guard selectors contains USDC and the function selector to transfer. All calls to the billing router or to transfer USDC by the department will revert if called via its multi-signature functionality.

Both whitelist and blacklist guards will check in their checkTransaction function if the call is made to any contracts or guard selectors on the list.

Blacklist Guard's checkTransaction function
Mezz Guard's onList function, which Blacklist Guard inherits

If either of these conditions is true, the blacklist guard will revert the transaction, while the whitelist guard will not revert the transaction. This enables a department’s ancestor or the treasury’s governance to either blacklist or whitelist certain function calls.

By default, Mezzanine uses blacklist guards. Whitelist guards require an extensive amount of overhead. Due to the high number of dependencies in Mezzanine, all other core contracts, such as the token timelock, would need to be whitelisted in addition to all other non-Mezzanine contracts that the department may use.

Guards can be easily changed from one implementation to another. It should be noted that when a guard is swapped, the lists are not maintained. For example, if a whitelist guard is used such that swaps on a DEX revert, those transactions will not be blocked if the user swaps to a blacklist guard. Instead, the controller of the guard must manually update the list.

Each guard has a controller which is set during the guard's initialization. The controller is responsible for setting the contract and guard selector lists. The controller of a department's guard will be its parent, while the controller of a treasury's guard will be the system used for shareholder governance.

The shareholder guard is unique. It is a blacklist guard whose controller is always the company's governance system. Departments may want to use a shareholder guard in special circumstances. For instance, a Mezzanine company may desire to be more democratic in its operations. All decisions then to blacklist transactions for departments should then stem from governance. Treasuries, by default, are deployed with shareholder guards.

Last updated