Slashing

Validator slashing and jail management for network security

Overview

The Slashing precompile provides an interface to the Ontomir SDK's x/slashing module, which is responsible for penalizing validators for misbehavior, such as downtime or double-signing. This precompile allows smart contracts to unjail validators and query slashing-related information, including validator signing info and module parameters.

Precompile Address: 0x0000000000000000000000000000000000000806

Gas Costs

Gas costs are approximated and may vary based on chain settings.

Method
Gas Cost

Transactions

2000 + (30 × bytes of input)

Queries

1000 + (3 × bytes of input)

Transaction Methods

unjail

Allows a validator to unjail themselves after being jailed for downtime.

```solidity Solidity expandable lines // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;

contract SlashingExample { address constant SLASHING_PRECOMPILE = 0x0000000000000000000000000000000000000806; struct SigningInfo { address validatorAddress; int64 startHeight; int64 indexOffset; int64 jailedUntil; bool tombstoned; int64 missedBlocksCounter; } event ValidatorUnjailed(address indexed validator); event ValidatorSlashingInfoQueried(address indexed validator, bool isJailed, bool isTombstoned); function unjailValidator(address validatorAddress) external returns (bool success) { require(validatorAddress != address(0), "Invalid validator address"); // Check signing info to prevent unjailing tombstoned validators SigningInfo memory info = this.getValidatorSigningInfo(validatorAddress); require(!info.tombstoned, "Tombstoned validators cannot be unjailed"); require(info.jailedUntil > int64(int256(block.timestamp)), "Validator is not jailed"); (bool callSuccess, bytes memory result) = SLASHING_PRECOMPILE.call( abi.encodeWithSignature("unjail(address)", validatorAddress) ); require(callSuccess, "Unjail call failed"); success = abi.decode(result, (bool)); require(success, "Unjail operation failed"); emit ValidatorUnjailed(validatorAddress); return success; } function getValidatorSigningInfo(address consAddress) external view returns (SigningInfo memory signingInfo) { (bool success, bytes memory result) = SLASHING_PRECOMPILE.staticcall( abi.encodeWithSignature("getSigningInfo(address)", consAddress) ); require(success, "Signing info query failed"); signingInfo = abi.decode(result, (SigningInfo)); return signingInfo; } function canValidatorBeUnjailed(address consAddress) external view returns (bool canUnjail, string memory reason) { SigningInfo memory info = this.getValidatorSigningInfo(consAddress); // CRITICAL: Tombstoned validators can NEVER be unjailed if (info.tombstoned) { return (false, "PERMANENT_TOMBSTONE: Validator can never be unjailed due to severe infractions (e.g., double-signing)"); } if (info.jailedUntil <= int64(int256(block.timestamp))) { return (false, "Validator is not currently jailed"); } return (true, "Validator can be unjailed"); } function getValidatorStatus(address consAddress) external view returns (bool isJailed, bool isTombstoned, int64 missedBlocks) { SigningInfo memory info = this.getValidatorSigningInfo(consAddress); isJailed = info.jailedUntil > int64(int256(block.timestamp)); isTombstoned = info.tombstoned; missedBlocks = info.missedBlocksCounter; emit ValidatorSlashingInfoQueried(consAddress, isJailed, isTombstoned); return (isJailed, isTombstoned, missedBlocks); }

}

</CodeGroup>

## Query Methods

### `getSigningInfo`

Returns the signing information for a specific validator.

<CodeGroup>
```javascript Ethers.js expandable lines
import { ethers } from "ethers";

// ABI definition for the function
const precompileAbi = [
  "function getSigningInfo(address consAddress) view returns (tuple(address validatorAddress, int64 startHeight, int64 indexOffset, int64 jailedUntil, bool tombstoned, int64 missedBlocksCounter) signingInfo)"
];

// Provider and contract setup
const provider = new ethers.JsonRpcProvider("<RPC_URL>");
const precompileAddress = "0x0000000000000000000000000000000000000806";
const contract = new ethers.Contract(precompileAddress, precompileAbi, provider);

// Input: The consensus address of the validator
const consAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Placeholder

async function getSigningInfo() {
  try {
    const signingInfo = await contract.getSigningInfo(consAddress);
    console.log("Signing Info:", JSON.stringify(signingInfo, null, 2));
  } catch (error) {
    console.error("Error fetching signing info:", error);
  }
}

getSigningInfo();
# Note: Replace <RPC_URL> and the placeholder consensus address with your actual data.
curl -X POST --data '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
        {
            "to": "0x0000000000000000000000000000000000000806",
            "data": "0x3f554612000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045"
        },
        "latest"
    ],
    "id": 1
}' -H "Content-Type: application/json" <RPC_URL>

getSigningInfos

Returns the signing information for all validators, with pagination support.

```javascript Ethers.js expandable lines import { ethers } from "ethers";

// ABI definition for the function const precompileAbi = [ "function getSigningInfos(tuple(bytes key, uint64 offset, uint64 limit, bool countTotal, bool reverse) pagination) view returns (tuple(address validatorAddress, int64 startHeight, int64 indexOffset, int64 jailedUntil, bool tombstoned, int64 missedBlocksCounter)[] signingInfos, tuple(bytes nextKey, uint64 total) pageResponse)" ];

// Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000806"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider);

// Input for pagination const pagination = { key: "0x", offset: 0, limit: 10, countTotal: true, reverse: false, };

async function getSigningInfos() { try { const result = await contract.getSigningInfos(pagination); console.log("Signing Infos:", JSON.stringify(result.signingInfos, null, 2)); console.log("Pagination Response:", result.pageResponse); } catch (error) { console.error("Error fetching signing infos:", error); } }

getSigningInfos();


```bash cURL expandable lines
# This example queries for the first 10 validators' signing info.
curl -X POST --data '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
        {
            "to": "0x0000000000000000000000000000000000000806",
            "data": "0x5f993f4300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"
        },
        "latest"
    ],
    "id": 1
}' -H "Content-Type: application/json" <RPC_URL>

getParams

Returns the current parameters for the slashing module.

```javascript Ethers.js expandable lines import { ethers } from "ethers";

// ABI definition for the function const precompileAbi = [ "function getParams() view returns (tuple(int64 signedBlocksWindow, tuple(uint256 value, uint8 precision) minSignedPerWindow, int64 downtimeJailDuration, tuple(uint256 value, uint8 precision) slashFractionDoubleSign, tuple(uint256 value, uint8 precision) slashFractionDowntime) params)" ];

// Provider and contract setup const provider = new ethers.JsonRpcProvider(""); const precompileAddress = "0x0000000000000000000000000000000000000806"; const contract = new ethers.Contract(precompileAddress, precompileAbi, provider);

async function getParams() { try { const params = await contract.getParams(); console.log("Slashing Parameters:", JSON.stringify(params, null, 2)); } catch (error) { console.error("Error fetching slashing parameters:", error); } }

getParams();


**Note**: The slashing `getParams()` function may return empty data if slashing parameters are not configured on your network. The cURL method will still demonstrate the correct function call format.

```bash cURL expandable lines
# Note: Replace <RPC_URL> with your actual RPC endpoint.
curl -X POST --data '{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
        {
            "to": "0x0000000000000000000000000000000000000806",
            "data": "0x4035236b"
        },
        "latest"
    ],
    "id": 1
}' -H "Content-Type: application/json" <RPC_URL>

Full Solidity Interface & ABI

// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.17;

import "../common/Types.sol";

/// @dev The ISlashing contract's address.
address constant SLASHING_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000806;

/// @dev The ISlashing contract's instance.
ISlashing constant SLASHING_CONTRACT = ISlashing(SLASHING_PRECOMPILE_ADDRESS);

/// @dev SigningInfo defines a validator's signing info for monitoring their
/// liveness activity.
struct SigningInfo {
    /// @dev Address of the validator
    address validatorAddress;
    /// @dev Height at which validator was first a candidate OR was unjailed
    int64 startHeight;
    /// @dev Index offset into signed block bit array
    int64 indexOffset;
    /// @dev Timestamp until which validator is jailed due to liveness downtime
    int64 jailedUntil;
    /// @dev Whether or not a validator has been tombstoned (killed out of validator set)
    bool tombstoned;
    /// @dev Missed blocks counter (to avoid scanning the array every time)
    int64 missedBlocksCounter;
}

/// @dev Params defines the parameters for the slashing module.
struct Params {
    /// @dev SignedBlocksWindow defines how many blocks the validator should have signed
    int64 signedBlocksWindow;
    /// @dev MinSignedPerWindow defines the minimum blocks signed per window to avoid slashing
    Dec minSignedPerWindow;
    /// @dev DowntimeJailDuration defines how long the validator will be jailed for downtime
    int64 downtimeJailDuration;
    /// @dev SlashFractionDoubleSign defines the percentage of slash for double sign
    Dec slashFractionDoubleSign;
    /// @dev SlashFractionDowntime defines the percentage of slash for downtime
    Dec slashFractionDowntime;
}

/// @author Evmos Team
/// @title Slashing Precompiled Contract
/// @dev The interface through which solidity contracts will interact with slashing.
/// We follow this same interface including four-byte function selectors, in the precompile that
/// wraps the pallet.
/// @custom:address 0x0000000000000000000000000000000000000806
interface ISlashing {
    /// @dev Emitted when a validator is unjailed
    /// @param validator The address of the validator
    event ValidatorUnjailed(address indexed validator);

    /// @dev GetSigningInfo returns the signing info for a specific validator.
    /// @param consAddress The validator consensus address
    /// @return signingInfo The validator signing info
    function getSigningInfo(
        address consAddress
    ) external view returns (SigningInfo memory signingInfo);

    /// @dev GetSigningInfos returns the signing info for all validators.
    /// @param pagination Pagination configuration for the query
    /// @return signingInfos The list of validator signing info
    /// @return pageResponse Pagination information for the response
    function getSigningInfos(
        PageRequest calldata pagination
    ) external view returns (SigningInfo[] memory signingInfos, PageResponse memory pageResponse);

    /// @dev Unjail allows validators to unjail themselves after being jailed for downtime
    /// @param validatorAddress The validator operator address to unjail
    /// @return success true if the unjail operation was successful
    function unjail(address validatorAddress) external returns (bool success);

    /// @dev GetParams returns the slashing module parameters
    /// @return params The slashing module parameters
    function getParams() external view returns (Params memory params);
}
{
  "_format": "hh-sol-artifact-1",
  "contractName": "ISlashing",
  "sourceName": "solidity/precompiles/slashing/ISlashing.sol",
  "abi": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "validator",
          "type": "address"
        }
      ],
      "name": "ValidatorUnjailed",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "getParams",
      "outputs": [
        {
          "components": [
            {
              "internalType": "int64",
              "name": "signedBlocksWindow",
              "type": "int64"
            },
            {
              "components": [
                {
                  "internalType": "uint256",
                  "name": "value",
                  "type": "uint256"
                },
                {
                  "internalType": "uint8",
                  "name": "precision",
                  "type": "uint8"
                }
              ],
              "internalType": "struct Dec",
              "name": "minSignedPerWindow",
              "type": "tuple"
            },
            {
              "internalType": "int64",
              "name": "downtimeJailDuration",
              "type": "int64"
            },
            {
              "components": [
                {
                  "internalType": "uint256",
                  "name": "value",
                  "type": "uint256"
                },
                {
                  "internalType": "uint8",
                  "name": "precision",
                  "type": "uint8"
                }
              ],
              "internalType": "struct Dec",
              "name": "slashFractionDoubleSign",
              "type": "tuple"
            },
            {
              "components": [
                {
                  "internalType": "uint256",
                  "name": "value",
                  "type": "uint256"
                },
                {
                  "internalType": "uint8",
                  "name": "precision",
                  "type": "uint8"
                }
              ],
              "internalType": "struct Dec",
              "name": "slashFractionDowntime",
              "type": "tuple"
            }
          ],
          "internalType": "struct Params",
          "name": "params",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "consAddress",
          "type": "address"
        }
      ],
      "name": "getSigningInfo",
      "outputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "validatorAddress",
              "type": "address"
            },
            {
              "internalType": "int64",
              "name": "startHeight",
              "type": "int64"
            },
            {
              "internalType": "int64",
              "name": "indexOffset",
              "type": "int64"
            },
            {
              "internalType": "int64",
              "name": "jailedUntil",
              "type": "int64"
            },
            {
              "internalType": "bool",
              "name": "tombstoned",
              "type": "bool"
            },
            {
              "internalType": "int64",
              "name": "missedBlocksCounter",
              "type": "int64"
            }
          ],
          "internalType": "struct SigningInfo",
          "name": "signingInfo",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "bytes",
              "name": "key",
              "type": "bytes"
            },
            {
              "internalType": "uint64",
              "name": "offset",
              "type": "uint64"
            },
            {
              "internalType": "uint64",
              "name": "limit",
              "type": "uint64"
            },
            {
              "internalType": "bool",
              "name": "countTotal",
              "type": "bool"
            },
            {
              "internalType": "bool",
              "name": "reverse",
              "type": "bool"
            }
          ],
          "internalType": "struct PageRequest",
          "name": "pagination",
          "type": "tuple"
        }
      ],
      "name": "getSigningInfos",
      "outputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "validatorAddress",
              "type": "address"
            },
            {
              "internalType": "int64",
              "name": "startHeight",
              "type": "int64"
            },
            {
              "internalType": "int64",
              "name": "indexOffset",
              "type": "int64"
            },
            {
              "internalType": "int64",
              "name": "jailedUntil",
              "type": "int64"
            },
            {
              "internalType": "bool",
              "name": "tombstoned",
              "type": "bool"
            },
            {
              "internalType": "int64",
              "name": "missedBlocksCounter",
              "type": "int64"
            }
          ],
          "internalType": "struct SigningInfo[]",
          "name": "signingInfos",
          "type": "tuple[]"
        },
        {
          "components": [
            {
              "internalType": "bytes",
              "name": "nextKey",
              "type": "bytes"
            },
            {
              "internalType": "uint64",
              "name": "total",
              "type": "uint64"
            }
          ],
          "internalType": "struct PageResponse",
          "name": "pageResponse",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "validatorAddress",
          "type": "address"
        }
      ],
      "name": "unjail",
      "outputs": [
        {
          "internalType": "bool",
          "name": "success",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}