Last active
February 22, 2023 00:30
-
-
Save mariorz/49976ce76773de2758a6b6bc29855257 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"constant": true, | |
"inputs": [], | |
"name": "name", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "string" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": false, | |
"inputs": [ | |
{ | |
"name": "_spender", | |
"type": "address" | |
}, | |
{ | |
"name": "_value", | |
"type": "uint256" | |
} | |
], | |
"name": "approve", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "nonpayable", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [], | |
"name": "totalSupply", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "uint256" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": false, | |
"inputs": [ | |
{ | |
"name": "_from", | |
"type": "address" | |
}, | |
{ | |
"name": "_to", | |
"type": "address" | |
}, | |
{ | |
"name": "_value", | |
"type": "uint256" | |
} | |
], | |
"name": "transferFrom", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "nonpayable", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [], | |
"name": "decimals", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "uint8" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "_owner", | |
"type": "address" | |
} | |
], | |
"name": "balanceOf", | |
"outputs": [ | |
{ | |
"name": "balance", | |
"type": "uint256" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [], | |
"name": "symbol", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "string" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": false, | |
"inputs": [ | |
{ | |
"name": "_to", | |
"type": "address" | |
}, | |
{ | |
"name": "_value", | |
"type": "uint256" | |
} | |
], | |
"name": "transfer", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "bool" | |
} | |
], | |
"payable": false, | |
"stateMutability": "nonpayable", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [ | |
{ | |
"name": "_owner", | |
"type": "address" | |
}, | |
{ | |
"name": "_spender", | |
"type": "address" | |
} | |
], | |
"name": "allowance", | |
"outputs": [ | |
{ | |
"name": "", | |
"type": "uint256" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"payable": true, | |
"stateMutability": "payable", | |
"type": "fallback" | |
}, | |
{ | |
"anonymous": false, | |
"inputs": [ | |
{ | |
"indexed": true, | |
"name": "owner", | |
"type": "address" | |
}, | |
{ | |
"indexed": true, | |
"name": "spender", | |
"type": "address" | |
}, | |
{ | |
"indexed": false, | |
"name": "value", | |
"type": "uint256" | |
} | |
], | |
"name": "Approval", | |
"type": "event" | |
}, | |
{ | |
"anonymous": false, | |
"inputs": [ | |
{ | |
"indexed": true, | |
"name": "from", | |
"type": "address" | |
}, | |
{ | |
"indexed": true, | |
"name": "to", | |
"type": "address" | |
}, | |
{ | |
"indexed": false, | |
"name": "value", | |
"type": "uint256" | |
} | |
], | |
"name": "Transfer", | |
"type": "event" | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ethers = require("ethers"); | |
const IERC20_ABI = require("./contracts/IERC20.json") | |
const V3UTILS_ABI = require("./contracts/V3Utils.json") | |
var RPC_URL = {}; | |
var V3_CONTRACT_ADDRESS = {}; | |
//mainnet | |
RPC_URL["MAINNET"]="" | |
V3_CONTRACT_ADDRESS["MAINNET"]="0x531110418d8591c92e9cbbfc722db8ffb604fafd" | |
//polygon | |
RPC_URL["POLYGON"]="" | |
V3_CONTRACT_ADDRESS["POLYGON"]="0x8c925768c793e00c095135b8656d6014ee2d07bb" | |
//optimism | |
RPC_URL["OPTIMISM"]="" | |
V3_CONTRACT_ADDRESS["OPTIMISM"]="0x2A017f2Fb369F4CA061B8D8A922Bb05100e8f8C3" | |
//arbitrum | |
RPC_URL["ARBITRUM"]="" | |
V3_CONTRACT_ADDRESS["ARBITRUM"]="0x95a8cc9ab71b26bdacbe6a7ccf519456edc2a164" | |
const totals = {} | |
// Accounts at risk, contact us | |
const accountsAtRisk = new Set(["0xDAA27d84ea816F28F4c420F7b0AD6a9998B7e305", | |
"0x859Bb79038C20F09A7Ad5726e036028f301d5de6", | |
"0x8cadb20A4811f363Dadb863A190708bEd26245F8", | |
"0xfC39B1A29B576c637ea39AAD2E2D9D6271770bEd", | |
"0x4eD9c6193ede88A5D41b2E833E46508800420780", | |
"0xfC39B1A29B576c637ea39AAD2E2D9D6271770bEd", | |
"0xa712503e840648c4FcAf569E6Db9A1911B82224e", | |
"0x1826b03d331df21d5Fd72a93549CAfCB9dc2D095", | |
"0x14CCd5BeaB5Ee806dacd954a6142F4901DB05fdD", | |
"0x3E7d81B7B1FAe155201D1A2a6F73b66c97dEde76", | |
"0x89425a801dbbacD4562638161198b6d3b85A314c", | |
"0x19D32180BC2210eA3b500553d3Db90ef248A38f3", | |
"0x0A3f4aBCe67AA283bB64668230a7390c7DFc9FF0", | |
"0x8cadb20A4811f363Dadb863A190708bEd26245F8"].map(x=>x.toLowerCase())); | |
async function exploitAnalysis(network) { | |
const provider = new ethers.providers.JsonRpcProvider(RPC_URL[network]) | |
const contractAddress = V3_CONTRACT_ADDRESS[network] | |
const v3utils = new ethers.Contract(contractAddress, V3UTILS_ABI, provider) | |
const swapFilter = v3utils.filters.Swap() | |
const swaps = await provider.getLogs({ | |
fromBlock: 0, | |
toBlock: "latest", | |
...swapFilter | |
}) | |
const transfers = {}; | |
for (const swap of swaps) { | |
const tx = await provider.getTransactionReceipt(swap.transactionHash) | |
// swap events | |
const sl = tx.logs.filter(l => l.topics[0] == "0xfa2dda1cc1b86e41239702756b13effbc1a092b5c57e3ad320fbe4f3b13fe235") | |
// add liquidity events | |
const sil = tx.logs.filter(l => l.topics[0] == "0x1aa7d7662b161a8d4058004091e4c60474753d2f085db526dfb22da59838d0a7") | |
// remove liquidity events | |
const wcs = tx.logs.filter(l => l.topics[0] == "0xf8aacb16b34d229aa53d44a7759a3741b5c098e099666e51a4ae9d66a5956c43") | |
// transactions with swap only are the ones involved in the hack | |
if (sl.length > 0 && sil.length == 0 && wcs.length == 0) { | |
//console.log(network, tx.transactionHash); | |
const abi = ["event Transfer(address indexed from, address indexed to, uint256 value)"]; | |
const iface = new ethers.utils.Interface(abi); | |
const block = await provider.getBlock(tx.blockNumber); | |
for (const log of tx.logs) { | |
try { | |
const parsedLog = iface.parseLog(log); | |
if (parsedLog.name === "Transfer") { | |
//console.log(log.address); | |
if (parsedLog.args.to.toLowerCase() == contractAddress.toLowerCase() || | |
parsedLog.args.from.toLowerCase() == contractAddress.toLowerCase()) { | |
continue; | |
} | |
const erc20Address = log.address; | |
const erc20Contract = new ethers.Contract(erc20Address, ["function symbol() view returns (string)", "function decimals() view returns (uint8)"], | |
provider); | |
const [symbol, decimals] = await Promise.all([erc20Contract.symbol(), erc20Contract.decimals()]); | |
const value = parseFloat(ethers.utils.formatUnits(parsedLog.args.value, decimals)); | |
const victimAddress = parsedLog.args.from; | |
const attackerAddress = tx.from; | |
const key = symbol+value+victimAddress; | |
var revokeState = "Revoked"; | |
if (!(key in transfers)) { | |
if (accountsAtRisk.has(parsedLog.args.from.toLowerCase())) { | |
revokeState = "Must Revoke"; | |
} | |
console.log(network, victimAddress, symbol, value, revokeState); | |
if (!(symbol in totals)) { | |
totals[symbol] = value; | |
} else { | |
totals[symbol] = totals[symbol]+value; | |
} | |
transfers[symbol+value+victimAddress] = 1 | |
} | |
} | |
} catch { | |
// non ERC20 transfer events | |
} | |
} | |
} | |
} | |
} | |
async function exploitAnalysisTotals() { | |
const results = await Promise.all([exploitAnalysis("MAINNET"), | |
exploitAnalysis("POLYGON"), | |
exploitAnalysis("OPTIMISM"), | |
exploitAnalysis("ARBITRUM")]); | |
console.log("totals:") | |
console.log(totals); | |
} | |
exploitAnalysisTotals(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{"inputs":[{"internalType":"contract INonfungiblePositionManager","name":"_nonfungiblePositionManager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AmountError","type":"error"},{"inputs":[],"name":"CollectError","type":"error"},{"inputs":[],"name":"EtherSendFailed","type":"error"},{"inputs":[],"name":"NoEtherToken","type":"error"},{"inputs":[],"name":"NotSupportedWhatToDo","type":"error"},{"inputs":[],"name":"NotWETH","type":"error"},{"inputs":[],"name":"SameToken","type":"error"},{"inputs":[],"name":"SlippageError","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[],"name":"TooMuchEtherSent","type":"error"},{"inputs":[],"name":"TransferError","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"WrongChain","type":"error"},{"inputs":[],"name":"WrongContract","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTokenId","type":"uint256"}],"name":"ChangeRange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"CompoundFees","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":false,"internalType":"address","name":"tokenOut","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"SwapAndIncreaseLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"SwapAndMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawAndCollectAndSwap","type":"event"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"enum V3Utils.WhatToDo","name":"whatToDo","type":"uint8"},{"internalType":"address","name":"targetToken","type":"address"},{"internalType":"uint256","name":"amountIn0","type":"uint256"},{"internalType":"uint256","name":"amountOut0Min","type":"uint256"},{"internalType":"bytes","name":"swapData0","type":"bytes"},{"internalType":"uint256","name":"amountIn1","type":"uint256"},{"internalType":"uint256","name":"amountOut1Min","type":"uint256"},{"internalType":"bytes","name":"swapData1","type":"bytes"},{"internalType":"uint128","name":"feeAmount0","type":"uint128"},{"internalType":"uint128","name":"feeAmount1","type":"uint128"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amountAddMin0","type":"uint256"},{"internalType":"uint256","name":"amountAddMin1","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"unwrap","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"},{"internalType":"bytes","name":"swapAndMintReturnData","type":"bytes"}],"internalType":"struct V3Utils.Instructions","name":"instructions","type":"tuple"}],"name":"execute","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nonfungiblePositionManager","outputs":[{"internalType":"contract INonfungiblePositionManager","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"tokenIn","type":"address"},{"internalType":"contract IERC20","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bytes","name":"swapData","type":"bytes"},{"internalType":"bool","name":"unwrap","type":"bool"}],"internalType":"struct V3Utils.SwapParams","name":"params","type":"tuple"}],"name":"swap","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"contract IERC20","name":"swapSourceToken","type":"address"},{"internalType":"uint256","name":"amountIn0","type":"uint256"},{"internalType":"uint256","name":"amountOut0Min","type":"uint256"},{"internalType":"bytes","name":"swapData0","type":"bytes"},{"internalType":"uint256","name":"amountIn1","type":"uint256"},{"internalType":"uint256","name":"amountOut1Min","type":"uint256"},{"internalType":"bytes","name":"swapData1","type":"bytes"},{"internalType":"uint256","name":"amountAddMin0","type":"uint256"},{"internalType":"uint256","name":"amountAddMin1","type":"uint256"}],"internalType":"struct V3Utils.SwapAndIncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"swapAndIncreaseLiquidity","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"contract IERC20","name":"token0","type":"address"},{"internalType":"contract IERC20","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"recipientNFT","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"contract IERC20","name":"swapSourceToken","type":"address"},{"internalType":"uint256","name":"amountIn0","type":"uint256"},{"internalType":"uint256","name":"amountOut0Min","type":"uint256"},{"internalType":"bytes","name":"swapData0","type":"bytes"},{"internalType":"uint256","name":"amountIn1","type":"uint256"},{"internalType":"uint256","name":"amountOut1Min","type":"uint256"},{"internalType":"bytes","name":"swapData1","type":"bytes"},{"internalType":"uint256","name":"amountAddMin0","type":"uint256"},{"internalType":"uint256","name":"amountAddMin1","type":"uint256"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct V3Utils.SwapAndMintParams","name":"params","type":"tuple"}],"name":"swapAndMint","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"weth","outputs":[{"internalType":"contract IWETH9","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment