A smart contract that manages operators, challengers, and slashing conditions. It enables staking and challenging with penalties for misbehavior.
- Operator registration with ETH staking
- Challenger registration
- Challenge submission and resolution
- Stake withdrawal mechanism with safety delays
- Slashing penalties for misbehavior
- Register with minimum required stake
- Can be challenged for misbehavior
- Can withdraw stake with timelock
- Get deactivated if stake falls below minimum
- Register with minimum challenger stake
- Randomly selected for challenge eligibility
- Can submit evidence of operator misbehavior
- Resolved by contract owner
- Result in stake slashing if proven valid
- Node.js v16+
- npm/yarn
- Hardhat
# Install dependencies
npm install
# Run tests
npx hardhat test
# Check coverage
npx hardhat coverage
Operators
- Stake ETH to participate
- Must maintain minimum stake
- Can withdraw with delay period
- Subject to slashing penalties
Challengers
- Stake ETH to submit challenges
- Pseudo-randomly selected for eligibility
- Submit evidence of operator misbehavior
Admin
- Resolves challenges
- Controls slashing decisions
- Manages contract parameters
// Operator Functions
registerOperator() payable
deactivateOperator()
initiateWithdrawal(uint256 amount, bool isEmergency)
completeWithdrawal()
// Challenge Functions
registerChallenger() payable
submitChallenge(address operator, bytes32 evidence)
resolveChallenge(address operator, uint256 index, bool slashed)
- Minimum stake: 1 ETH
- Maximum stake: 10 ETH
- Withdrawal delay: 3 days
- Slashing penalty: 10%
npx hardhat test test/OperatorSlashing.test.js
Operators can escape slashing by leveraging the emergency withdrawal mechanism:
Exploit Sequence:
- Submit a challenge against an operator
- Immediately initiate emergency withdrawal
- Complete withdrawal before challenge resolution
- Avoid all slashing penalties
Proof of Concept Test:
it("should demonstrate slashing evasion", async function() {
// Submit challenge
await contract.submitChallenge(operator.address, evidence);
// Emergency withdrawal before resolution
await contract.connect(operator).initiateWithdrawal(fullStake, true);
await contract.connect(operator).completeWithdrawal();
// Challenge resolution now ineffective
await contract.connect(owner).resolveChallenge(operator.address, 0, true);
});
The test test/OperatorSlashingVulnerability.test.js
demonstrates how an operator can escape slashing by performing an emergency withdrawal before challenge resolution.
- Undermines challenger economic stake
- Allows operators to evade punishment
- Breaks core contract economic security model
- Implement challenge submission rate limits
- Add progressive stake penalties
- Create challenge resolution windows
- Prevent withdrawals during active challenges
- Enhance randomness generation mechanism
function submitChallenge(address operator, bytes32 evidence) external {
// Add per-challenger cooldown tracking
require(
block.timestamp >= lastChallengeTime[msg.sender] + CHALLENGE_COOLDOWN,
"Challenge too frequent"
);
// Implement more robust eligibility check
require(isRobustEligibleChallenger(msg.sender, operator), "Not eligible");
// Track and penalize repeated unsuccessful challenges
lastChallengeTime[msg.sender] = block.timestamp;
challengeAttempts[msg.sender]++;
}
sequenceDiagram
participant Operator
participant Contract
participant Challenger
participant Owner
Note over Contract: Registration Phase
Operator->>Contract: registerOperator() + stake
activate Contract
Contract-->>Operator: OperatorRegistered event
deactivate Contract
Challenger->>Contract: registerChallenger() + stake
activate Contract
Contract-->>Challenger: ChallengerRegistered event
deactivate Contract
Note over Contract: Challenge Phase
Challenger->>Contract: canChallenge(operator)
activate Contract
Contract-->>Challenger: true/false
deactivate Contract
Challenger->>Contract: submitChallenge(operator, evidence)
activate Contract
Contract-->>Challenger: ChallengeSubmitted event
deactivate Contract
Note over Contract: Resolution Phase
Owner->>Contract: resolveChallenge(operator, index, slashed)
activate Contract
alt Slashed
Contract->>Contract: Calculate penalty (10%)
Contract->>Contract: Reduce stake
opt Stake < minStake
Contract->>Contract: Deactivate operator
end
end
Contract-->>Owner: ChallengeResolved event
deactivate Contract
Note over Contract: Withdrawal Phase
Operator->>Contract: initiateWithdrawal(amount)
activate Contract
Contract-->>Operator: WithdrawalInitiated event
deactivate Contract
Note right of Contract: Delay Period
Operator->>Contract: completeWithdrawal()
activate Contract
Contract->>Contract: Validate delay
Contract->>Operator: Transfer stake
Contract-->>Operator: WithdrawalProcessed event
deactivate Contract
opt Cancel Withdrawal
Operator->>Contract: cancelWithdrawal()
activate Contract
Contract-->>Operator: WithdrawalCancelled event
deactivate Contract
end