Dart调用Uniswap V3 QuoterV2 quoteExactOutput()模拟查询报错求助
quoteExactOutput()函数报错(execution reverted) 问题背景
我尝试用Dart调用Uniswap V3 QuoterV2合约的quoteExactOutput()函数,该函数并非view函数,但我仅需模拟调用(不向以太坊发送交易),目标是获取WETH与UNI代币的兑换比例(例如1.0 WETH对应多少UNI)。
我的Dart代码
import 'dart:convert'; import 'dart:io'; import 'package:token_price_uniswap/utils/constant.dart'; import 'package:web3dart/web3dart.dart'; import 'package:http/http.dart'; Future<DeployedContract> loadContract() async { final contract = DeployedContract( ContractAbi.fromJson(uniswapV3QuoterV2Abi, "UniswapV3Quoter2"), EthereumAddress.fromHex(uniswapV3QuoterV2Address)); return contract; } Future<String> sendTx( String funcname, List<dynamic> args, Web3Client ethClient, String privateKey, ) async { EthPrivateKey credentials = EthPrivateKey.fromHex(privateKey); DeployedContract contract = await loadContract(); final ethFunction = contract.function(funcname); final result = await ethClient.sendTransaction( credentials, Transaction.callContract( contract: contract, function: ethFunction, parameters: args, ), chainId: null, fetchChainIdFromNetworkId: true); return result; } Future<List<dynamic>> fetchFromContract( String funcName, List<dynamic> args, Web3Client ethClient) async { final contract = await loadContract(); final calledFunction = contract.function(funcName); final result = ethClient.call( contract: contract, function: calledFunction, params: args, ); return result; } Future<List> getWeth(Web3Client ethClient) async { List<dynamic> result = await fetchFromContract("WETH9", [], ethClient); print("called Uniswap V3 WETH"); return result; } Future<List<dynamic>> quoteExactOutput( Web3Client ethClient, List<int> path, String amountIn, ) async { final amountInBigInt = BigInt.parse(amountIn); final params = [ path, amountInBigInt, ]; print("params: $path"); final result = await fetchFromContract('quoteExactOutput', params, ethClient); return result; } Future<void> main(List<String> arguments) async { Web3Client? ethClient; Client? httpClient; httpClient = Client(); ethClient = Web3Client(rpcUrl, httpClient); String wethAmount = '1000000000000000000'; // 1 WETH final path = [uniswapTokenAddress, 3000, wethTokenAddress]; // call quote exact input final encodedPath = encode(path); var result = await quoteExactOutput(ethClient, encodedPath, wethAmount); print("1 WETH is equal to ${result[0]} UNI"); }
报错信息
Unhandled exception: RPCError: got code -32000 with msg "execution reverted". #0 JsonRPC.call (package:web3dart/json_rpc.dart:71:7) <asynchronous suspension> #1 Web3Client.makeRPCCall (package:web3dart/src/core/client.dart:53:20) <asynchronous suspension> #2 Web3Client.call (package:web3dart/src/core/client.dart:408:27) <asynchronous suspension> #3 quoteExactOutput(package:token_price_uniswap/services/uniswap_v3_functions.dart:108:18) <asynchronous suspension> #4 main (file:///D:/Eight/Programs/token_price_uniswap/bin/token_price_uniswap.dart:40:7) <asynchronous suspension>
问题排查
我怀疑错误源于path参数的编码不正确:Solidity中通过abi.encodePacked()生成该参数,但我不清楚Dart中的实现方式。我已用Python实现相同功能,通过eth_abi.packed.encode_packed()处理path参数后调用成功。
可行的Python代码
from web3 import Web3 import eth_abi.packed import json # setup our account and chain connection - we will use infura here rpc_endpoint = "https://mainnet.infura.io/v3/INFURA_ID" # web3 = Web3(Web3.HTTPProvider(rpc_endpoint)) # some addresses first UNISWAP_v3_QUOTER2_ADDRESS = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e" UNISWAP_TOKEN_ADDRESS = "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" WETH_TOKEN_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # Load abi f = open('./assets/uniswap_v3_quoter2.json') UNISWAP_V3_QUOTER2_ABI = json.load(f) # load the contracts quoter2_contract = web3.eth.contract(address=UNISWAP_v3_QUOTER2_ADDRESS, abi=UNISWAP_V3_QUOTER2_ABI) # prepare the function call parameters path = eth_abi.packed.encode_packed(['address','uint24','address'], [WETH_TOKEN_ADDRESS, 3000, UNISWAP_TOKEN_ADDRESS]) amount_to_buy_for = 1 * 10**18 # call quote exact output amount_out, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactOutput(path, amount_to_buy_for).call() print(f"for {amount_to_buy_for/10**18} WETH you would get {amount_out/10**18} UNI token")
Python代码输出
for 1.0 UNI token you have to pay 284.3230485269558 WETH
解决方案:Dart中实现abi.encodePacked编码path参数
在Dart中,你可以手动实现abi.encodePacked的逻辑来处理path参数——因为path的结构是[address, uint24, address],对应20字节地址 + 3字节手续费 + 20字节地址,直接拼接这些字节即可。
添加以下编码函数:
Uint8List encodePackedPath(String tokenAddress1, int fee, String tokenAddress2) { // 转换第一个地址为20字节Uint8List final addr1Bytes = EthereumAddress.fromHex(tokenAddress1).bytes; // 转换uint24手续费为3字节大端序Uint8List final feeBytes = Uint8List(3)..buffer.asByteData().setUint24(0, fee, Endian.big); // 转换第二个地址为20字节Uint8List final addr2Bytes = EthereumAddress.fromHex(tokenAddress2).bytes; // 拼接所有字节 final combined = Uint8List(addr1Bytes.length + feeBytes.length + addr2Bytes.length); var offset = 0; combined.setRange(offset, offset + addr1Bytes.length, addr1Bytes); offset += addr1Bytes.length; combined.setRange(offset, offset + feeBytes.length, feeBytes); offset += feeBytes.length; combined.setRange(offset, offset + addr2Bytes.length, addr2Bytes); return combined; }
然后修改main函数中的path处理逻辑:
Future<void> main(List<String> arguments) async { Web3Client? ethClient; Client? httpClient; httpClient = Client(); ethClient = Web3Client(rpcUrl, httpClient); String wethAmount = '1000000000000000000'; // 1 WETH // 注意:quoteExactOutput的path顺序是「输出代币 → 手续费 → 输入代币」,如果要计算1 WETH能换多少UNI,path应该是UNI → 3000 → WETH final encodedPath = encodePackedPath(uniswapTokenAddress, 3000, wethTokenAddress); var result = await quoteExactOutput(ethClient, encodedPath, wethAmount); // 注意:quoteExactOutput返回的第一个值是需要的输入金额,如果你传的是要得到的UNI数量,这里才是对应的WETH数量;如果要算1 WETH换多少UNI,需要调整参数为amountOut是UNI的数量,或者改用quoteExactInput print("1 WETH is equal to ${BigInt.parse(result[0].toString()) / BigInt.from(10).pow(18)} UNI"); }
另外需要注意:quoteExactOutput的参数定义是function quoteExactOutput(bytes calldata path, uint256 amountOut) external returns (uint256 amountIn, ...),即你传入要得到的amountOut(比如1 UNI),返回需要的amountIn(对应的WETH数量)。如果要计算1 WETH能换多少UNI,应该调用quoteExactInput函数,或者调整quoteExactOutput的参数为amountOut是你想得到的UNI数量,path为WETH → 3000 → UNI。
内容的提问来源于stack exchange,提问作者Eight

