Slashing Walkthrough
A worked example of a slashing event on Mainnet, with the concrete vote, execute, and cleanup commands operators run end to end.
A hypothetical slashing event on Mainnet shows how the pieces fit together. Read Slashing first for the conceptual model and the object IDs. The commands below are the ones operators actually run, using the live Mainnet object IDs.
The scenario:
- The candidate is a misbehaving storage node with
StakingPoolID0xCANDIDATE_NODE_ID(substitute the real ID before running anything). - Operators have agreed to slash after observing the behavior and reaching consensus through the Walrus operator channels.
- The current epoch is
N. Mainnet epochs are two weeks long, so there is generous time to collect votes. - The voting operator's storage node has
StakingPoolID$NODE_IDand shard countW.
The Mainnet object IDs you reference throughout:
$ WALRUS_PACKAGE=0x98da433aa0139512c210597b1c5e3df6cd121d8d77f8652691bb66fadfc8aa1b
$ SLASHING_MANAGER=0xfe343f7b72f67a1eca9bfc9dff0dac0520e11dca3bda5a0ea32816af6f722109
$ STAKING_OBJECT=0x10b9d30c28448939ce6c4d6c6e0ffce4a7f8a4ada8248bdad09ef8b70e4a3904
$ TREASURY_OBJECT=0x3a19b564728b56df21e0e2bd3582b2be00be992e27b44faf30611e15ae6eac91
$ CANDIDATE_NODE_ID=0xCANDIDATE_NODE_ID # Replace with the real candidate ID.
$ NODE_ID=0xYOUR_NODE_ID # Replace with your own StakingPool ID.
Day 1 of epoch N: how to cast the first vote
The first operator to act submits the vote programmable transaction block (PTB) from the wallet that is governance_authorized on their storage node's StakingPool. The PTB constructs an Authenticated value tied to the sender, then passes it to vote_for_slashing.
$ 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
After the transaction lands, the SlashingManager table has a new entry keyed by the candidate's node ID. The entry's epoch is N, the voting_weight equals the first voter's shard count W, and the voters set contains a single ID.
Confirm the new proposal by reading the SlashingManager object:
$ sui client object $SLASHING_MANAGER --json
The JSON output contains a slashing_candidates table. Look for an entry whose key matches $CANDIDATE_NODE_ID.
Days 1 to 10 of epoch N: how to collect quorum
Other operators repeat the same vote PTB from their own governance wallets. Each new vote adds the voter's shard count to voting_weight and appends the voter's node ID to the voters set. The contract aborts with EDuplicateVote if an operator tries to vote twice on the same proposal in the same epoch.
Operators can monitor progress by re-running the read command above and observing voting_weight climb toward 667 (the quorum on a 1000-shard committee).
How to execute the slashing after quorum
As soon as voting_weight >= 667, the proposal is finalizable. Operators pick one party to submit the execute call so the others do not waste gas on aborted races. The execute call does not take an Authenticated argument, so a plain sui client call works.
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function execute_slashing \
--args $SLASHING_MANAGER $STAKING_OBJECT $TREASURY_OBJECT $CANDIDATE_NODE_ID \
--gas-budget 100000000
The transaction does three things atomically:
- Removes the proposal from the
SlashingManagertable. - Extracts the full commission balance from the candidate's
StakingPool. - Burns the extracted WAL through the
ProtectedTreasury.
Verify the outcome:
$ sui client object $CANDIDATE_NODE_ID --json | grep -A1 commission
$ sui client object $TREASURY_OBJECT --json
$ sui client object $SLASHING_MANAGER --json
You see the candidate's commission field at zero, the WAL total_supply lower by the burned amount, and no entry for the candidate in slashing_candidates.
How to clean up dangling proposals from prior epochs
If the operators do not reach 667 votes by the end of epoch N, the next vote in epoch N+1 resets the proposal: the first vote in N+1 creates a fresh proposal with voting_weight starting from zero, and the contract discards all prior voters. The vote must start over.
If the operators abandoned a proposal and chose not to restart it in the new epoch, the entry remains in the SlashingManager table until an explicit cleanup. Anyone can clear stale entries:
$ CANDIDATES='["0xCANDIDATE_NODE_ID","0xANOTHER_ID"]'
$ sui client call \
--package $WALRUS_PACKAGE \
--module slashing \
--function cleanup_slashing_proposals \
--args $SLASHING_MANAGER $STAKING_OBJECT "$CANDIDATES" \
--gas-budget 100000000
The function removes only proposals whose stored epoch is strictly less than the current epoch. It leaves active proposals untouched and silently skips an entry that does not exist.
Which functions cover the slashing lifecycle?
The same three primitives cover the full lifecycle:
| Action | Function | Authorization |
|---|---|---|
| Vote | slashing::vote_for_slashing | governance_authorized on the voter's pool |
| Execute | slashing::execute_slashing | Permissionless after quorum |
| Cleanup | slashing::cleanup_slashing_proposals | Permissionless |
For abort codes, monitoring, FAQs, and the parallel with the contract upgrade vote, see the main Slashing page.
What happens if 2 operators execute at the same time
Only one execute_slashing call succeeds for each proposal. The function first checks that a proposal exists for the candidate. It then removes that proposal from the SlashingManager table, checks the epoch and quorum, and burns the commission. If a second operator submits the same call after the first one executes, the second call finds no proposal for the candidate and aborts with ENoProposalForNode. The candidate's commission burns only once, and the abort does not change the outcome of the first call.