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.
Transactions
2000 + (30 × bytes of input)
Queries
1000 + (3 × bytes of input)
Transaction Methods
unjail
unjailAllows 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
getSigningInfosReturns 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
getParamsReturns 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"
}
]
}