mev-inspect
Version:
A JS port of 'mev-inspect-py' optimised for ease of use.
342 lines • 11.1 kB
JavaScript
import { Contract } from 'ethcall';
import { ZeroAddress } from 'ethers';
import vaultAbi from '../../abi/balancerV2Vault.js';
function isValidSwap(event) {
return event.name === 'Swap';
}
function isValidDeposit(event) {
const deltas = event.values.deltas;
if (!deltas) {
return false;
}
return (event.name === 'PoolBalanceChanged' && deltas.every((delta) => delta >= 0));
}
function isValidWithdrawal(event) {
const deltas = event.values.deltas;
if (!deltas) {
return false;
}
return (event.name === 'PoolBalanceChanged' && deltas.every((delta) => delta <= 0));
}
function isValidTransfer(event) {
return event.name === 'InternalBalanceChanged';
}
const VAULT_ADDRESS = '0xba12222222228d8ba445958a75a0704d566bf2c8';
function getPoolCalls(id) {
const contract = new Contract(VAULT_ADDRESS, vaultAbi);
return [contract.getPoolTokens(id)];
}
function processPoolCalls(result) {
const [poolTokens] = result;
if (!poolTokens) {
return null;
}
const tokens = poolTokens.tokens;
const assets = tokens.map((token) => token.toLowerCase());
return {
assets,
factoryAddress: VAULT_ADDRESS,
};
}
function parseSwap(pool, event, transfers, allEvents) {
const { transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event;
const { assetIn, assetOut, amountIn, amountOut } = getSwapValues(event);
const vault = event.address.toLowerCase();
const { from, to } = getClusterInputOutput(vault, logIndex, allEvents, transfers);
return {
contract: {
address: pool.address,
protocol: {
abi: 'BalancerV2',
factory: pool.factory,
},
},
block: {
hash: blockHash,
number: blockNumber,
},
transaction: {
from: transactionFrom.toLowerCase(),
hash,
index: transactionIndex,
gasUsed,
},
event: {
logIndex,
address: address.toLowerCase(),
},
from,
to,
assetIn: {
type: 'erc20',
address: assetIn,
},
amountIn,
assetOut: {
type: 'erc20',
address: assetOut,
},
amountOut,
metadata: {},
};
}
function parseDeposit(pool, event) {
const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event;
const depositor = values.liquidityProvider.toLowerCase();
const assets = values.tokens.map((token) => token.toLowerCase());
const amounts = values.deltas;
return {
contract: {
address: pool.address,
protocol: {
abi: 'BalancerV2',
factory: pool.factory,
},
},
block: {
hash: blockHash,
number: blockNumber,
},
transaction: {
from: transactionFrom.toLowerCase(),
hash,
index: transactionIndex,
gasUsed,
},
event: {
address: address.toLowerCase(),
logIndex,
},
depositor,
assets: assets.map((asset) => {
return {
type: 'erc20',
address: asset,
};
}),
amounts,
metadata: {},
};
}
function parseWithdrawal(pool, event) {
const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event;
const withdrawer = values.liquidityProvider.toLowerCase();
const assets = values.tokens.map((token) => token.toLowerCase());
const amounts = values.deltas.map((value) => -value);
return {
contract: {
address: pool.address,
protocol: {
abi: 'BalancerV2',
factory: pool.factory,
},
},
block: {
hash: blockHash,
number: blockNumber,
},
transaction: {
from: transactionFrom.toLowerCase(),
hash,
index: transactionIndex,
gasUsed,
},
event: {
address: address.toLowerCase(),
logIndex,
},
withdrawer,
assets: assets.map((asset) => {
return {
type: 'erc20',
address: asset,
};
}),
amounts,
metadata: {},
};
}
function parseTransfer(event) {
const { values, transactionFrom, transactionHash: hash, transactionIndex, gasUsed, logIndex, address, blockHash, blockNumber, } = event;
const user = values.user.toLowerCase();
const token = values.token.toLowerCase();
const delta = values.delta;
const vault = event.address.toLowerCase();
const from = delta > 0 ? vault : user;
const to = delta > 0 ? user : vault;
const value = delta > 0 ? delta : -delta;
return {
block: {
hash: blockHash,
number: blockNumber,
},
transaction: {
from: transactionFrom.toLowerCase(),
hash,
index: transactionIndex,
gasUsed,
},
event: {
logIndex,
address: address.toLowerCase(),
},
asset: token,
from,
to,
value,
};
}
function getClusterInputOutput(vault, logIndex, allEvents, transfers) {
function isVaultSwap(event) {
return (event.classifier.type === 'swap' &&
event.classifier.protocol === 'BalancerV2');
}
const emptyInputOutput = {
from: ZeroAddress,
to: ZeroAddress,
};
const sortedEvents = [...allEvents];
sortedEvents.sort((a, b) => a.logIndex - b.logIndex);
// Go back from current event until not a Balancer V2 swap found
let startIndex = allEvents.findIndex((event) => event.logIndex === logIndex);
while (startIndex > 0 && isVaultSwap(allEvents[startIndex - 1])) {
startIndex--;
}
const startSwap = allEvents[startIndex];
// Go forward from current event until not a Balancer V2 swap found
let endIndex = allEvents.findIndex((event) => event.logIndex === logIndex);
while (endIndex < allEvents.length - 1 &&
isVaultSwap(allEvents[endIndex + 1])) {
endIndex++;
}
const endSwap = allEvents[endIndex];
// Go forward from there until a swap found
let endTransferIndex = endIndex;
while (endTransferIndex < allEvents.length - 1 &&
allEvents[endTransferIndex + 1].classifier.type === 'transfer') {
endTransferIndex++;
}
// Make sure there is at least one transfer
if (endIndex === endTransferIndex) {
// Theoretically possible, but not economically viable
// Either way, w/o transfers, we can't deduct the sender
return emptyInputOutput;
}
const endTransfer = allEvents[endTransferIndex];
// Make sure all swaps in cluster form a valid chain
for (let i = startIndex; i < endIndex; i++) {
const swap = allEvents[i];
const nextSwap = allEvents[i + 1];
if (swap.classifier.type !== 'swap' ||
nextSwap.classifier.type !== 'swap') {
return emptyInputOutput;
}
const { assetOut: swapAssetOut, amountOut: swapAmountOut } = getSwapValues(swap);
const { assetIn: nextSwapAssetIn, amountIn: nextSwapAmountIn } = getSwapValues(nextSwap);
if (swapAssetOut !== nextSwapAssetIn) {
return emptyInputOutput;
}
if (swapAmountOut !== nextSwapAmountIn) {
return emptyInputOutput;
}
}
// Get cluster transfers, inflows, outflows
const clusterTransfers = transfers.filter((transfer) => transfer.event.logIndex > endSwap.logIndex &&
transfer.event.logIndex <= endTransfer.logIndex);
const clusterInflows = clusterTransfers.filter((transfer) => transfer.to === vault);
const clusterOutflows = clusterTransfers.filter((transfer) => transfer.from === vault);
// Get first inflow, last outflow
const firstInflow = clusterInflows[0];
const lastOutflow = clusterOutflows[clusterOutflows.length - 1];
// Make sure first inflow matches first swap in a cluster
if (clusterInflows.length > 0) {
const { assetIn, amountIn } = getSwapValues(startSwap);
if (firstInflow.asset !== assetIn ||
(firstInflow.value !== amountIn && firstInflow.event.address !== vault) ||
firstInflow.to !== vault) {
return emptyInputOutput;
}
}
// Make sure last outflow matches last swap in a cluster
if (clusterOutflows.length > 0) {
const { assetOut, amountOut } = getSwapValues(endSwap);
if (lastOutflow.asset !== assetOut ||
(lastOutflow.value !== amountOut &&
lastOutflow.event.address !== vault) ||
lastOutflow.from !== vault) {
return emptyInputOutput;
}
}
// Get input/output
const clusterFrom = clusterInflows.length === 0
? clusterOutflows.length === 0
? ZeroAddress
: lastOutflow.to
: firstInflow.from;
const clusterTo = clusterOutflows.length === 0
? clusterInflows.length === 0
? ZeroAddress
: firstInflow.from
: lastOutflow.to;
const from = logIndex === startSwap.logIndex ? clusterFrom : vault;
const to = logIndex === endSwap.logIndex ? clusterTo : vault;
return {
from,
to,
};
}
function getSwapValues(swap) {
const assetIn = swap.values.tokenIn.toLowerCase();
const assetOut = swap.values.tokenOut.toLowerCase();
const amountIn = swap.values.amountIn;
const amountOut = swap.values.amountOut;
return {
assetIn,
assetOut,
amountIn,
amountOut,
};
}
const CLASSIFIERS = {
swap: {
type: 'swap',
protocol: 'BalancerV2',
abi: vaultAbi,
isValid: isValidSwap,
parse: parseSwap,
pool: {
getCalls: getPoolCalls,
processCalls: processPoolCalls,
},
},
liquidityDeposit: {
type: 'liquidity_deposit',
protocol: 'BalancerV2',
abi: vaultAbi,
isValid: isValidDeposit,
parse: parseDeposit,
pool: {
getCalls: getPoolCalls,
processCalls: processPoolCalls,
},
},
liquidityWithdrawal: {
type: 'liquidity_withdrawal',
protocol: 'BalancerV2',
abi: vaultAbi,
isValid: isValidWithdrawal,
parse: parseWithdrawal,
pool: {
getCalls: getPoolCalls,
processCalls: processPoolCalls,
},
},
transfer: {
type: 'transfer',
abi: vaultAbi,
isValid: isValidTransfer,
parse: parseTransfer,
},
};
export default CLASSIFIERS;
//# sourceMappingURL=balancerV2.js.map