如何在不使用ABI的情况下用ethers.rs调用智能合约函数?
用ethers.rs通过Function Selector调用合约函数(无需ABI)
要实现不依赖ABI、仅通过合约地址和目标函数信息调用合约,核心是手动生成function selector并编码参数,最终组装交易的data字段。以下是具体实现步骤:
1. 生成Function Selector
Function Selector是函数签名的Keccak-256哈希前4字节,函数签名格式为函数名(参数类型1,参数类型2,...)(比如transfer(address,uint256))。用ethers.rs的工具函数即可生成:
use ethers::core::utils::keccak256; // 定义目标函数的标准签名 let function_signature = "transfer(address,uint256)"; // 计算哈希并截取前4字节作为selector let selector = &keccak256(function_signature.as_bytes())[0..4];
2. 编码函数参数
按照Solidity ABI规则编码参数,ethers.rs提供了encode函数直接处理,只需传入参数对应的Token数组:
use ethers::abi::{encode, Token}; use ethers::core::types::{Address, U256}; // 构造与函数参数匹配的Token集合 let params = vec![ Token::Address("0x1234567890abcdef1234567890abcdef12345678".parse::<Address>().unwrap()), Token::Uint(U256::from(1000)), ]; // 编码参数 let encoded_params = encode(¶ms);
3. 组装交易Data字段
将selector与编码后的参数拼接,得到交易所需的data内容:
let mut data = selector.to_vec(); data.extend(encoded_params);
4. 构建并发送交易
最后用ethers.rs的Provider和Signer构建交易,发送至目标合约地址:
use ethers::{ prelude::*, core::types::{TransactionRequest, U256}, }; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 连接以太坊节点(示例为本地节点,可替换为Infura/Alchemy等远程节点) let provider = Provider::<Http>::try_from("http://localhost:8545")?; // 加载签名者(用私钥实例化钱包) let wallet = "YOUR_PRIVATE_KEY".parse::<LocalWallet>()?; let signer = wallet.connect(provider); // 目标合约地址 let contract_address = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0".parse::<Address>()?; // 生成selector let function_signature = "transfer(address,uint256)"; let selector = &keccak256(function_signature.as_bytes())[0..4]; // 编码参数 let params = vec![ Token::Address("0x1234567890abcdef1234567890abcdef12345678".parse().unwrap()), Token::Uint(U256::from(1000)), ]; let encoded_params = encode(¶ms); // 组装data字段 let mut data = selector.to_vec(); data.extend(encoded_params); // 构建交易请求 let tx = TransactionRequest::new() .to(contract_address) .data(data) .value(U256::from(0)) // 无需ETH的函数设为0 .gas_price(U256::from(20_000_000_000)) // 可根据网络动态调整,或用provider估算 .gas(provider.estimate_gas(&tx, None).await?); // 自动估算gas更可靠 // 发送交易并等待确认 let tx_response = signer.send_transaction(tx, None).await?; println!("交易哈希: {:?}", tx_response.tx_hash()); let receipt = tx_response.confirmations(1).await?; println!("交易收据: {:?}", receipt); Ok(()) }
关键注意事项
- 函数签名必须严格匹配Solidity定义:参数类型要写全称(比如
uint256不能简写为uint,address不能省略)。 - 参数编码需对应正确的Token类型:比如地址用
Token::Address,大数值用Token::Uint/Token::Int。 - 若调用view/pure只读函数,无需发送交易,改用
provider.call(&tx, None).await?获取返回值,再用ethers::abi::decode解析结果。
内容的提问来源于stack exchange,提问作者NewToCode
相关产品推荐
相关产品推荐

