|
| 1 | +import { ethers } from "ethers"; |
| 2 | + |
| 3 | +const GEODE_MIN = ethers.BigNumber.from("1000000002"); |
| 4 | +const GEODE_MAX = ethers.BigNumber.from("1000000007"); |
| 5 | + |
| 6 | +export interface LegacyVrfAddresses { |
| 7 | + aavegotchiDiamond: string; |
| 8 | + forgeDiamond: string; |
| 9 | +} |
| 10 | + |
| 11 | +export interface LegacyVrfPreflightSummary { |
| 12 | + latestBlock: number; |
| 13 | + pendingPortalCount: number; |
| 14 | + pendingPortalTokenIds: string[]; |
| 15 | + pendingForgeCount: number; |
| 16 | + pendingForge: { user: string; requestId: string }[]; |
| 17 | + readyToClaimForgeCount: number; |
| 18 | + readyToClaimForge: { user: string; requestId: string }[]; |
| 19 | +} |
| 20 | + |
| 21 | +async function findDeployBlock( |
| 22 | + provider: ethers.providers.Provider, |
| 23 | + address: string, |
| 24 | + latest: number |
| 25 | +) { |
| 26 | + let lo = 0; |
| 27 | + let hi = latest; |
| 28 | + |
| 29 | + while (lo < hi) { |
| 30 | + const mid = Math.floor((lo + hi) / 2); |
| 31 | + const code = await provider.getCode(address, mid); |
| 32 | + if (code && code !== "0x") { |
| 33 | + hi = mid; |
| 34 | + } else { |
| 35 | + lo = mid + 1; |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + return lo; |
| 40 | +} |
| 41 | + |
| 42 | +async function getLogsByChunks( |
| 43 | + provider: ethers.providers.Provider, |
| 44 | + filterBase: ethers.providers.Filter, |
| 45 | + fromBlock: number, |
| 46 | + toBlock: number, |
| 47 | + step = 1_000_000 |
| 48 | +) { |
| 49 | + const logs = []; |
| 50 | + for (let start = fromBlock; start <= toBlock; start += step) { |
| 51 | + const end = Math.min(start + step - 1, toBlock); |
| 52 | + const chunk = await provider.getLogs({ |
| 53 | + ...filterBase, |
| 54 | + fromBlock: start, |
| 55 | + toBlock: end, |
| 56 | + }); |
| 57 | + logs.push(...chunk); |
| 58 | + } |
| 59 | + return logs; |
| 60 | +} |
| 61 | + |
| 62 | +export async function getLegacyVrfPreflightSummary( |
| 63 | + provider: ethers.providers.Provider, |
| 64 | + addresses: LegacyVrfAddresses |
| 65 | +): Promise<LegacyVrfPreflightSummary> { |
| 66 | + const latest = await provider.getBlockNumber(); |
| 67 | + const aavegotchiDeployBlock = await findDeployBlock( |
| 68 | + provider, |
| 69 | + addresses.aavegotchiDiamond, |
| 70 | + latest |
| 71 | + ); |
| 72 | + const forgeDeployBlock = await findDeployBlock( |
| 73 | + provider, |
| 74 | + addresses.forgeDiamond, |
| 75 | + latest |
| 76 | + ); |
| 77 | + |
| 78 | + const portalInterface = new ethers.utils.Interface([ |
| 79 | + "event OpenPortals(uint256[] _tokenIds)", |
| 80 | + "event PortalOpened(uint256 indexed tokenId)", |
| 81 | + ]); |
| 82 | + const openTopic = portalInterface.getEventTopic("OpenPortals"); |
| 83 | + const openedTopic = portalInterface.getEventTopic("PortalOpened"); |
| 84 | + |
| 85 | + const openLogs = await getLogsByChunks( |
| 86 | + provider, |
| 87 | + { address: addresses.aavegotchiDiamond, topics: [openTopic] }, |
| 88 | + aavegotchiDeployBlock, |
| 89 | + latest |
| 90 | + ); |
| 91 | + const openedLogs = await getLogsByChunks( |
| 92 | + provider, |
| 93 | + { address: addresses.aavegotchiDiamond, topics: [openedTopic] }, |
| 94 | + aavegotchiDeployBlock, |
| 95 | + latest |
| 96 | + ); |
| 97 | + |
| 98 | + const pendingPortals = new Set<string>(); |
| 99 | + for (const log of openLogs) { |
| 100 | + const parsed = portalInterface.parseLog(log); |
| 101 | + for (const tokenId of parsed.args._tokenIds) { |
| 102 | + pendingPortals.add(tokenId.toString()); |
| 103 | + } |
| 104 | + } |
| 105 | + for (const log of openedLogs) { |
| 106 | + const parsed = portalInterface.parseLog(log); |
| 107 | + pendingPortals.delete(parsed.args.tokenId.toString()); |
| 108 | + } |
| 109 | + |
| 110 | + const forgeInterface = new ethers.utils.Interface([ |
| 111 | + "event TransferSingle(address indexed operator,address indexed from,address indexed to,uint256 id,uint256 value)", |
| 112 | + "event TransferBatch(address indexed operator,address indexed from,address indexed to,uint256[] ids,uint256[] values)", |
| 113 | + "function getRequestInfo(address user) view returns (tuple(address user,uint256 requestId,uint8 status,uint256 randomNumber,uint256[] geodeTokenIds,uint256[] amountPerToken))", |
| 114 | + ]); |
| 115 | + const transferSingleTopic = forgeInterface.getEventTopic("TransferSingle"); |
| 116 | + const transferBatchTopic = forgeInterface.getEventTopic("TransferBatch"); |
| 117 | + const toTopic = ethers.utils.hexZeroPad( |
| 118 | + addresses.forgeDiamond.toLowerCase(), |
| 119 | + 32 |
| 120 | + ); |
| 121 | + |
| 122 | + const singleLogs = await getLogsByChunks( |
| 123 | + provider, |
| 124 | + { |
| 125 | + address: addresses.forgeDiamond, |
| 126 | + topics: [transferSingleTopic, null, null, toTopic], |
| 127 | + }, |
| 128 | + forgeDeployBlock, |
| 129 | + latest |
| 130 | + ); |
| 131 | + const batchLogs = await getLogsByChunks( |
| 132 | + provider, |
| 133 | + { |
| 134 | + address: addresses.forgeDiamond, |
| 135 | + topics: [transferBatchTopic, null, null, toTopic], |
| 136 | + }, |
| 137 | + forgeDeployBlock, |
| 138 | + latest |
| 139 | + ); |
| 140 | + |
| 141 | + const forgeUsers = new Set<string>(); |
| 142 | + for (const log of singleLogs) { |
| 143 | + const parsed = forgeInterface.parseLog(log); |
| 144 | + if (parsed.args.id.gte(GEODE_MIN) && parsed.args.id.lte(GEODE_MAX)) { |
| 145 | + forgeUsers.add(parsed.args.from.toLowerCase()); |
| 146 | + } |
| 147 | + } |
| 148 | + for (const log of batchLogs) { |
| 149 | + const parsed = forgeInterface.parseLog(log); |
| 150 | + const hasGeode = parsed.args.ids.some( |
| 151 | + (id) => id.gte(GEODE_MIN) && id.lte(GEODE_MAX) |
| 152 | + ); |
| 153 | + if (hasGeode) { |
| 154 | + forgeUsers.add(parsed.args.from.toLowerCase()); |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + const forgeContract = new ethers.Contract( |
| 159 | + addresses.forgeDiamond, |
| 160 | + forgeInterface, |
| 161 | + provider |
| 162 | + ); |
| 163 | + const pendingForge: { user: string; requestId: string }[] = []; |
| 164 | + const readyToClaimForge: { user: string; requestId: string }[] = []; |
| 165 | + for (const user of forgeUsers) { |
| 166 | + try { |
| 167 | + const info = await forgeContract.getRequestInfo(user); |
| 168 | + const status = Number(info.status); |
| 169 | + if (status === 0) { |
| 170 | + pendingForge.push({ user, requestId: info.requestId.toString() }); |
| 171 | + } else if (status === 1) { |
| 172 | + readyToClaimForge.push({ |
| 173 | + user, |
| 174 | + requestId: info.requestId.toString(), |
| 175 | + }); |
| 176 | + } |
| 177 | + } catch (err) {} |
| 178 | + } |
| 179 | + |
| 180 | + return { |
| 181 | + latestBlock: latest, |
| 182 | + pendingPortalCount: pendingPortals.size, |
| 183 | + pendingPortalTokenIds: Array.from(pendingPortals), |
| 184 | + pendingForgeCount: pendingForge.length, |
| 185 | + pendingForge, |
| 186 | + readyToClaimForgeCount: readyToClaimForge.length, |
| 187 | + readyToClaimForge, |
| 188 | + }; |
| 189 | +} |
| 190 | + |
| 191 | +export async function assertNoPendingLegacyVrfRequests( |
| 192 | + provider: ethers.providers.Provider, |
| 193 | + addresses: LegacyVrfAddresses |
| 194 | +) { |
| 195 | + const summary = await getLegacyVrfPreflightSummary(provider, addresses); |
| 196 | + |
| 197 | + if (summary.pendingPortalCount > 0 || summary.pendingForgeCount > 0) { |
| 198 | + throw new Error( |
| 199 | + `Legacy PoP VRF requests are still pending. pendingPortals=${summary.pendingPortalCount} pendingForge=${summary.pendingForgeCount}` |
| 200 | + ); |
| 201 | + } |
| 202 | + |
| 203 | + return summary; |
| 204 | +} |
0 commit comments