Slashing
Walrus protects the network from misbehaving storage nodes through an onchain slashing mechanism. Committee members vote against a candidate node, and when a quorum of voting weight is reached, anyone can finalize the slashing to burn the candidate's accumulated commission. The penalty is paid by the operator and never by the delegators staking with that pool.
The mechanism deliberately mirrors the contract upgrade vote. Both actions are gated by the same governance_authorized entity on the node's staking pool, both use a quorum of more than two-thirds of the shards, and both reset their state at the boundary of each epoch. If you have voted on a contract upgrade before, the wallet, signing flow, and threshold semantics are identical.
Slashing is a serious action that permanently destroys WAL by burning the commission balance of the slashed pool. Only vote to slash a node when you have confirmed misbehavior such as repeated unavailability, withholding shards, or other clear protocol violations. Coordinate with other operators in the Walrus operator channels before initiating a vote, and prefer giving the candidate operator a chance to respond first.
How slashing works
The walrus::slashing module on Sui owns a shared SlashingManager object that holds an onchain table of open proposals. Each proposal is keyed by the candidate's node ID, which is the object ID of the candidate's StakingPool. A proposal tracks three pieces of state:
- The epoch in which the proposal was created or last refreshed. The proposal is only valid for execution in this epoch.
- The accumulated voting weight, expressed in shards. The current Walrus deployment has 1000 shards, so the quorum threshold is 667.
- The set of voter node IDs that have already cast a vote. The same node cannot vote twice for the same proposal.
The workflow has three stages:
-
Vote. Each voting node calls
slashing::vote_for_slashingfrom the wallet authorized for governance on that node's pool. The first vote creates the proposal; subsequent votes add to its weight. The candidate node itself can technically vote on its own slashing proposal, but in practice no honest operator would. -
Execute. Once the accumulated voting weight meets the quorum threshold, anyone (not only voters) can call
slashing::execute_slashingto burn the candidate's commission balance. The transaction removes the proposal from the table and atomically extracts and burns the WAL. -
Cleanup (optional). Stale proposals from past epochs remain in the
SlashingManagertable until they are explicitly removed. Anyone can callslashing::cleanup_slashing_proposalsto clear them, which slightly reduces onchain state.
Quorum and voting weights
A node's voting weight is its current shard count, which is stored on the staking pool and refreshed each epoch through committee selection. Both Mainnet and Testnet use 1000 shards, so a slashing proposal reaches quorum when at least 667 shards' worth of votes have been recorded against it. The Move source computes this through the formula 3 * weight >= 2 * n_shards + 1, which is the same threshold the rest of the Walrus protocol uses for finality.
There is no separate supermajority-of-stake requirement beyond the shard quorum. Because Walrus assigns shards to nodes in proportion to their stake (subject to the active-set caps), a quorum of shards is a faithful proxy for a quorum of honest operators.
Epoch-bound proposals
A proposal must collect a quorum and be executed within a single epoch. When the epoch advances, the next vote on an existing proposal replaces it with a fresh one for the new epoch: the voting weight resets to zero and the prior voters are cleared. The execute path checks the proposal's stored epoch against the staking object's current epoch and aborts if they differ.
This design has two important consequences:
- Votes you collect near the end of an epoch are wasted if the epoch boundary passes before quorum is reached. On Mainnet, epochs are two weeks long, so there is generous time. On Testnet, epochs are one day, so coordinate quickly.
- The same proposal can be re-opened in the next epoch, but the voting starts from scratch. Operators who voted in the previous epoch must vote again.
If you are coordinating a vote, plan to gather a quorum well before the end of the epoch and announce a target execute time so a single party submits the finalization transaction.
What gets burned
execute_slashing calls into the slashed node's staking pool, extracts the full accumulated commission balance, and burns it through wal::ProtectedTreasury. The total WAL supply visible on the ProtectedTreasury object decreases by exactly that amount. The slashing affects only the operator's commission. Staked WAL belonging to delegators is not touched, withdrawals continue to be honored, and the node remains a member of the committee until it is deactivated through the normal lifecycle.
Because the penalty is bounded by the accumulated commission, the deterrent grows with how long an operator has gone without collecting commission. Operators who collect frequently have less at risk, but the slashing event is still public and signals operator quality to delegators.
Preparing to vote
Before you submit a vote, confirm three things:
-
Your
governance_authorizedwallet is the one you intend to use. The vote checks the transaction sender (or a controlling object) against thegovernance_authorizedfield on your pool. If you have not yet designated a dedicated governance wallet, follow Update the commission receiver and entity authorized for governance first. The hot wallet on the storage node machine should not hold this authorization. -
The wallet holds enough SUI for gas. The vote and execute transactions are small, so a few SUI is more than enough.
-
The Sui CLI is configured for the correct network. Run
sui client switch --env mainnetorsui client switch --env testnetand verify withsui client active-env.
Look up the candidate node ID
The candidate node ID is the object ID of the candidate's StakingPool. You can find it through any of the following:
- The Walrus staking app lists every node along with its ID.
- Other operators in the slashing discussion typically share the ID directly.
- The
walrus info allcommand lists committee members. The node ID is the same as theStakingPoolobject ID.
Walrus onchain object IDs
The IDs in the table below are the live shared objects on each network. You can substitute them into the commands later on this page directly. The values are stable across contract upgrades because the SlashingManager, Staking, and ProtectedTreasury are shared objects rather than packages.
- Mainnet
- Testnet
| Object | ID |
|---|---|
| Walrus package (current) | 0x98da433aa0139512c210597b1c5e3df6cd121d8d77f8652691bb66fadfc8aa1b |
| Walrus package (original) | 0xfdc88f7d7cf30afab2f82e8380d11ee8f70efb90e863d1de8616fae1bb09ea77 |
| Staking object | 0x10b9d30c28448939ce6c4d6c6e0ffce4a7f8a4ada8248bdad09ef8b70e4a3904 |
| System object | 0x2134d52768ea07e8c43570ef975eb3e4c27a39fa6396bef985b5abc58d03ddd2 |
| SlashingManager | 0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109 |
WAL ProtectedTreasury | 0x3a19b564728b56df21e0e2bd3582b2be00be992e27b44faf30611e15ae6eac91 |
| Object | ID |
|---|---|
| Walrus package (current) | 0x849e95d2718938d66c37fb91df76d72f78526c1864c339bac415ce8ecda2d8cc |
| Walrus package (original) | 0xd84704c17fc870b8764832c535aa6b11f21a95cd6f5bb38a9b07d2cf42220c66 |
| Staking object | 0xbe46180321c30aab2f8b3501e24048377287fa708018a5b7c2792b35fe339ee3 |
| System object | 0x6c2547cbbc38025cf3adac45f63cb0a8d12ecf777cdc75a4971612bf97fdf6af |
| SlashingManager | 0xba59eb74de5c7707f830415ff9f64261d107d790bb65ab5835918eeac62daaee |
WAL ProtectedTreasury | 0x1dcbbb4fc81c39901be78fcaa6ee0be688512e5ceef11713ef7fe1e6f6a3131b |
The Walrus package ID changes after every contract upgrade. To get the latest package ID, read the package_id field on the Staking object on Suiscan and use whatever value is there. The original package address is the one used for Move type identifiers and does not change.
Vote on a slashing proposal
There is no dedicated walrus node-admin subcommand for slashing, so you submit the vote directly through the Sui CLI as a programmable transaction block (PTB). A PTB is required because vote_for_slashing takes an Authenticated argument that must be constructed onchain in the same transaction. You cannot pass an Authenticated value from the command line.
The PTB has two move calls:
walrus::auth::authenticate_senderconstructs anAuthenticatedvalue tied to the transaction sender.walrus::slashing::vote_for_slashingconsumes that value, checks it against your pool'sgovernance_authorized, and records the vote.
If your governance authorization is delegated to an object rather than an address, replace the first call with walrus::auth::authenticate_with_object and pass the controlling object as its argument.
- Mainnet
- Testnet
$ NODE_ID= # Your storage node ID (the StakingPool you operate).
$ CANDIDATE_NODE_ID= # The StakingPool ID of the candidate to slash.
$ WALRUS_PACKAGE=0x98da433aa0139512c210597b1c5e3df6cd121d8d77f8652691bb66fadfc8aa1b
$ SLASHING_MANAGER=0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109
$ STAKING_OBJECT=0x10b9d30c28448939ce6c4d6c6e0ffce4a7f8a4ada8248bdad09ef8b70e4a3904
$ sui client ptb \
--move-call $WALRUS_PACKAGE::auth::authenticate_sender \
--assign auth \
--move-call $WALRUS_PACKAGE::slashing::vote_for_slashing \
@$SLASHING_MANAGER @$STAKING_OBJECT auth @$NODE_ID @$CANDIDATE_NODE_ID \
--gas-budget 100000000
$ NODE_ID= # Your storage node ID (the StakingPool you operate).
$ CANDIDATE_NODE_ID= # The StakingPool ID of the candidate to slash.
$ WALRUS_PACKAGE=0x849e95d2718938d66c37fb91df76d72f78526c1864c339bac415ce8ecda2d8cc
$ SLASHING_MANAGER=0xba59eb74de5c7707f830415ff9f64261d107d790bb65ab5835918eeac62daaee
$ STAKING_OBJECT=0xbe46180321c30aab2f8b3501e24048377287fa708018a5b7c2792b35fe339ee3
$ sui client ptb \
--move-call $WALRUS_PACKAGE::auth::authenticate_sender \
--assign auth \
--move-call $WALRUS_PACKAGE::slashing::vote_for_slashing \
@$SLASHING_MANAGER @$STAKING_OBJECT auth @$NODE_ID @$CANDIDATE_NODE_ID \
--gas-budget 100000000
If the transaction succeeds, your vote has been recorded. The Sui CLI prints the transaction digest, which you should share with other operators so they can confirm the vote landed.
Voting through an authorizing object
If your governance_authorized is set to an object (for example, a multisig admin cap held in a secure wallet), replace the first move call. The example below assumes a generic object of type T that you own; substitute the concrete type tag for your authorizing object.
$ AUTH_OBJECT= # The object ID that authorizes governance for your pool.
$ AUTH_OBJECT_TYPE= # The fully qualified Move type of the object.
$ sui client ptb \
--move-call $WALRUS_PACKAGE::auth::authenticate_with_object \
"<$AUTH_OBJECT_TYPE>" @$AUTH_OBJECT \
--assign auth \
--move-call $WALRUS_PACKAGE::slashing::vote_for_slashing \
@$SLASHING_MANAGER @$STAKING_OBJECT auth @$NODE_ID @$CANDIDATE_NODE_ID \
--gas-budget 100000000
Vote abort codes
The vote transaction aborts cleanly when something is wrong with the request. The abort codes you might see are:
| Code | Name | Meaning |
|---|---|---|
| 0 | ENotAuthorized | The sender (or authorizing object) does not match the governance_authorized set on your pool. Check the pool on Suiscan. |
| 1 | EDuplicateVote | Your node already voted on the current proposal. If you intended to revoke and re-cast, wait for the next epoch to refresh. |
| 2 | ENoProposalForNode | Only emitted by execute_slashing or cleanup_slashing_proposals. You should not see this from a vote transaction. |
Sui prints the abort code along with the module name in the failure output, so you can match the value above directly. The error names live in walrus-sui/types/move_errors.rs, where the constants are auto-generated from the Move source.
Monitoring the proposal
You can inspect the live state of the proposal at any time by reading the SlashingManager object. Either look it up directly on Suiscan using the object ID from the table above, or call sui client object from a shell:
$ sui client object 0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109 --json
The object exposes a slashing_candidates table. Each entry is a SlashingProposal with epoch, node_id, voting_weight, and voters fields. You can confirm:
- Whether a proposal for the candidate exists in the current epoch.
- How many shards have voted so far, and how far from the 667-shard quorum.
- Which voter node IDs have already cast a vote.
If your wallet has already cast a vote but the proposal does not show your node ID in voters, the transaction might have aborted silently in your wallet UI. Re-check the digest on Suiscan to confirm.
Execute the slashing
Once the accumulated voting weight has reached or exceeded the quorum threshold in the current epoch, anyone can finalize the slashing. The execute call does not take an Authenticated argument, so a plain sui client call works.
Coordinate with the other operators so a single party submits the execute transaction. Multiple operators racing to execute waste gas on the losing transactions, which all abort with ENoProposalForNode after the first one succeeds.
- Mainnet
- Testnet
$ CANDIDATE_NODE_ID= # The StakingPool ID of the candidate being slashed.
$ WALRUS_PACKAGE=0x98da433aa0139512c210597b1c5e3df6cd121d8d77f8652691bb66fadfc8aa1b
$ SLASHING_MANAGER=0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109
$ STAKING_OBJECT=0x10b9d30c28448939ce6c4d6c6e0ffce4a7f8a4ada8248bdad09ef8b70e4a3904
$ TREASURY_OBJECT=0x3a19b564728b56df21e0e2bd3582b2be00be992e27b44faf30611e15ae6eac91
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function execute_slashing \
--args $SLASHING_MANAGER $STAKING_OBJECT $TREASURY_OBJECT $CANDIDATE_NODE_ID \
--gas-budget 100000000
$ CANDIDATE_NODE_ID= # The StakingPool ID of the candidate being slashed.
$ WALRUS_PACKAGE=0x849e95d2718938d66c37fb91df76d72f78526c1864c339bac415ce8ecda2d8cc
$ SLASHING_MANAGER=0xba59eb74de5c7707f830415ff9f64261d107d790bb65ab5835918eeac62daaee
$ STAKING_OBJECT=0xbe46180321c30aab2f8b3501e24048377287fa708018a5b7c2792b35fe339ee3
$ TREASURY_OBJECT=0x1dcbbb4fc81c39901be78fcaa6ee0be688512e5ceef11713ef7fe1e6f6a3131b
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function execute_slashing \
--args $SLASHING_MANAGER $STAKING_OBJECT $TREASURY_OBJECT $CANDIDATE_NODE_ID \
--gas-budget 100000000
Execute abort codes
| Code | Name | Meaning |
|---|---|---|
| 2 | ENoProposalForNode | No open proposal exists for the candidate. The proposal might have been executed already, or no votes have been cast. |
| 3 | EWrongEpoch | The proposal was created in a past epoch and no fresh vote has been recorded in the current epoch. Restart the vote. |
| 4 | ENotEnoughVotes | The accumulated voting weight is below 667. Collect more votes before retrying. |
Verify the result
After the execute transaction lands, confirm three things:
- The slashed pool's
commissionbalance is zero. Look up the candidate'sStakingPoolobject on Suiscan and read thecommissionfield, or runsui client object <CANDIDATE_NODE_ID> --jsonand grep forcommission. - The total WAL supply has decreased by the burned amount. Read the
ProtectedTreasuryobject on Suiscan, or call the read-onlywal::wal::total_supplyfunction. - The proposal has been removed from the
SlashingManagertable. The candidate's entry should no longer appear inslashing_candidates.
Sui also emits standard transaction effects for the execute call, so an offchain indexer that monitors the SlashingManager object's transaction history can correlate the digest with the slashing event. There is no dedicated event type for slashing in the current contract.
Clean up stale proposals
Proposals from previous epochs remain in the SlashingManager table until they are explicitly removed. Cleanup is permissionless: any wallet can call cleanup_slashing_proposals with a batch of candidate node IDs. The function removes only proposals whose stored epoch is strictly less than the current epoch. Active proposals in the current epoch are left untouched. Calling cleanup on a node ID that has no entry is a no-op.
Batching reduces gas. You can include any number of candidate IDs in the same call, subject to Sui's per-transaction gas budget.
- Mainnet
- Testnet
$ WALRUS_PACKAGE=0x98da433aa0139512c210597b1c5e3df6cd121d8d77f8652691bb66fadfc8aa1b
$ SLASHING_MANAGER=0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109
$ STAKING_OBJECT=0x10b9d30c28448939ce6c4d6c6e0ffce4a7f8a4ada8248bdad09ef8b70e4a3904
$ CANDIDATES='["0x...","0x..."]' # JSON array of candidate node IDs to clean up.
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function cleanup_slashing_proposals \
--args $SLASHING_MANAGER $STAKING_OBJECT "$CANDIDATES" \
--gas-budget 100000000
$ WALRUS_PACKAGE=0x849e95d2718938d66c37fb91df76d72f78526c1864c339bac415ce8ecda2d8cc
$ SLASHING_MANAGER=0xba59eb74de5c7707f830415ff9f64261d107d790bb65ab5835918eeac62daaee
$ STAKING_OBJECT=0xbe46180321c30aab2f8b3501e24048377287fa708018a5b7c2792b35fe339ee3
$ CANDIDATES='["0x...","0x..."]' # JSON array of candidate node IDs to clean up.
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function cleanup_slashing_proposals \
--args $SLASHING_MANAGER $STAKING_OBJECT "$CANDIDATES" \
--gas-budget 100000000
For a worked walkthrough that ties the vote, execute, and cleanup commands into a single timeline, see Slashing walkthrough.
Operational practices
Treat slashing as a coordination problem with onchain enforcement. The contract enforces quorum and authorization, but the social process around it is what keeps the network healthy. The following practices help keep that process predictable:
- Use a dedicated governance wallet. The hot wallet on your storage node machine should not be authorized for governance. If you have not yet set this up, the commission and governance page walks through it. The same authorization is used for contract upgrade votes and slashing votes.
- Coordinate in advance. Reach out to other operators in the Walrus operator channels before voting. A slashing event is high-impact and operators expect to discuss evidence before voting.
- Give the candidate a chance to respond. Many apparent misbehavior signals (a node returning stale data, missing shards, refusing reads) have benign explanations like a crashloop or a misconfigured upgrade. A quick message often resolves the issue.
- Aim for a single execute. Once you and your peers agree quorum is reached, choose one operator to submit the execute transaction so the others do not waste gas on aborted races.
- Plan around the epoch boundary. On Mainnet, epochs are two weeks long, so timing is rarely tight. On Testnet, epochs are one day, so a slashing vote must move quickly or restart at the boundary.
- Document the rationale. Slashing is public and permanent. A short post-mortem describing what was observed, what was tried, and what was decided helps the community understand the event and build trust in the process.
Frequently asked questions
The following answers cover the questions operators ask most often when preparing to vote.
Can a node revoke its own vote?
No. Once a vote is cast, it remains until the epoch advances and the proposal is refreshed (or replaced by a new one). If you cast a vote in error, your only recovery is to wait for the next epoch.
What happens if the candidate's commission balance is empty?
execute_slashing still succeeds. It extracts whatever balance is currently in the commission pool, including zero, and burns it. The proposal is removed from the table. In this case the onchain WAL supply does not decrease, but the slashing event has still been recorded by the transaction effect, and the social signal stands.
Does slashing affect the candidate's stake or delegators?
No. Slashing burns only the operator's accumulated commission. Staked WAL belonging to delegators is unaffected. Delegators can continue to withdraw stake, the node remains a committee member until separately deactivated, and shard assignments do not change as a result of slashing.
Can the candidate vote on its own slashing?
The contract does not prevent self-voting: the governance_authorized check is the only gate. In practice, an honest operator would not vote on their own slashing, and the candidate's voting weight alone is far below quorum.
Is there a slashing event in the contract?
The current contract does not emit a dedicated event for slashing. The transaction effects (object changes on the SlashingManager, the StakingPool, and the ProtectedTreasury) are the canonical record. If you need to track slashing programmatically, monitor transactions that touch the SlashingManager shared object and filter by the slashing module's execute_slashing function.
What stops malicious operators from spamming slashing votes?
Votes are gated by governance_authorized, which means a vote can only come from an existing committee member's governance wallet. A malicious minority can create proposals, but they cannot reach quorum without honest operators joining. Stale proposals occupy a small amount of onchain storage and can be cleaned up permissionlessly.
Can I see slashing activity in the staking app?
Not at the time of writing. A staking app surface for slashing is planned once the mechanism is exercised more broadly. For now, use Suiscan and the sui client object command to inspect the SlashingManager directly.
Reference
- Move source:
contracts/walrus/sources/slashing.movein the Walrus repository. - Related: Commission and Governance, which covers the
governance_authorizedsetup and the parallel contract upgrade voting flow. - Sui PTB reference: see the Sui developer documentation for
sui client ptbsyntax.