Safety Module Slashing

Triggering a Safety Module

Assets in a Safety Module can be slashed in the event that any of the Safety Module's configured triggers are used to trigger it. To trigger a Safety Module, SafetyModule.trigger(trigger_) can be called permissionlessly with a trigger in the triggered state that the Safety Module has been configured to use (see Create a Trigger).

/// @notice Triggers the safety module by referencing one of the triggers configured for this safety module.
function trigger(ITrigger trigger_) external;

Each trigger is allowed to be successfully used with SafetyModule.trigger(trigger_) once.

At a high-level, when SafetyModule.trigger(trigger_) is called with a valid trigger successfully:

  • Protocol fees are dripped.

  • SafetyModule.numPendingSlashes is incremented by 1. An invariant of the protocol is that the Safety Module is triggered while SafetyModule.numPendingSlashes > 0 && SafetyModule.safetyModuleState != SafetyModuleState.PAUSED (see Safety Module States).

  • SafetyModule.triggerData(trigger_).triggered is set to true, disallowing the trigger to be used to trigger the Safety Module again.

  • SafetyModule.payoutHandlerNumPendingSlashes(triggerData_.payoutHandler) is incremented by 1 (see Payout Handlers).

  • event Triggered(trigger_) is emitted

  • SafetyModule.safetyModuleState() is set to triggered if the Safety Module is not paused.

Safety Module State Change

When SafetyModule.trigger is called with a valid trigger in the triggered state, the Safety Module's state becomes triggered if it is currently active and not paused (see Safety Module States).

Payout Handlers

Safety Modules require that each trigger is configured with a payout handler address, which is allowed to slash assets from each reserve pool in the Safety Module in the case that the trigger is used to successfully call SafetyModule.trigger(trigger_) (see Define Safety Module Configuration).

To determine the payout handler for a trigger configured for the Safety Module, integrators can use SafetyModule.triggerData(trigger_):

/// @param exists Whether the trigger exists.
/// @param payoutHandler The payout handler that is authorized to slash assets when the trigger is triggered.
/// @param triggered Whether the trigger has triggered the safety module. A trigger cannot trigger the safety module
///        more than once.
struct Trigger {
  bool exists;
  address payoutHandler;
  bool triggered;
}

function triggerData(ITrigger trigger_) external view returns (Trigger memory);

When a trigger is successfully triggers the Safety Module, the amount of times its assigned payout handler is allowed to slash assets is incremented by one. When the payout handler slashes assets, the amount of times they are allowed to slash assets is decremented by one.

To determine how many times a specific payout handler can slash assets at the current block (which requires a trigger has configured it as its payout handler and has been used to trigger the Safety Module), integrators can call SafetyModule.payoutHandlerNumPendingSlashes(triggerData_.payoutHandler).

/// @dev Maps payout handlers to the number of times they are allowed to call slash at the current block.
function payoutHandlerNumPendingSlashes(address payoutHandler_) external returns (uint256);

Slashing Safety Module Assets

Safety Module assets can only be slashed by valid payout handlers, and the Safety Module must be in the triggered state (SafetyModule.safetyModuleState() == TRIGGERED).

To slash assets, payout handlers can call SafetyModule.slash(Slash[] memory slashes_, address receiver_):

struct Slash {
  // ID of the reserve pool.
  uint8 reservePoolId;
  // Asset amount that will be slashed from the reserve pool.
  uint256 amount;
}

/// @notice Slashes the reserve pools, sends the assets to the receiver, and returns the safety module to the ACTIVE
/// state if there are no payout handlers that still need to slash assets. Note: Payout handlers can call this
/// function once for each triggered trigger that has it assigned as its payout handler.
/// @param slashes_ The slashes to execute.
/// @param receiver_ The address to receive the slashed assets.
function slash(Slash[] memory slashes_, address receiver_) external;

At a high-level, when SafetyModule.slash is called by a valid payout handler:

  • SafetyModule.numPendingSlashes() is decremented by 1.

  • SafetyModule.payoutHandlerNumPendingSlashes(payoutHandler) is decremented by 1.

  • Assets are slashed from each reserve pool according to the slashes_ specified, and transferred to receiver_.

  • Internal asset accounting is updated for each reserve pool and underlying asset.

    • SafetyModule.reservePool(reservePoolId_).depositAmount is decreased by the amount slashed from the reserve pool.

    • SafetyModule.assetPools(asset_).amount is decreased by the amount of the underlying asset slashed.

  • If SafetyModule.numPendingSlashes == 0, SafetyModule.safetyModuleState() is set to active.

  • For each reserve pool that is slashed, event Slashed( address indexed payoutHandler_, address indexed receiver_, uint256 indexed reservePoolId_, uint256 assetAmount_ ); is emitted.

Last updated