Skip to content

Instantly share code, notes, and snippets.

@mariorz
Last active February 22, 2023 00:30
Show Gist options
  • Save mariorz/49976ce76773de2758a6b6bc29855257 to your computer and use it in GitHub Desktop.
Save mariorz/49976ce76773de2758a6b6bc29855257 to your computer and use it in GitHub Desktop.
[
{
"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"
}
]
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();
[{"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