//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import "openzeppelin-contracts/contracts/access/Ownable.sol";
import "contracts/MevShareCaptureLogger.sol";
import "./MevShareCTF.sol";
contract MevShareCTFMagicNumber is MevShareCTFBase {
uint256 public activeBlock;
uint256 private magicNumber;
event Activate(uint256 lowerBound, uint256 upperBound);
constructor(MevShareCaptureLogger _mevShareCaptureLogger) MevShareCTFBase(_mevShareCaptureLogger) payable {
}
function activateRewardMagicNumber(uint256 _lowerBound, uint256 _upperBound, uint256 _magicNumber) external payable onlyOwner {
require (_lowerBound <= _magicNumber && _upperBound >= _magicNumber);
activeBlock = block.number;
magicNumber = _magicNumber;
emit Activate(_lowerBound, _upperBound);
}
function claimRewardInternal(uint256 _magicNumber, uint256 _captureId) internal returns (bool) {
if (activeBlock != block.number || _magicNumber != magicNumber) {
return false;
}
activeBlock = 0;
magicNumber = 0;
mevShareCaptureLogger.registerCapture(_captureId, tx.origin);
return true;
}
}
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import "contracts/MevShareCaptureLogger.sol";
import "./MevShareCTFMagicNumber.sol";
contract MevShareCTFMagicNumberV1 is MevShareCTFMagicNumber {
constructor(MevShareCaptureLogger _mevShareCaptureLogger) MevShareCTFMagicNumber(_mevShareCaptureLogger) payable {
}
function claimReward(uint256 _magicNumber) external {
require(claimRewardInternal(_magicNumber, 201));
}
}
So the data
field contains upper bound and lower bound of the magic number.
import MevShareClient, {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
TransactionOptions
} from "@flashbots/mev-share-client"
import { Contract, JsonRpcProvider, Wallet } from 'ethers'
import { mevShareCTFMagicNumber_ABI } from './abi'
import dotenv from "dotenv"
dotenv.config()
const TX_GAS_LIMIT = 400000
const MAX_GAS_PRICE = 20n
const MAX_PRIORITY_FEE = 20n
const GWEI = 10n ** 9n
const RPC_URL = process.env.RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY || Wallet.createRandom().privateKey
const provider = new JsonRpcProvider(RPC_URL)
const signer = new Wallet(PRIVATE_KEY, provider)
const mevShare = MevShareClient.useEthereumGoerli(signer)
const MevShareCTFMagicNumber_ADDRESS = "0x118Bcb654d9A7006437895B51b5cD4946bF6CdC2"
const mevShareCTFMagicNumber = new Contract(MevShareCTFMagicNumber_ADDRESS, MevShareCTFMagicNumber_ABI, signer)
function transactionIsRelevant(pendingTx: IPendingTransaction, PAIR_ADDRESS: string) {
return ((pendingTx.logs || []).some(log => log.address === MevShareCTFMagicNumber_ADDRESS.toLowerCase()))
}
async function getSignedBackrunTx(nonce: number , _magicNumber: Number) {
const backrunTx = await mevShareCTFMagicNumber.claimReward.populateTransaction(_magicNumber)
const backrunTxFull = {
...backrunTx,
chainId: 5,
maxFeePerGas: MAX_GAS_PRICE * GWEI,
maxPriorityFeePerGas: MAX_PRIORITY_FEE * GWEI,
gasLimit: TX_GAS_LIMIT,
nonce: nonce
}
return signer.signTransaction(backrunTxFull)
}
async function backrunAttempt( currentBlockNumber: number, nonce: number, pendingTxHash: string, _magicNumber: Number ) {
const backrunSignedTx = await getSignedBackrunTx(nonce, _magicNumber)
try {
const sendBundleResult = await mevShare.sendBundle({
inclusion: { block: currentBlockNumber + 1 },
body: [
{ hash: pendingTxHash },
{ tx: backrunSignedTx, canRevert: false }
]
},)
console.log('Bundle Hash: ' + sendBundleResult.bundleHash)
} catch (e) {
console.log('Error: ', e)
}
}
async function main() {
mevShare.on('transaction', async ( pendingTx: IPendingTransaction ) => {
// console.log(pendingTx)
if (transactionIsRelevant(pendingTx, MevShareCTFMagicNumber_ADDRESS)) {
console.log(pendingTx)
const currentBlockNumber = await provider.getBlockNumber()
const nonce = await signer.getNonce('latest')
const dataOne = "0x" + (pendingTx.logs || [])[0].data.slice(-14)
const dataTwo = "0x" + (pendingTx.logs || [])[0].data.slice(-78, -64)
let lowerBound
let upperBound
if (dataOne < dataTwo) {
lowerBound = dataOne
upperBound = dataTwo
}
else {
upperBound = dataOne
lowerBound = dataTwo
}
console.log("upperBound: ", upperBound)
console.log("lowerBound: ", lowerBound)
const upperBoundDecimal = Number(upperBound)
const lowerBoundDecimal = Number(lowerBound)
console.log("upperBoundDecimal: ", upperBoundDecimal)
console.log("lowerBoundDecimal: ", lowerBoundDecimal)
for (let magicNumber = lowerBoundDecimal; magicNumber <= upperBoundDecimal; magicNumber++) {
backrunAttempt(currentBlockNumber, nonce, pendingTx.hash, magicNumber);
}
}
})
}
main()
//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
import "contracts/MevShareCaptureLogger.sol";
import "./MevShareCTFMagicNumber.sol";
contract MevShareCTFMagicNumberV2 is MevShareCTFMagicNumber {
constructor(MevShareCaptureLogger _mevShareCaptureLogger) MevShareCTFMagicNumber(_mevShareCaptureLogger) payable {
}
function claimReward(uint256 _magicNumber) external {
require(tx.origin == msg.sender);
require(claimRewardInternal(_magicNumber, 202));
}
}
require(tx.origin == msg.sender)
means we have to override msg.sender
. This is easy to do in populateTransaction()
.
import MevShareClient, {
BundleParams,
HintPreferences,
IPendingBundle,
IPendingTransaction,
TransactionOptions
} from "@flashbots/mev-share-client"
import { Contract, JsonRpcProvider, Wallet } from 'ethers'
import { mevShareCTFMagicNumber_ABI } from './abi'
import dotenv from "dotenv"
dotenv.config()
const TX_GAS_LIMIT = 400000
const MAX_GAS_PRICE = 20n
const MAX_PRIORITY_FEE = 20n
const GWEI = 10n ** 9n
const RPC_URL = process.env.RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY || Wallet.createRandom().privateKey
const provider = new JsonRpcProvider(RPC_URL)
const signer = new Wallet(PRIVATE_KEY, provider)
const mevShare = MevShareClient.useEthereumGoerli(signer)
const MevShareCTFMagicNumber_ADDRESS = "0x9BE957D1c1c1F86Ba9A2e1215e9d9EEFdE615a56"
const mevShareCTFMagicNumber = new Contract(MevShareCTFMagicNumber_ADDRESS, MevShareCTFMagicNumber_ABI, signer)
function transactionIsRelevant(pendingTx: IPendingTransaction, PAIR_ADDRESS: string) {
return ((pendingTx.logs || []).some(log => log.address === MevShareCTFMagicNumber_ADDRESS.toLowerCase()))
}
async function getSignedBackrunTx(nonce: number , _magicNumber: Number) {
const backrunTx = await mevShareCTFMagicNumber.claimReward.populateTransaction(_magicNumber, {from: signer.address})
const backrunTxFull = {
...backrunTx,
chainId: 5,
maxFeePerGas: MAX_GAS_PRICE * GWEI,
maxPriorityFeePerGas: MAX_PRIORITY_FEE * GWEI,
gasLimit: TX_GAS_LIMIT,
nonce: nonce
}
return signer.signTransaction(backrunTxFull)
}
async function backrunAttempt( currentBlockNumber: number, nonce: number, pendingTxHash: string, _magicNumber: Number ) {
const backrunSignedTx = await getSignedBackrunTx(nonce, _magicNumber)
try {
const sendBundleResult = await mevShare.sendBundle({
inclusion: { block: currentBlockNumber + 1 },
body: [
{ hash: pendingTxHash },
{ tx: backrunSignedTx, canRevert: false }
]
},)
console.log('Bundle Hash: ' + sendBundleResult.bundleHash)
} catch (e) {
console.log('Error: ', e)
}
}
async function main() {
mevShare.on('transaction', async ( pendingTx: IPendingTransaction ) => {
// console.log(pendingTx)
if (transactionIsRelevant(pendingTx, MevShareCTFMagicNumber_ADDRESS)) {
console.log(pendingTx)
const currentBlockNumber = await provider.getBlockNumber()
const nonce = await signer.getNonce('latest')
const dataOne = "0x" + (pendingTx.logs || [])[0].data.slice(-14)
const dataTwo = "0x" + (pendingTx.logs || [])[0].data.slice(-78, -64)
let lowerBound
let upperBound
if (dataOne < dataTwo) {
lowerBound = dataOne
upperBound = dataTwo
}
else {
upperBound = dataOne
lowerBound = dataTwo
}
console.log("upperBound: ", upperBound)
console.log("lowerBound: ", lowerBound)
const upperBoundDecimal = Number(upperBound)
const lowerBoundDecimal = Number(lowerBound)
console.log("upperBoundDecimal: ", upperBoundDecimal)
console.log("lowerBoundDecimal: ", lowerBoundDecimal)
for (let magicNumber = lowerBoundDecimal; magicNumber <= upperBoundDecimal; magicNumber++) {
backrunAttempt(currentBlockNumber, nonce, pendingTx.hash, magicNumber);
}
}
})
}
main()