Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "subtensor_chain"]
path = subtensor_chain
url = https://github.com/opentensor/subtensor.git
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 77041d
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"type": "comonjs",
"dependencies": {
"@polkadot-api/descriptors": "^0.0.1"
}
Expand Down
34 changes: 28 additions & 6 deletions script/DeploySaintDurbin.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ contract DeploySaintDurbin is Script {
function run() external {
// Configuration - ALL MUST BE SET BEFORE DEPLOYMENT
address emergencyOperator = vm.envAddress("EMERGENCY_OPERATOR");
address drainAddress = vm.envAddress("DRAIN_ADDRESS");
bytes32 drainSs58Address = vm.envBytes32("DRAIN_SS58_ADDRESS");
bytes32 validatorHotkey = vm.envBytes32("VALIDATOR_HOTKEY");
uint16 validatorUid = uint16(vm.envUint("VALIDATOR_UID"));
bytes32 thisSs58PublicKey = vm.envBytes32("CONTRACT_SS58_KEY");
uint16 netuid = uint16(vm.envUint("NETUID"));
bytes16 validatorHotkeyHash = bytes16(
vm.envBytes32("VALIDATOR_HOTKEY_HASH")
);

// Recipients configuration
bytes32[] memory recipientColdkeys = new bytes32[](16);
Expand All @@ -32,7 +36,9 @@ contract DeploySaintDurbin is Script {
// Remaining 12 wallets (92% total - uneven distribution)
// Load from environment
for (uint256 i = 4; i < 16; i++) {
recipientColdkeys[i] = vm.envBytes32(string.concat("RECIPIENT_", vm.toString(i)));
recipientColdkeys[i] = vm.envBytes32(
string.concat("RECIPIENT_", vm.toString(i))
);
}

// Uneven distribution of remaining 92%
Expand All @@ -59,6 +65,7 @@ contract DeploySaintDurbin is Script {
// Log configuration
console.log("Deploying SaintDurbin with:");
console.log("Emergency Operator:", emergencyOperator);
console.log("Drain Address:", drainAddress);
console.log("Drain SS58 Address:", vm.toString(drainSs58Address));
console.log("Validator Hotkey:", vm.toString(validatorHotkey));
console.log("Validator UID:", validatorUid);
Expand All @@ -71,11 +78,13 @@ contract DeploySaintDurbin is Script {

SaintDurbin saintDurbin = new SaintDurbin(
emergencyOperator,
drainAddress,
drainSs58Address,
validatorHotkey,
validatorUid,
thisSs58PublicKey,
netuid,
validatorHotkeyHash,
recipientColdkeys,
proportions
);
Expand All @@ -87,17 +96,30 @@ contract DeploySaintDurbin is Script {
console.log("Initial principal locked:", saintDurbin.principalLocked());

// Get current validator info
(bytes32 hotkey, uint16 uid, bool isValid) = saintDurbin.getCurrentValidatorInfo();
console.log("Current validator hotkey matches:", hotkey == validatorHotkey);
(bytes32 hotkey, uint16 uid, bool isValid) = saintDurbin
.getCurrentValidatorInfo();
console.log(
"Current validator hotkey matches:",
hotkey == validatorHotkey
);
console.log("Current validator UID:", uid);
console.log("Validator is valid:", isValid);

// Verify immutable configuration
console.log("\nVerifying immutable configuration:");
console.log("Emergency Operator:", saintDurbin.emergencyOperator());
console.log("Drain SS58 Address:", vm.toString(saintDurbin.drainSs58Address()));
console.log("Current Validator Hotkey:", vm.toString(saintDurbin.currentValidatorHotkey()));
console.log("Contract SS58 Key:", vm.toString(saintDurbin.thisSs58PublicKey()));
console.log(
"Drain SS58 Address:",
vm.toString(saintDurbin.drainSs58Address())
);
console.log(
"Current Validator Hotkey:",
vm.toString(saintDurbin.currentValidatorHotkey())
);
console.log(
"Contract SS58 Key:",
vm.toString(saintDurbin.thisSs58PublicKey())
);
console.log("NetUID:", saintDurbin.netuid());

console.log("\nDeployment complete! Contract is now fully immutable.");
Expand Down
6 changes: 6 additions & 0 deletions script/TestDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ contract TestDeploy is Script {

// Test configuration
address emergencyOperator = msg.sender;
address drainAddress = address(0x1234);
bytes32 drainSs58Address = bytes32(uint256(1));
bytes32 validatorHotkey = bytes32(uint256(2));

uint16 validatorUid = 0;
bytes32 thisSs58PublicKey = bytes32(uint256(3));
bytes16 validatorHotkeyHash = bytes16(uint128(4));

uint16 netuid = 0;

// Recipients configuration
Expand Down Expand Up @@ -45,11 +49,13 @@ contract TestDeploy is Script {

SaintDurbin saintDurbin = new SaintDurbin(
emergencyOperator,
drainAddress,
drainSs58Address,
validatorHotkey,
validatorUid,
thisSs58PublicKey,
netuid,
validatorHotkeyHash,
recipientColdkeys,
proportions
);
Expand Down
3 changes: 2 additions & 1 deletion script/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "saintdurbin-distribution",
"version": "1.0.0",
"type": "commonjs",
"description": "SaintDurbin yield distribution cron job",
"main": "distribute.js",
"scripts": {
Expand All @@ -12,4 +13,4 @@
"axios": "^1.6.0",
"dotenv": "^16.3.1"
}
}
}
137 changes: 90 additions & 47 deletions scripts/check-validator.js → scripts/check-validator.cjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// scripts/check-validator.js
const { ethers } = require('ethers');
require('dotenv').config();

// Only load dotenv if not in test environment
if (process.env.NODE_ENV !== 'test') {
require('dotenv').config();
}

const SAINTDURBIN_ABI = [
"function currentValidatorHotkey() external view returns (bytes32)",
"function currentValidatorUid() external view returns (uint16)",
"function getCurrentValidatorInfo() external view returns (bytes32 hotkey, uint16 uid, bool isValid)",
"function getStakedBalance() external view returns (uint256)",
"function checkAndSwitchValidator() external"
"function checkAndSwitchValidator() external",
"event ValidatorSwitched(bytes32 indexed oldHotkey, bytes32 indexed newHotkey, uint16 newUid, string reason)"
];

/**
Expand All @@ -16,13 +21,13 @@ const SAINTDURBIN_ABI = [
* @returns {Promise<Object>} Validator info including hotkey, uid, isValid, and stakedBalance
*/
async function getValidatorInfo(contract) {
const [hotkey, uid, isValid] = await contract.getCurrentValidatorInfo();
const validatorInfo = await contract.getCurrentValidatorInfo();
const stakedBalance = await contract.getStakedBalance();

return {
hotkey: hotkey.toString(),
uid: uid.toString(),
isValid,
hotkey: validatorInfo.hotkey.toString(),
uid: validatorInfo.uid.toString(),
isValid: validatorInfo.isValid,
stakedBalance: stakedBalance.toString(),
stakedBalanceFormatted: ethers.formatUnits(stakedBalance, 9)
};
Expand All @@ -34,40 +39,74 @@ async function getValidatorInfo(contract) {
* @param {Object} options - Options for the switch
* @param {boolean} options.skipTransaction - If true, don't actually send the transaction
* @param {number} options.gasLimit - Gas limit for the transaction
* @param {boolean} options.silent - Suppress console output
* @returns {Promise<Object>} Result of the switch operation
*/
async function switchValidator(contract, options = {}) {
const { skipTransaction = false, gasLimit = 500000 } = options;

const { skipTransaction = false, gasLimit = 500000, silent = false } = options;
const log = silent ? () => {} : console.log;

if (skipTransaction) {
return {
success: true,
skipped: true,
message: 'Transaction skipped (test mode)'
message: 'Validator switch simulated (skipTransaction=true)'
};
}

const tx = await contract.checkAndSwitchValidator({ gasLimit });
const receipt = await tx.wait();

if (receipt.status !== 1) {

try {
const tx = await contract.checkAndSwitchValidator({ gasLimit });
log('Transaction submitted:', tx.hash);

const receipt = await tx.wait();

if (receipt.status !== 1) {
return {
success: false,
transactionHash: tx.hash,
message: 'Transaction failed'
};
}

// Parse ValidatorSwitched events from the receipt
const filter = contract.filters.ValidatorSwitched();
const events = await contract.queryFilter(filter, receipt.blockNumber, receipt.blockNumber);

if (events.length > 0) {
const event = events[0];
const { oldHotkey, newHotkey, newUid, reason } = event.args;

// Get new validator info after switch
const newValidatorInfo = await getValidatorInfo(contract);

return {
success: true,
transactionHash: tx.hash,
message: `Validator switched from UID ${event.args.oldUid || 'unknown'} to UID ${newUid.toString()} - ${reason}`,
newValidator: newValidatorInfo,
event: {
oldValidator: oldHotkey,
newValidator: newHotkey,
oldUid: event.args.oldUid,
newUid: newUid,
reason: reason
}
};
} else {
// No switch occurred
return {
success: true,
transactionHash: tx.hash,
message: 'Validator check completed (no switch occurred)'
};
}
} catch (error) {
return {
success: false,
transactionHash: tx.hash,
message: 'Transaction failed'
message: `Failed to switch validator: ${error.message}`,
error: error
};
}

// Get new validator info after switch
const newValidatorInfo = await getValidatorInfo(contract);

return {
success: true,
transactionHash: tx.hash,
previousValidator: null, // Will be set by calling function
newValidator: newValidatorInfo,
message: 'Validator switched successfully'
};
}

/**
Expand All @@ -90,19 +129,19 @@ async function checkValidator(options = {}) {
skipTransaction = false,
silent = false
} = options;

const log = silent ? () => {} : console.log;
const error = silent ? () => {} : console.error;

try {
// Initialize provider, wallet, and contract
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(contractAddress, SAINTDURBIN_ABI, wallet);

// Get current validator info
const validatorInfo = await getValidatorInfo(contract);

log('SaintDurbin Validator Check');
log('Contract:', contractAddress);
log('');
Expand All @@ -113,34 +152,34 @@ async function checkValidator(options = {}) {
log('');
log('Contract Staked Balance:', validatorInfo.stakedBalanceFormatted, 'TAO');
log('');

const result = {
success: true,
contractAddress,
currentValidator: validatorInfo,
switchPerformed: false,
switchResult: null
};

if (!validatorInfo.isValid) {
log('⚠️ WARNING: Current validator is no longer valid!');
log('The contract will automatically switch validators during the next distribution.');

if (shouldSwitch) {
log('');
log('Triggering manual validator switch...');
const switchResult = await switchValidator(contract, { skipTransaction });

const switchResult = await switchValidator(contract, { skipTransaction, silent });
result.switchPerformed = true;
result.switchResult = switchResult;

if (switchResult.success) {
if (switchResult.skipped) {
log('✅ Switch skipped (test mode)');
} else {
log('Transaction submitted:', switchResult.transactionHash);
log('✅ Transaction successful!');

if (switchResult.newValidator) {
log('');
log('New Validator:');
Expand All @@ -160,15 +199,19 @@ async function checkValidator(options = {}) {
} else {
log('✅ Validator is healthy and active');
}

return result;

} catch (err) {
error('❌ Error checking validator:', err.message);
const errorMessage = err.message || err.toString();
error('❌ Error checking validator:', errorMessage);
return {
success: false,
error: err.message,
errorDetails: err
error: 'Failed to check validator',
errorDetails: {
message: errorMessage,
stack: err.stack
}
};
}
}
Expand All @@ -178,12 +221,12 @@ async function checkValidator(options = {}) {
*/
async function main() {
const shouldSwitch = process.argv.includes('--switch');

const result = await checkValidator({
shouldSwitch,
silent: false
});

if (!result.success) {
process.exit(1);
}
Expand All @@ -203,4 +246,4 @@ if (require.main === module) {
console.error('Unhandled error:', error);
process.exit(1);
});
}
}
Loading