pragma solidity >=0.6.5; pragma experimental ABIEncoderV2; import "forge-std/Script.sol"; import "../src/interfaces/IUniswapV3.sol"; import "../src/UniswapV3MultiQuoter.sol"; import "../src/UniswapV3Common.sol"; /// @title Writes UniswapV3 gas estimates to file /// @notice Gas estimates for UniswapV3QuoterV2 and UniswapV3MultiQuoter are written with each quote function /// being called from a clean slate (no warm storage reads). UniswapV3QuoterV2 reverts at the end of the quote /// function while UniswapV3MultiQuoter doesn't. Hence, after very call to UniswapV3MultiQuoter's quote function, /// the data writer's functions revert. contract UniswapV3GasDataWriter is Script, UniswapV3Common { string private filePath; IUniswapV3QuoterV2 private immutable uniQuoter; IUniswapV3MultiQuoter private immutable multiQuoter; IUniswapV3Factory private immutable factory; string private constant headers = "path,amount,isInput,ticksCrossed,mqGasEstimate,uqGasEstimate"; constructor( string memory filePath_, IUniswapV3QuoterV2 uniQuoter_, IUniswapV3MultiQuoter multiQuoter_, IUniswapV3Factory factory_ ) { filePath = filePath_; uniQuoter = uniQuoter_; multiQuoter = multiQuoter_; factory = factory_; // write the CSV file headers vm.writeLine(filePath, headers); } /// @notice Writes exact input gas estimates for given UniswapV3 path and set of amounts. /// @param uniswapPath The path of the swap, i.e. each token pair and the pool fee /// @param amounts The amounts in of the first token to swap function writeGasDataForExactInputs(bytes memory uniswapPath, uint256[] memory amounts) external { uint32[] memory uqTicksCrossed = new uint32[](amounts.length); uint256[] memory uqInputGasEst = new uint256[](amounts.length); for (uint256 i = 0; i < amounts.length; ++i) { try uniQuoter.quoteExactInput{gas: 700e3}(uniswapPath, amounts[i]) returns ( uint256, uint160[] memory, uint32[] memory ticksCrossed, uint256 gasEstimate ) { uqTicksCrossed[i] = ticksCrossed[0]; uqInputGasEst[i] = gasEstimate; } catch {} } uint256[] memory mqInputGasEst; try multiQuoter.quoteExactMultiInput(factory, uniswapPath, amounts) {} catch (bytes memory reason) { (, , mqInputGasEst) = catchMultiSwapResult(reason); } for (uint256 i = 0; i < amounts.length; ++i) { if (mqInputGasEst[i] == 0 || uqInputGasEst[i] == 0) { continue; } string memory pathStr = toHexString(uniswapPath); string memory amountStr = toHexString(abi.encodePacked(amounts[i])); string memory ticksCrossedStr = toHexString(abi.encodePacked(uqTicksCrossed[i])); string memory mqGasEstimateStr = toHexString(abi.encodePacked(mqInputGasEst[i])); string memory uqGasEstimateStr = toHexString(abi.encodePacked(uqInputGasEst[i])); string memory row = string( abi.encodePacked( pathStr, ",", amountStr, ",", "TRUE", ",", ticksCrossedStr, ",", mqGasEstimateStr, ",", uqGasEstimateStr ) ); vm.writeLine(filePath, row); } revert(); // revert to clear warm storage from multiquoter } /// @notice Writes exact output gas estimates for given UniswapV3 path and set of amounts. /// @param uniswapPath The path of the swap, i.e. each token pair and the pool fee /// @param amounts The amounts out of the first token to swap function writeGasDataForExactOutputs(bytes memory uniswapPath, uint256[] memory amounts) external { uint32[] memory uqTicksCrossed = new uint32[](amounts.length); uint256[] memory uqOutputGasEst = new uint256[](amounts.length); for (uint256 i = 0; i < amounts.length; ++i) { try uniQuoter.quoteExactOutput{gas: 700e3}(uniswapPath, amounts[i]) returns ( uint256, uint160[] memory, uint32[] memory ticksCrossed, uint256 gasEstimate ) { uqTicksCrossed[i] = ticksCrossed[0]; uqOutputGasEst[i] = gasEstimate; } catch {} } uint256[] memory mqOutputGasEst; try multiQuoter.quoteExactMultiOutput(factory, uniswapPath, amounts) {} catch (bytes memory reason) { (, , mqOutputGasEst) = catchMultiSwapResult(reason); } for (uint256 i = 0; i < amounts.length; ++i) { if (mqOutputGasEst[i] == 0 || uqOutputGasEst[i] == 0) { continue; } string memory pathStr = toHexString(uniswapPath); string memory amountStr = toHexString(abi.encodePacked(amounts[i])); string memory ticksCrossedStr = toHexString(abi.encodePacked(uqTicksCrossed[i])); string memory mqGasEstimateStr = toHexString(abi.encodePacked(mqOutputGasEst[i])); string memory uqGasEstimateStr = toHexString(abi.encodePacked(uqOutputGasEst[i])); string memory row = string( abi.encodePacked( pathStr, ",", amountStr, ",", "FALSE", ",", ticksCrossedStr, ",", mqGasEstimateStr, ",", uqGasEstimateStr ) ); vm.writeLine(filePath, row); } revert(); // revert to clear warm storage from multiquoter } function toHexString(bytes memory buffer) private pure returns (string memory) { // Fixed buffer size for hexadecimal convertion bytes memory converted = new bytes(buffer.length * 2); bytes memory _base = "0123456789abcdef"; for (uint256 i = 0; i < buffer.length; i++) { converted[i * 2] = _base[uint8(buffer[i]) / _base.length]; converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length]; } return string(abi.encodePacked("0x", converted)); } } /// @title Iterates through multiple token paths and pools and writes gas estimate data to file. /// @notice The generated CSV file should be used †o perform a linear regression between /// UniswapV3QuoterV2 gas results and UniswapV3MultiQuoter gas results. The results from those regression /// should be used to populate gas scaling parameters for UniswapV3MultiQuoter. contract UniswapV3GasDataCollector is Script, UniswapV3Common { IUniswapV3QuoterV2 private constant uniQuoter = IUniswapV3QuoterV2(0x61fFE014bA17989E743c5F6cB21bF9697530B21e); IUniswapV3Factory private factory; UniswapV3MultiQuoter private multiQuoter; string private constant filePath = "UniswapV3GasData.csv"; function setUp() public { factory = uniQuoter.factory(); multiQuoter = new UniswapV3MultiQuoter(); } function run() public { UniswapV3GasDataWriter w = new UniswapV3GasDataWriter(filePath, uniQuoter, multiQuoter, factory); address[12] memory tokenList; tokenList[0] = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // USDC tokenList[1] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // WETH tokenList[2] = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // USDT tokenList[3] = 0x6B175474E89094C44Da98b954EedeAC495271d0F; // DAI tokenList[4] = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; // WBTC tokenList[5] = 0xD533a949740bb3306d119CC777fa900bA034cd52; // CRV tokenList[6] = 0x111111111117dC0aa78b770fA6A738034120C302; // 1INCH tokenList[7] = 0xE41d2489571d322189246DaFA5ebDe1F4699F498; // ZRX tokenList[8] = 0x4d224452801ACEd8B2F0aebE155379bb5D594381; // APE tokenList[9] = 0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919; // RAI tokenList[10] = 0x3845badAde8e6dFF049820680d1F14bD3903a5d0; // SAND tokenList[11] = 0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0; // MATIC uint256[][] memory amountsList = new uint256[][](10); for (uint256 i = 0; i < amountsList.length; ++i) { amountsList[i] = new uint256[](10); for (uint256 j = 0; j < amountsList[i].length; ++j) { amountsList[i][j] = (j + 1) * 10 ** (2 * i + 4); } } for (uint256 i = 0; i < tokenList.length; ++i) { for (uint256 j = 0; j < tokenList.length; ++j) { console.log("Processing i=%d, j=%d", i + 1, j + 1); console.log("--- %d / %d", i * tokenList.length + j + 1, tokenList.length * tokenList.length); if (i == j) { continue; } IERC20TokenV06[] memory tokenPath = new IERC20TokenV06[](2); tokenPath[0] = IERC20TokenV06(tokenList[i]); tokenPath[1] = IERC20TokenV06(tokenList[j]); for (uint256 k = 0; k < amountsList.length; ++k) { IUniswapV3Pool[][] memory poolPaths = getPoolPaths( factory, multiQuoter, tokenPath, amountsList[k][amountsList[k].length - 1] ); for (uint256 l = 0; l < poolPaths.length; ++l) { if (!isValidPoolPath(poolPaths[l])) { continue; } bytes memory uniswapPath = toUniswapPath(tokenPath, poolPaths[l]); try w.writeGasDataForExactInputs(uniswapPath, amountsList[k]) {} catch {} try w.writeGasDataForExactOutputs(uniswapPath, amountsList[k]) {} catch {} } } } } } }