// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
// TokenVerdict sell simulator. Never deployed: its runtime bytecode is placed at a throwaway address through an
// eth_call state override (with an ETH balance, or a quote-token balance override), so nothing is ever sent on chain.
// One call: buy `token` with `amountIn` of `quote` directly on `pool`, send a slice to a fresh wallet (transfer test),
// then sell the rest back on the same pool. Every step is try/catch so the result reports where it failed.
interface IERC20 { function balanceOf(address) external view returns (uint256); function transfer(address, uint256) external returns (bool); }
interface IWETH { function deposit() external payable; }
interface IV2 { function getReserves() external view returns (uint112, uint112, uint32); function token0() external view returns (address); function swap(uint256, uint256, address, bytes calldata) external; }
interface ISolidly { function getAmountOut(uint256, address) external view returns (uint256); }
struct PoolKey { address currency0; address currency1; uint24 fee; int24 tickSpacing; address hooks; }
struct SwapParams { bool zeroForOne; int256 amountSpecified; uint160 sqrtPriceLimitX96; }
interface IPM {
function unlock(bytes calldata) external returns (bytes memory);
function swap(PoolKey memory, SwapParams memory, bytes calldata) external returns (int256);
function sync(address) external;
function settle() external payable returns (uint256);
function take(address, address, uint256) external;
}
interface IV3 { function token0() external view returns (address); function swap(address, bool, int256, uint160, bytes calldata) external returns (int256, int256); }
contract TVSim {
address constant WETH = 0x4200000000000000000000000000000000000006;
uint160 constant MIN_SQRT = 4295128740;
uint160 constant MAX_SQRT = 1461446703485210103287273103530467335439183471;
address constant WALLET = 0x7E57000000000000000000000000000000007E57;
struct R {
bool funded; bool buyOk; bool transferOk; bool sellTransferOk; bool sellOk;
uint256 buyExpected; uint256 buyReceived;
uint256 transferSent; uint256 transferReceived;
uint256 sellAmount; uint256 poolReceived; uint256 sellExpected; uint256 sellReceived;
uint256 buyGas; uint256 sellGas;
bytes buyErr; bytes sellErr;
}
address private cbToken; // token the active V3 callback must pay
// kind: 1 = Uniswap V2 fork (0.3% fee assumed, 1% retry), 2 = Solidly / Aerodrome classic, 3 = V3-style concentrated liquidity
function run(address pool, uint8 kind, address token, address quote, uint256 amountIn) external returns (R memory r) {
if (quote == WETH && IERC20(WETH).balanceOf(address(this)) < amountIn) IWETH(WETH).deposit{value: amountIn}();
r.funded = IERC20(quote).balanceOf(address(this)) >= amountIn;
if (!r.funded) return r;
// ---- buy ----
uint256 b0 = IERC20(token).balanceOf(address(this));
uint256 g = gasleft();
if (kind == 3) {
bool zf = IV3(pool).token0() == quote;
cbToken = quote;
try IV3(pool).swap(address(this), zf, int256(amountIn), zf ? MIN_SQRT : MAX_SQRT, "") returns (int256 a0, int256 a1) {
r.buyOk = true; r.buyExpected = uint256(-(zf ? a1 : a0));
} catch (bytes memory e) { r.buyErr = e; }
} else {
(bool ok, bytes memory e) = _v2Buy(pool, kind, token, quote, amountIn, r);
r.buyOk = ok; if (!ok) r.buyErr = e;
}
r.buyGas = g - gasleft();
if (!r.buyOk) return r;
r.buyReceived = IERC20(token).balanceOf(address(this)) - b0;
if (r.buyReceived == 0) return r;
// ---- wallet transfer test (10%) ----
r.transferSent = r.buyReceived / 10;
if (r.transferSent > 0) {
uint256 w0 = IERC20(token).balanceOf(WALLET);
(bool ok,) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, WALLET, r.transferSent));
r.transferOk = ok;
if (ok) r.transferReceived = IERC20(token).balanceOf(WALLET) - w0;
}
// ---- sell ----
r.sellAmount = IERC20(token).balanceOf(address(this));
uint256 q0 = IERC20(quote).balanceOf(address(this));
g = gasleft();
if (kind == 3) {
bool zf = IV3(pool).token0() == token;
cbToken = token;
try IV3(pool).swap(address(this), zf, int256(r.sellAmount), zf ? MIN_SQRT : MAX_SQRT, "") returns (int256 a0, int256 a1) {
r.sellOk = true; r.sellTransferOk = true; r.poolReceived = uint256(zf ? a0 : a1); r.sellExpected = uint256(-(zf ? a1 : a0));
} catch (bytes memory e) { r.sellErr = e; }
} else {
uint256 p0 = IERC20(token).balanceOf(pool);
(bool ok, bytes memory e) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, pool, r.sellAmount));
if (!ok) { r.sellErr = e; }
else {
r.sellTransferOk = true;
r.poolReceived = IERC20(token).balanceOf(pool) - p0;
(ok, e) = _v2Sell(pool, kind, token, quote, r);
r.sellOk = ok; if (!ok) r.sellErr = e;
}
}
r.sellGas = g - gasleft();
if (r.sellOk) r.sellReceived = IERC20(quote).balanceOf(address(this)) - q0;
}
function _out(address pool, uint8 kind, address tokenIn, uint256 amtIn, uint256 feeBps) internal view returns (uint256) {
if (kind == 2) return ISolidly(pool).getAmountOut(amtIn, tokenIn);
(uint112 r0, uint112 r1,) = IV2(pool).getReserves();
bool zIn = IV2(pool).token0() == tokenIn;
uint256 rin = zIn ? r0 : r1; uint256 rout = zIn ? r1 : r0;
uint256 a = amtIn * (10000 - feeBps);
return (a * rout) / (rin * 10000 + a);
}
function _swapOut(address pool, address tokenOut, uint256 out) internal returns (bool ok, bytes memory e) {
bool z = IV2(pool).token0() == tokenOut;
try IV2(pool).swap(z ? out : 0, z ? 0 : out, address(this), "") { ok = true; } catch (bytes memory err) { e = err; }
}
function _v2Buy(address pool, uint8 kind, address token, address quote, uint256 amountIn, R memory r) internal returns (bool ok, bytes memory e) {
uint256 out = _out(pool, kind, quote, amountIn, 30);
r.buyExpected = out;
IERC20(quote).transfer(pool, amountIn);
(ok, e) = _swapOut(pool, token, out);
if (!ok && kind == 1) { out = _out(pool, kind, quote, amountIn, 100); r.buyExpected = out; (ok, e) = _swapOut(pool, token, out); }
}
function _v2Sell(address pool, uint8 kind, address token, address quote, R memory r) internal returns (bool ok, bytes memory e) {
uint256 out = _out(pool, kind, token, r.poolReceived, 30);
r.sellExpected = out;
(ok, e) = _swapOut(pool, quote, out);
if (!ok && kind == 1) { out = _out(pool, kind, token, r.poolReceived, 100); r.sellExpected = out; (ok, e) = _swapOut(pool, quote, out); }
}
// ---- Uniswap v4 (singleton PoolManager, flash accounting) ----
address constant PM = 0x498581fF718922c3f8e6A244956aF099B2652b2b;
function _bal(address c, address who) internal view returns (uint256) { return c == address(0) ? who.balance : IERC20(c).balanceOf(who); }
function run4(PoolKey calldata key, address token, uint256 amountIn) external returns (R memory r) {
address quote = key.currency0 == token ? key.currency1 : key.currency0;
if (quote == WETH && IERC20(WETH).balanceOf(address(this)) < amountIn) IWETH(WETH).deposit{value: amountIn}();
r.funded = _bal(quote, address(this)) >= amountIn;
if (!r.funded) return r;
uint256 b0 = IERC20(token).balanceOf(address(this));
uint256 g = gasleft();
try IPM(PM).unlock(abi.encode(uint8(1), key, token, quote, amountIn)) returns (bytes memory ret) {
r.buyOk = true; r.buyExpected = abi.decode(ret, (uint256));
} catch (bytes memory e) { r.buyErr = e; }
r.buyGas = g - gasleft();
if (!r.buyOk) return r;
r.buyReceived = IERC20(token).balanceOf(address(this)) - b0;
if (r.buyReceived == 0) return r;
r.transferSent = r.buyReceived / 10;
if (r.transferSent > 0) {
uint256 w0 = IERC20(token).balanceOf(WALLET);
(bool ok,) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, WALLET, r.transferSent));
r.transferOk = ok;
if (ok) r.transferReceived = IERC20(token).balanceOf(WALLET) - w0;
}
r.sellAmount = IERC20(token).balanceOf(address(this));
uint256 q0 = _bal(quote, address(this));
g = gasleft();
try IPM(PM).unlock(abi.encode(uint8(2), key, token, quote, r.sellAmount)) returns (bytes memory ret) {
r.sellOk = true; r.sellTransferOk = true;
(r.poolReceived, r.sellExpected) = abi.decode(ret, (uint256, uint256));
} catch (bytes memory e) { r.sellErr = e; }
r.sellGas = g - gasleft();
if (r.sellOk) r.sellReceived = _bal(quote, address(this)) - q0;
}
function unlockCallback(bytes calldata data) external returns (bytes memory) {
require(msg.sender == PM, "pm");
(uint8 stage, PoolKey memory key, address token, address quote, uint256 amt) = abi.decode(data, (uint8, PoolKey, address, address, uint256));
if (stage == 1) {
bool zf = key.currency0 == quote;
int256 d = IPM(PM).swap(key, SwapParams(zf, -int256(amt), zf ? MIN_SQRT : MAX_SQRT), "");
int128 qd = zf ? int128(d >> 128) : int128(d);
int128 td = zf ? int128(d) : int128(d >> 128);
_settle(quote, uint256(uint128(-qd)));
IPM(PM).take(token, address(this), uint256(uint128(td)));
return abi.encode(uint256(uint128(td)));
}
// stage 2: pay the tokens in first (settle credits what the PoolManager actually received), then swap exactly that
IPM(PM).sync(token);
IERC20(token).transfer(PM, amt);
uint256 paid = IPM(PM).settle();
bool zf2 = key.currency0 == token;
int256 d2 = IPM(PM).swap(key, SwapParams(zf2, -int256(paid), zf2 ? MIN_SQRT : MAX_SQRT), "");
int128 qd2 = zf2 ? int128(d2) : int128(d2 >> 128);
int128 td2 = zf2 ? int128(d2 >> 128) : int128(d2);
if (td2 < 0 && uint256(uint128(-td2)) < paid) IPM(PM).take(token, address(this), paid - uint256(uint128(-td2)));
IPM(PM).take(quote, address(this), uint256(uint128(qd2)));
return abi.encode(paid, uint256(uint128(qd2)));
}
function _settle(address c, uint256 amt) internal {
if (c == address(0)) { IPM(PM).settle{value: amt}(); return; }
IPM(PM).sync(c);
IERC20(c).transfer(PM, amt);
IPM(PM).settle();
}
function _pay(int256 a0, int256 a1) internal {
uint256 owed = uint256(a0 > 0 ? a0 : a1);
IERC20(cbToken).transfer(msg.sender, owed);
}
function uniswapV3SwapCallback(int256 a0, int256 a1, bytes calldata) external { _pay(a0, a1); }
function pancakeV3SwapCallback(int256 a0, int256 a1, bytes calldata) external { _pay(a0, a1); }
function algebraSwapCallback(int256 a0, int256 a1, bytes calldata) external { _pay(a0, a1); }
receive() external payable {}
}
agentgate-source: code mirror on Cloudflare Pages. All repos