SaintDurbin is a sophisticated smart contract system designed for the Bittensor EVM network (Subtensor) that manages the distribution of staking yields to 16 predetermined recipients while protecting the principal amount. The contract features automatic validator switching to maintain optimal staking returns and includes comprehensive security mechanisms.
- Immutable Architecture: Recipients and proportions are fixed at deployment
- Autonomous Operation: Daily yield distribution with minimal external intervention
- Principal Protection: Advanced algorithms prevent distribution of staked principal
- Validator Management: Automatic switching when validators lose permits or become inactive
- Emergency Controls: Time-locked emergency drain with multisig protection
- Gas Optimized: Efficient implementation minimizing transaction costs
- Architecture
- Core Features
- Security Model
- Business Logic
- Technical Implementation
- Testing
- Deployment
- Operations
- Development
- Audit Information
┌─────────────────────────────────────────────────────────────────┐
│ Bittensor Subtensor EVM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ IStaking (0x805) │ │ IMetagraph (0x802)│ │
│ │ Precompile │ │ Precompile │ │
│ └──────────────────┘ └──────────────────┘ │
│ ▲ ▲ │
│ │ │ │
│ ┌────────┴──────────────────────────────┴────────────┐ │
│ │ SaintDurbin.sol Contract │ │
│ │ │ │
│ │ • Yield Distribution Engine │ │
│ │ • Validator Management System │ │
│ │ • Principal Protection Mechanism │ │
│ │ • Emergency Drain Controls │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌───────────────────────────┴─────────────────────────┐ │
│ │ External Interactions │ │
│ │ │ │
│ │ • GitHub Actions (Daily Automation) │ │
│ │ • Emergency Operator (Time-locked) │ │
│ │ • Public Functions (Permissionless) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
src/
├── SaintDurbin.sol # Main contract implementation
└── interfaces/
├── IStakingV2.sol # Bittensor staking interface (0x805)
└── IMetagraph.sol # Bittensor metagraph interface (0x802)
test/
├── SaintDurbin.t.sol # Core functionality tests
├── SaintDurbinPrincipal.t.sol # Principal protection tests
├── SaintDurbinEmergency.t.sol # Emergency mechanism tests
├── SaintDurbinValidatorSwitch.t.sol # Validator switching tests
├── SaintDurbin_ConstructorTests.sol # Constructor validation
└── mocks/ # Mock implementations for testing
The contract distributes yields to exactly 16 recipients with fixed proportions:
Recipient | Proportion | Percentage |
---|---|---|
Sam | 100 | 1% |
WSL | 100 | 1% |
Paper | 500 | 5% |
Florian | 100 | 1% |
3 recipients | 100 each | 1% each |
3 recipients | 300 each | 3% each |
3 recipients | 1000 each | 10% each |
2 recipients | 1500 each | 15% each |
1 recipient | 2000 | 20% |
Total | 10,000 | 100% |
- Frequency: Minimum 7,200 blocks (~24 hours at 12s/block)
- Trigger: Permissionless - anyone can call
executeTransfer()
- Amount: Only distributes staking yields, never principal
- Automation: GitHub Actions cron job for daily execution
The contract employs sophisticated algorithms to detect and protect principal:
// Rate-based detection
if (currentRate > lastRewardRate * 2) {
// Principal addition detected
}
// Absolute detection
if (availableYield > lastPaymentAmount * 3) {
// Unusual yield increase detected
}
The contract automatically monitors and switches validators:
- Check Frequency: Every 100 blocks during distribution
- Switch Triggers:
- Validator loses permit
- Validator becomes inactive
- Hotkey/UID mismatch detected
- Selection Criteria: Highest combination of stake and dividends
Three-stage security system:
- Request: Emergency operator initiates drain
- Timelock: 24-hour waiting period
- Execution: Transfer to Polkadot multisig address
Security Features:
- Cannot be executed immediately
- Cancellable by operator or anyone after 48 hours
- Destination is 2/3 multisig on Polkadot side
- Full event logging for transparency
Function | Access | Description |
---|---|---|
executeTransfer() |
Public | Anyone can trigger distribution |
checkAndSwitchValidator() |
Public | Anyone can trigger validator check |
requestEmergencyDrain() |
Emergency Operator | Initiate drain with timelock |
executeEmergencyDrain() |
Emergency Operator | Execute after timelock |
cancelEmergencyDrain() |
Operator/Public* | Cancel drain request |
*Public can cancel after 48 hours
-
Reentrancy Protection
nonReentrant
modifier on critical functions- Checks-effects-interactions pattern
- State updates before external calls
-
Input Validation
- Constructor validates all parameters
- Proportions must sum to exactly 10,000
- Address and hotkey validation
-
Immutability
- Recipients cannot be changed
- Proportions are fixed
- Core configuration is immutable
-
Error Handling
- Custom errors for gas efficiency
- Comprehensive error messages
- Graceful failure handling
graph TD
A[Start executeTransfer] --> B{Can Execute?}
B -->|No| C[Revert: Too Soon]
B -->|Yes| D{Check Validator}
D --> E[Calculate Yield]
E --> F{Detect Principal?}
F -->|Yes| G[Lock Principal]
F -->|No| H[Use Full Yield]
G --> I[Use Previous Amount]
H --> J[Distribute to Recipients]
I --> J
J --> K[Update State]
K --> L[Emit Events]
graph TD
A[Check Current Validator] --> B{Valid?}
B -->|Yes| C[Continue]
B -->|No| D[Find Best Validator]
D --> E[Calculate Scores]
E --> F[Select Highest Score]
F --> G[Update State]
G --> H[Move Stake]
H -->|Success| I[Emit Success]
H -->|Fail| J[Rollback State]
J --> K[Emit Failure]
The contract uses a dual-method approach:
- Rate Analysis: Compares current reward rate to historical rate
- Absolute Comparison: Checks if yield is unusually high
// Simplified logic
if (yieldRate > historicalRate * 2 || yield > lastPayment * 3) {
principalDetected = true;
lockAdditionalPrincipal();
useHistoricalPaymentAmount();
}
function executeTransfer() external nonReentrant
Main distribution function that:
- Validates timing constraints
- Checks validator status
- Calculates available yield
- Distributes to recipients
- Updates tracking variables
function checkAndSwitchValidator() external
function _checkAndSwitchValidator() internal
function _switchToNewValidator(string memory reason) internal
Manages validator selection and switching with automatic fallback.
function requestEmergencyDrain() external onlyEmergencyOperator
function executeEmergencyDrain() external onlyEmergencyOperator nonReentrant
function cancelEmergencyDrain() external
Time-locked emergency drain system with multiple safety checks.
// Immutable Configuration
IStaking public immutable staking; // 0x805
IMetagraph public immutable metagraph; // 0x802
bytes32 public immutable thisSs58PublicKey;
uint16 public immutable netuid;
address public immutable emergencyOperator;
bytes32 public immutable drainSs58Address;
// Mutable State
bytes32 public currentValidatorHotkey;
uint16 public currentValidatorUid;
uint256 public principalLocked;
uint256 public emergencyDrainRequestedAt;
// Tracking
uint256 public previousBalance;
uint256 public lastTransferBlock;
uint256 public lastRewardRate;
uint256 public lastPaymentAmount;
uint256 public lastValidatorCheckBlock;
event StakeTransferred(uint256 totalAmount, uint256 newBalance);
event RecipientTransfer(bytes32 indexed coldkey, uint256 amount, uint256 proportion);
event PrincipalDetected(uint256 amount, uint256 totalPrincipal);
event ValidatorSwitched(bytes32 indexed oldHotkey, bytes32 indexed newHotkey, uint16 newUid, string reason);
event ValidatorCheckFailed(string reason);
event EmergencyDrainRequested(uint256 executionTime);
event EmergencyDrainExecuted(bytes32 indexed drainAddress, uint256 amount);
event TransferFailed(bytes32 indexed coldkey, uint256 amount, string reason);
- Cached Array Length: Prevents repeated SLOAD operations
- Batch Operations: Minimizes state changes
- Custom Errors: More gas-efficient than require strings
- Event Logging: Off-chain monitoring instead of storage
Category | Coverage | Description |
---|---|---|
Unit Tests | 100% | All functions and branches |
Integration | 95% | Contract interactions |
Edge Cases | 90% | Boundary conditions |
Security | 100% | Attack vectors |
# Run all tests
./test-all.sh
# Run with integration tests
./test-all.sh --integration
# Run specific test suite
forge test --match-contract SaintDurbinValidatorSwitch -vvv
# Run with gas report
forge test --gas-report
# Run with coverage
forge coverage
-
Unit Tests (Foundry)
- Constructor validation
- Distribution logic
- Principal protection
- Validator switching
- Emergency mechanisms
-
Integration Tests (JavaScript + Local Chain)
- End-to-end distribution flow
- Validator status changes
- Script automation
- Multi-cycle operations
-
Security Tests
- Reentrancy attacks
- Principal extraction attempts
- Timelock bypasses
- Access control violations
# Run Slither
just slither
# Run Aderyn
aderyn .
# Run mythril
myth analyze src/SaintDurbin.sol
-
Environment Configuration
cp .env.example .env # Edit .env with your parameters
-
Configuration File (
script/config.json
){ "emergencyOperator": "0x...", "drainSs58Address": "0x...", "validatorHotkey": "0x...", "validatorUid": 0, "thisSs58PublicKey": "0x...", "netuid": 0, "recipients": [ {"coldkey": "0x...", "proportion": 100}, // ... 16 total recipients ] }
-
Validation
- Verify proportions sum to exactly 10,000
- Confirm all addresses are valid
- Check initial validator is active
forge script script/DeploySaintDurbin.s.sol:DeploySaintDurbin \
--rpc-url $BITTENSOR_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--legacy
- Verify contract on explorer
- Confirm initial principal amount
- Test view functions
- Verify recipient configuration
- Check validator status
Automated via GitHub Actions:
name: Daily Yield Distribution
on:
schedule:
- cron: '0 0 * * *' # Daily at 00:00 UTC
Manual trigger:
cd scripts
node distribute.js
Check current validator:
node check-validator.js
Force validator switch:
node check-validator.js --switch
-
Initiate Drain
cast send $CONTRACT "requestEmergencyDrain()" \ --private-key $EMERGENCY_KEY
-
Wait 24 hours
-
Execute Drain
cast send $CONTRACT "executeEmergencyDrain()" \ --private-key $EMERGENCY_KEY
Key metrics to monitor:
- Daily distribution success
- Validator status changes
- Principal amount stability
- Gas costs
- Event logs
# Clone repository
git clone --recursive https://github.com/distributedstatemachine/st_durbin
cd st_durbin
# Install dependencies
forge install
cd scripts && npm install && cd ..
# Run tests
forge test
# Start local fork
anvil --fork-url $BITTENSOR_RPC_URL
# Deploy locally
forge script script/DeploySaintDurbin.s.sol \
--rpc-url http://localhost:8545 \
--private-key $ANVIL_KEY \
--broadcast
- Solidity: Follow Solidity Style Guide
- Comments: NatSpec for all public functions
- Tests: Descriptive test names
- Git: Conventional commits
- Fork the repository
- Create feature branch
- Write comprehensive tests
- Ensure all tests pass
- Submit pull request
In Scope:
src/SaintDurbin.sol
src/interfaces/IStakingV2.sol
src/interfaces/IMetagraph.sol
- Distribution logic
- Principal protection
- Validator management
- Emergency mechanisms
Out of Scope:
- Bittensor precompiles
- External automation scripts
- Deployment configuration
- Reentrancy: Protected via modifiers and patterns
- Access Control: Minimal privileged functions
- Integer Overflow: Solidity 0.8.20 built-in protection
- Principal Safety: Multiple detection mechanisms
- Validator Risk: Automatic switching with fallbacks
- Depends on Bittensor precompile availability
- Validator switching limited by available validators
- Emergency drain requires Polkadot multisig
- Distribution frequency limited by block interval
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Bittensor Foundation for the subtensor infrastructure
- OpenZeppelin for security best practices
- Foundry team for the development framework
For detailed technical specifications and audit information, please refer to SPEC.md