Skip to content

Commit 29324c2

Browse files
committed
combined command
1 parent f7e0521 commit 29324c2

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

src/cmd/rewards.ts

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,290 @@ export class ShowRewardInfo extends Command {
857857
}
858858
}
859859

860+
export class CheckAllPendingRoots extends Command {
861+
static paths = [["reward", "check-all-pending"]];
862+
dir = Option.String("--dir", "chains");
863+
864+
async execute() {
865+
const rewards = await loadAllData(this.dir, "rewards");
866+
console.log("🔍 Checking validity of all pending roots...\n");
867+
868+
let foundPending = false;
869+
let validCount = 0;
870+
let invalidCount = 0;
871+
let errorCount = 0;
872+
let readyToAccept: string[] = [];
873+
874+
for (const reward of rewards) {
875+
try {
876+
const { chain, publicClient } = getWalletInfo(reward.chainId);
877+
878+
if (!("urdFactory" in chain.morpho)) {
879+
console.log(`⚠️ Skipping ${reward.id}: No urdFactory for chain ${chain.id}`);
880+
continue;
881+
}
882+
883+
const pendingRoot = await getPendingRoot(
884+
publicClient,
885+
getAddress(reward.urdAddress),
886+
);
887+
888+
if (pendingRoot !== zeroHash) {
889+
foundPending = true;
890+
console.log(`\n${'='.repeat(60)}`);
891+
console.log(`🔄 CHECKING: ${reward.id} (${reward.name || "no name"})`);
892+
console.log(`${'='.repeat(60)}`);
893+
894+
let isValid = true;
895+
let hasErrors = false;
896+
897+
// Get detailed pending root info and timelock
898+
const pendingRootData = await getPendingRootWithTimestamp(
899+
publicClient,
900+
getAddress(reward.urdAddress),
901+
);
902+
903+
const timelockPeriod = await getTimelock(
904+
publicClient,
905+
getAddress(reward.urdAddress),
906+
);
907+
908+
console.log(`📋 Chain: ${reward.chainId}`);
909+
console.log(`📋 URD Address: ${reward.urdAddress}`);
910+
console.log(`📋 On-chain Pending Root: ${pendingRoot}`);
911+
912+
// Check endpoint validation
913+
try {
914+
const endpointUrl = `https://sap.icarus.tools/blue?method=getPendingTreeForCampaign&params=[%22${encodeURIComponent(reward.id)}%22]`;
915+
const response = await fetch(endpointUrl);
916+
917+
if (!response.ok) {
918+
throw new Error(`HTTP error! status: ${response.status}`);
919+
}
920+
921+
const endpointData = await response.json();
922+
923+
if (endpointData && endpointData.result && endpointData.result.root) {
924+
const campaignTree = endpointData.result;
925+
const endpointRoot = campaignTree.root;
926+
927+
console.log(`📋 Endpoint Root: ${endpointRoot}`);
928+
929+
if (pendingRoot.toLowerCase() === endpointRoot.toLowerCase()) {
930+
console.log("✅ Root Match: On-chain pending root matches endpoint root");
931+
} else {
932+
console.log("❌ Root Match: On-chain pending root does NOT match endpoint root");
933+
isValid = false;
934+
}
935+
936+
// Validate campaign progress
937+
const progressValid = this.validateCampaignProgressQuick(reward, campaignTree);
938+
if (!progressValid) {
939+
isValid = false;
940+
}
941+
942+
// Validate blacklist
943+
const blacklistValid = this.validateBlacklistQuick(campaignTree, reward.chainId);
944+
if (!blacklistValid) {
945+
isValid = false;
946+
}
947+
948+
} else {
949+
console.log("❌ Endpoint: Did not return a valid campaign tree");
950+
isValid = false;
951+
}
952+
} catch (error) {
953+
console.log(`❌ Endpoint Error: ${error instanceof Error ? error.message : error}`);
954+
hasErrors = true;
955+
}
956+
957+
// Display timelock information
958+
this.displayTimelockInfoQuick(pendingRootData, timelockPeriod);
959+
960+
// Final status
961+
const validAtTimestamp = Number(pendingRootData.timestamp);
962+
const now = Math.floor(Date.now() / 1000);
963+
const isReady = now >= validAtTimestamp;
964+
965+
if (hasErrors) {
966+
console.log(`\n🔴 FINAL STATUS: ERROR - Could not fully validate ${reward.id}`);
967+
errorCount++;
968+
} else if (!isValid) {
969+
console.log(`\n🔴 FINAL STATUS: INVALID - ${reward.id} has validation issues`);
970+
invalidCount++;
971+
} else if (!isReady) {
972+
console.log(`\n🟡 FINAL STATUS: VALID BUT LOCKED - ${reward.id} is valid but still in timelock`);
973+
validCount++;
974+
} else {
975+
console.log(`\n🟢 FINAL STATUS: VALID AND READY - ${reward.id} is ready to accept`);
976+
validCount++;
977+
readyToAccept.push(reward.id);
978+
}
979+
}
980+
} catch (error) {
981+
console.log(`❌ Error checking ${reward.id}: ${error instanceof Error ? error.message : error}`);
982+
errorCount++;
983+
}
984+
}
985+
986+
// Summary
987+
console.log(`\n${'='.repeat(60)}`);
988+
console.log(`📊 VALIDATION SUMMARY`);
989+
console.log(`${'='.repeat(60)}`);
990+
991+
if (!foundPending) {
992+
console.log("✨ No rewards have pending roots.");
993+
} else {
994+
console.log(`🟢 Valid: ${validCount}`);
995+
console.log(`🔴 Invalid: ${invalidCount}`);
996+
console.log(`⚠️ Errors: ${errorCount}`);
997+
console.log(`📊 Total Checked: ${validCount + invalidCount + errorCount}`);
998+
999+
if (validCount > 0) {
1000+
console.log(`\n✅ ${validCount} campaign(s) ready for acceptance`);
1001+
}
1002+
if (invalidCount > 0) {
1003+
console.log(`\n❌ ${invalidCount} campaign(s) have validation issues - DO NOT ACCEPT`);
1004+
}
1005+
if (errorCount > 0) {
1006+
console.log(`\n⚠️ ${errorCount} campaign(s) had validation errors - manual review required`);
1007+
}
1008+
1009+
// Print acceptance commands for ready campaigns
1010+
if (readyToAccept.length > 0) {
1011+
console.log(`\n${'='.repeat(60)}`);
1012+
console.log(`🚀 READY TO ACCEPT - RUN THESE COMMANDS:`);
1013+
console.log(`${'='.repeat(60)}`);
1014+
1015+
for (const campaignId of readyToAccept) {
1016+
console.log(`./cli reward accept ${campaignId}`);
1017+
}
1018+
1019+
console.log(`\n💡 Or accept all at once:`);
1020+
const acceptAllCommand = readyToAccept.map(id => `./cli reward accept ${id}`).join(' && ');
1021+
console.log(acceptAllCommand);
1022+
}
1023+
}
1024+
}
1025+
1026+
private validateCampaignProgressQuick(reward: any, campaignTree: any): boolean {
1027+
try {
1028+
const referenceTime = this.getMostRecentFriday9amPDT();
1029+
const startTime = campaignTree.metadata?.start_timestamp || reward.start_timestamp;
1030+
const endTime = campaignTree.metadata?.end_timestamp || reward.end_timestamp;
1031+
const totalRewardAmount = BigInt(reward.reward_amount);
1032+
1033+
let completionPercentage = 0;
1034+
if (referenceTime < startTime) {
1035+
completionPercentage = 0;
1036+
} else if (referenceTime >= endTime) {
1037+
completionPercentage = 100;
1038+
} else {
1039+
const elapsed = referenceTime - startTime;
1040+
const total = endTime - startTime;
1041+
completionPercentage = (elapsed / total) * 100;
1042+
}
1043+
1044+
if (campaignTree.tree && Array.isArray(campaignTree.tree)) {
1045+
const totalClaimable = campaignTree.tree.reduce((sum: bigint, entry: any) => {
1046+
return sum + BigInt(entry.claimable || entry.amount || 0);
1047+
}, BigInt(0));
1048+
1049+
const expectedMax = (totalRewardAmount * BigInt(Math.ceil(completionPercentage))) / BigInt(100);
1050+
const tolerance = BigInt(5);
1051+
const maxAllowed = (totalRewardAmount * (BigInt(Math.ceil(completionPercentage)) + tolerance)) / BigInt(100);
1052+
1053+
if (totalClaimable <= maxAllowed) {
1054+
console.log("✅ Progress: Total claimable amount is within expected range");
1055+
return true;
1056+
} else {
1057+
console.log("❌ Progress: Total claimable amount exceeds expected range");
1058+
return false;
1059+
}
1060+
}
1061+
return true;
1062+
} catch (error) {
1063+
console.log(`⚠️ Progress: Could not validate campaign progress: ${error}`);
1064+
return false;
1065+
}
1066+
}
1067+
1068+
private validateBlacklistQuick(campaignTree: any, chainId: number): boolean {
1069+
try {
1070+
const blacklist = loadBlacklist(this.dir, chainId);
1071+
1072+
if (blacklist.length === 0) {
1073+
console.log("ℹ️ Blacklist: No blacklist found for this chain");
1074+
return true;
1075+
}
1076+
1077+
if (!campaignTree.tree || !Array.isArray(campaignTree.tree)) {
1078+
console.log("⚠️ Blacklist: No tree data available for validation");
1079+
return false;
1080+
}
1081+
1082+
for (const entry of campaignTree.tree) {
1083+
const userAddress = (entry.account || entry.user || entry.address || '').toLowerCase();
1084+
if (blacklist.includes(userAddress)) {
1085+
console.log("🔥 CRITICAL: BLACKLISTED ADDRESS DETECTED - DO NOT ACCEPT");
1086+
return false;
1087+
}
1088+
}
1089+
1090+
console.log("✅ Blacklist: No blacklisted addresses found");
1091+
return true;
1092+
} catch (error) {
1093+
console.log(`⚠️ Blacklist: Could not validate blacklist: ${error}`);
1094+
return false;
1095+
}
1096+
}
1097+
1098+
private getMostRecentFriday9amPDT(): number {
1099+
const now = new Date();
1100+
const pdtOffset = 7 * 60;
1101+
const nowPDT = new Date(now.getTime() - pdtOffset * 60 * 1000);
1102+
const currentDay = nowPDT.getDay();
1103+
1104+
let daysToSubtract = 0;
1105+
if (currentDay === 5) {
1106+
daysToSubtract = 0;
1107+
} else if (currentDay === 6) {
1108+
daysToSubtract = 1;
1109+
} else if (currentDay === 0) {
1110+
daysToSubtract = 2;
1111+
} else {
1112+
daysToSubtract = currentDay + 2;
1113+
}
1114+
1115+
const targetFriday = new Date(nowPDT);
1116+
targetFriday.setDate(targetFriday.getDate() - daysToSubtract);
1117+
targetFriday.setHours(9, 0, 0, 0);
1118+
targetFriday.setHours(targetFriday.getHours() - 12);
1119+
1120+
const fridayUTC = new Date(targetFriday.getTime() + pdtOffset * 60 * 1000);
1121+
return Math.floor(fridayUTC.getTime() / 1000);
1122+
}
1123+
1124+
private displayTimelockInfoQuick(pendingRootData: any, timelockPeriod: bigint) {
1125+
if (pendingRootData.root === zeroHash) {
1126+
console.log(`ℹ️ Timelock: No pending root to timelock`);
1127+
return;
1128+
}
1129+
1130+
const validAtTimestamp = Number(pendingRootData.timestamp);
1131+
const now = Math.floor(Date.now() / 1000);
1132+
1133+
if (now >= validAtTimestamp) {
1134+
console.log(`✅ Timelock: Ready to accept (timelock expired)`);
1135+
} else {
1136+
const timeRemaining = validAtTimestamp - now;
1137+
const hoursRemaining = Math.floor(timeRemaining / 3600);
1138+
const minutesRemaining = Math.floor((timeRemaining % 3600) / 60);
1139+
console.log(`⏳ Timelock: Still locked (${hoursRemaining}h ${minutesRemaining}m remaining)`);
1140+
}
1141+
}
1142+
}
1143+
8601144
export class TransferOwner extends Command {
8611145
static paths = [["reward", "transfer-owner"]];
8621146

0 commit comments

Comments
 (0)