281 lines
8.6 KiB
Rust
281 lines
8.6 KiB
Rust
//! 以太坊桥插件实现(独立版本)
|
||
|
||
use ethers::prelude::*;
|
||
use serde::{Deserialize, Serialize};
|
||
use std::sync::Arc;
|
||
|
||
/// 桥插件错误
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub enum BridgeError {
|
||
#[error("Network error: {0}")]
|
||
Network(String),
|
||
|
||
#[error("Invalid proof: {0}")]
|
||
InvalidProof(String),
|
||
|
||
#[error("Plugin error: {0}")]
|
||
PluginError(String),
|
||
}
|
||
|
||
/// Token信息
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct TokenInfo {
|
||
pub contract_address: Option<String>,
|
||
pub symbol: String,
|
||
pub decimals: u8,
|
||
}
|
||
|
||
/// 以太坊桥插件
|
||
pub struct EthereumBridgePlugin {
|
||
/// 链ID(1=主网,5=Goerli,11155111=Sepolia)
|
||
chain_id: u64,
|
||
/// RPC提供商
|
||
provider: Arc<Provider<Http>>,
|
||
/// 桥合约地址
|
||
bridge_contract_address: String,
|
||
/// 链名称
|
||
chain_name: String,
|
||
/// Logo URL
|
||
_logo_url: String,
|
||
}
|
||
|
||
impl EthereumBridgePlugin {
|
||
/// 创建新的以太坊桥插件
|
||
///
|
||
/// # 参数
|
||
/// - `rpc_url`: 以太坊RPC节点URL
|
||
/// - `chain_id`: 链ID(1=主网)
|
||
/// - `bridge_contract_address`: 桥合约地址
|
||
pub async fn new(
|
||
rpc_url: &str,
|
||
chain_id: u64,
|
||
bridge_contract_address: String,
|
||
) -> Result<Self, BridgeError> {
|
||
let provider = Provider::<Http>::try_from(rpc_url)
|
||
.map_err(|e| BridgeError::Network(format!("Failed to connect to RPC: {}", e)))?;
|
||
|
||
let provider = Arc::new(provider);
|
||
|
||
let chain_name = match chain_id {
|
||
1 => "Ethereum Mainnet".to_string(),
|
||
5 => "Goerli Testnet".to_string(),
|
||
11155111 => "Sepolia Testnet".to_string(),
|
||
_ => format!("Ethereum Chain {}", chain_id),
|
||
};
|
||
|
||
let _logo_url = "https://ethereum.org/static/eth-diamond-black.png".to_string();
|
||
|
||
Ok(Self {
|
||
chain_id,
|
||
provider,
|
||
bridge_contract_address,
|
||
chain_name,
|
||
_logo_url,
|
||
})
|
||
}
|
||
|
||
/// 获取链ID
|
||
pub fn chain_id(&self) -> u64 {
|
||
self.chain_id
|
||
}
|
||
|
||
/// 获取链名称
|
||
pub fn chain_name(&self) -> &str {
|
||
&self.chain_name
|
||
}
|
||
|
||
/// 获取ETH余额
|
||
pub async fn get_eth_balance(&self, address: &str) -> Result<u128, BridgeError> {
|
||
let address: Address = address
|
||
.parse()
|
||
.map_err(|e| BridgeError::PluginError(format!("Invalid address: {}", e)))?;
|
||
|
||
let balance = self.provider
|
||
.get_balance(address, None)
|
||
.await
|
||
.map_err(|e| BridgeError::Network(format!("Failed to get balance: {}", e)))?;
|
||
|
||
Ok(balance.as_u128())
|
||
}
|
||
|
||
/// 获取ERC-20余额
|
||
pub async fn get_erc20_balance(
|
||
&self,
|
||
user_address: &str,
|
||
token_address: &str,
|
||
) -> Result<u128, BridgeError> {
|
||
let user_addr: Address = user_address
|
||
.parse()
|
||
.map_err(|e| BridgeError::PluginError(format!("Invalid user address: {}", e)))?;
|
||
|
||
let token_addr: Address = token_address
|
||
.parse()
|
||
.map_err(|e| BridgeError::PluginError(format!("Invalid token address: {}", e)))?;
|
||
|
||
// ERC-20 balanceOf函数选择器: 0x70a08231
|
||
let mut data = vec![0x70, 0xa0, 0x82, 0x31]; // balanceOf(address)
|
||
data.extend_from_slice(&[0u8; 12]); // 填充12字节
|
||
data.extend_from_slice(user_addr.as_bytes());
|
||
|
||
let tx = Eip1559TransactionRequest::new()
|
||
.to(token_addr)
|
||
.data(data);
|
||
|
||
let result = self.provider
|
||
.call(&tx.into(), None)
|
||
.await
|
||
.map_err(|e| BridgeError::Network(format!("Failed to call balanceOf: {}", e)))?;
|
||
|
||
// 解析返回值(32字节uint256)
|
||
if result.len() != 32 {
|
||
return Err(BridgeError::PluginError(
|
||
"Invalid balanceOf response".to_string()
|
||
));
|
||
}
|
||
|
||
let balance = U256::from_big_endian(&result);
|
||
Ok(balance.as_u128())
|
||
}
|
||
|
||
/// 获取余额(自动判断ETH或ERC-20)
|
||
pub async fn get_balance(
|
||
&self,
|
||
address: &str,
|
||
token: &TokenInfo,
|
||
) -> Result<u128, BridgeError> {
|
||
if let Some(contract_address) = &token.contract_address {
|
||
self.get_erc20_balance(address, contract_address).await
|
||
} else {
|
||
self.get_eth_balance(address).await
|
||
}
|
||
}
|
||
|
||
/// 构造锁定ETH交易数据
|
||
///
|
||
/// # 参数
|
||
/// - `amount`: ETH数量(wei)
|
||
/// - `nac_target_address`: NAC目标地址(32字节)
|
||
///
|
||
/// # 返回
|
||
/// 交易数据(可用于签名和广播)
|
||
pub fn build_lock_eth_tx_data(
|
||
&self,
|
||
_amount: u128,
|
||
nac_target_address: &[u8; 32],
|
||
) -> Vec<u8> {
|
||
// lockETH(bytes32 nacTargetAddress)
|
||
let mut data = vec![0x12, 0x34, 0x56, 0x78]; // 函数选择器(占位符)
|
||
data.extend_from_slice(nac_target_address);
|
||
data
|
||
}
|
||
|
||
/// 构造锁定ERC-20交易数据
|
||
///
|
||
/// # 参数
|
||
/// - `token_address`: ERC-20合约地址
|
||
/// - `amount`: Token数量
|
||
/// - `nac_target_address`: NAC目标地址(32字节)
|
||
///
|
||
/// # 返回
|
||
/// 交易数据
|
||
pub fn build_lock_erc20_tx_data(
|
||
&self,
|
||
token_address: &str,
|
||
amount: u128,
|
||
nac_target_address: &[u8; 32],
|
||
) -> Result<Vec<u8>, BridgeError> {
|
||
let token_addr: Address = token_address
|
||
.parse()
|
||
.map_err(|e| BridgeError::PluginError(format!("Invalid token address: {}", e)))?;
|
||
|
||
// lockERC20(address token, uint256 amount, bytes32 nacTargetAddress)
|
||
let mut data = vec![0xab, 0xcd, 0xef, 0x12]; // 函数选择器(占位符)
|
||
|
||
// token地址
|
||
data.extend_from_slice(&[0u8; 12]);
|
||
data.extend_from_slice(token_addr.as_bytes());
|
||
|
||
// amount
|
||
let mut amount_bytes = [0u8; 32];
|
||
U256::from(amount).to_big_endian(&mut amount_bytes);
|
||
data.extend_from_slice(&amount_bytes);
|
||
|
||
// NAC目标地址
|
||
data.extend_from_slice(nac_target_address);
|
||
|
||
Ok(data)
|
||
}
|
||
|
||
/// 获取交易收据
|
||
pub async fn get_transaction_receipt(
|
||
&self,
|
||
tx_hash: &str,
|
||
) -> Result<TransactionReceipt, BridgeError> {
|
||
let tx_hash_bytes = hex::decode(tx_hash.trim_start_matches("0x"))
|
||
.map_err(|e| BridgeError::PluginError(format!("Invalid tx hash: {}", e)))?;
|
||
|
||
let tx_hash_h256 = H256::from_slice(&tx_hash_bytes);
|
||
|
||
let receipt = self.provider
|
||
.get_transaction_receipt(tx_hash_h256)
|
||
.await
|
||
.map_err(|e| BridgeError::Network(format!("Failed to get receipt: {}", e)))?
|
||
.ok_or_else(|| BridgeError::InvalidProof("Transaction not found".to_string()))?;
|
||
|
||
Ok(receipt)
|
||
}
|
||
|
||
/// 获取区块信息
|
||
pub async fn get_block(&self, block_number: u64) -> Result<Block<H256>, BridgeError> {
|
||
let block = self.provider
|
||
.get_block(block_number)
|
||
.await
|
||
.map_err(|e| BridgeError::Network(format!("Failed to get block: {}", e)))?
|
||
.ok_or_else(|| BridgeError::Network("Block not found".to_string()))?;
|
||
|
||
Ok(block)
|
||
}
|
||
|
||
/// 获取桥合约地址
|
||
pub fn bridge_contract_address(&self) -> &str {
|
||
&self.bridge_contract_address
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[tokio::test]
|
||
#[ignore] // 需要实际的RPC节点
|
||
async fn test_ethereum_bridge_creation() {
|
||
let bridge = EthereumBridgePlugin::new(
|
||
"https://eth-mainnet.g.alchemy.com/v2/demo",
|
||
1,
|
||
"0x1234567890123456789012345678901234567890".to_string(),
|
||
).await;
|
||
|
||
assert!(bridge.is_ok());
|
||
let bridge = bridge.expect("mainnet: handle error");
|
||
assert_eq!(bridge.chain_id(), 1);
|
||
assert_eq!(bridge.chain_name(), "Ethereum Mainnet");
|
||
}
|
||
|
||
#[test]
|
||
fn test_build_lock_eth_tx_data() {
|
||
let bridge = EthereumBridgePlugin {
|
||
chain_id: 1,
|
||
provider: Arc::new(Provider::<Http>::try_from("http://localhost:8545").expect("mainnet: handle error")),
|
||
bridge_contract_address: "0x1234...".to_string(),
|
||
chain_name: "Test".to_string(),
|
||
_logo_url: "".to_string(),
|
||
};
|
||
|
||
let nac_address = [1u8; 32];
|
||
let data = bridge.build_lock_eth_tx_data(1000000, &nac_address);
|
||
|
||
// 函数选择器(4字节) + NAC地址(32字节) = 36字节
|
||
assert_eq!(data.len(), 36);
|
||
}
|
||
}
|