使用Rust模拟ethers.js中的parseUnits

我们知道,在同以太坊区块链进行交互的编程语言中,javascript是最便捷的语言,没有之一。但是世界上的语言不只一种,我们有时也需要使用其它语言和以太坊区块链交互,例如Rust。为此,Rust中有一个crate叫web3,它其中有个类型U256来对应Solidity中的uint256。但是它却缺少了两个很常用的功能,就是去除精度后转化为人类易读的浮点数和它的反向操作。例如我们需要将值为500000000000000000精度为18的数显示为0.5等。
在ethers.js中,提供了parseUnits和formatUnits这两个函数进行类似的操作,因此,作为学习Rust语言的一个练习 ,我们也模拟了这两个函数,代码如下:
convert.rs

///模仿ethers.js中的parseUnits和formatUnits
use std::error::Error;
use std::fmt;
use web3::types::U256;

#[derive(Debug)] // Allow the use of "{:?}" format specifier
pub enum ParseU256Error {
    
    
    FormatError,
    DecimalsError,
}

// Allow the use of "{}" format specifier
impl fmt::Display for ParseU256Error {
    
    
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    
    
        match *self {
    
    
            ParseU256Error::FormatError => write!(f, "Format Error!"),
            ParseU256Error::DecimalsError => write!(f, "Decimals Error!"),
        }
    }
}

// 允许将此类型视为错误
impl Error for ParseU256Error {
    
    }

pub trait ParseFormat {
    
    
    type Error;
    fn parse_units(source: &'static str, d: u32) -> Result<U256, Self::Error>;
    fn format_units(&self, d: u32) -> Result<String, Self::Error>;
}

const ZEROS: &str = "000000000000000000";

impl ParseFormat for U256 {
    
    
    type Error = ParseU256Error;
    ///直接将浮点字符串乘上精度转成U256
    fn parse_units(source: &'static str, d: u32) -> Result<U256, ParseU256Error> {
    
    
        if d > 18 {
    
    
            return Err(ParseU256Error::DecimalsError);
        }
        let decimals = U256::exp10(d as usize);
        let strs: Vec<&str> = source.split(".").collect();
        if strs.len() > 2 {
    
    
            return Err(ParseU256Error::FormatError);
        }
        let inter = U256::from_dec_str(strs[0])
            .unwrap()
            .checked_mul(decimals)
            .unwrap();
        if strs.len() == 1 {
    
    
            //只有整数部分
            return Ok(inter);
        }
        let mut z: String = strs[1].to_string();
        //小数部分需要补零
        let frac = if strs[1].len() >= d as usize {
    
    
            &strs[1][..d as usize]
        } else {
    
    
            z.push_str(&ZEROS[..d as usize - strs[1].len()]);
            &z[..]
        };
        let frac = U256::from_dec_str(frac).unwrap();
        Ok(inter.checked_add(frac).unwrap())
    }

    ///将U256转化为人类可读的浮点数字符串
    fn format_units(&self, d: u32) -> Result<String, ParseU256Error> {
    
    
        if d > 18 {
    
    
            return Err(ParseU256Error::DecimalsError);
        }
        let decimals = U256::exp10(d as usize);
        let m = self.checked_rem(decimals).unwrap().to_string(); //模
        let mut b = String::from(&ZEROS[..d as usize - m.len()]); //计算0的个数
        b.push_str(&m[..]); //前面补零
        let b = b.trim_end_matches('0');
        let mut inter = self.checked_div(decimals).unwrap().to_string(); //商
        //如果有余数
        if m != "0" {
    
    
            inter.push('.');
            inter.push_str(b);
        }
        Ok(inter.clone())
    }
}

#[test]
fn test_wrap() {
    
    
    let source = "1.035000000000000000001";
    let balance = U256::from(1_035_000_000_000_000_000u64);
    let b = U256::parse_units(source, 18).unwrap();
    assert_eq!(b, balance);
    let s = balance.format_units(18).unwrap();
    assert_eq!(s, "1.035");

    let source2 = "13897";
    let bal2 = U256::from(13_897_000_000_000 as u64);
    let b2 = U256::parse_units(source2, 9).unwrap();
    assert_eq!(b2, bal2);
    let s2 = bal2.format_units(9).unwrap();
    assert_eq!(s2, source2);
}

注意,我们这个转换精度不能超过18。

下面我们来看一下具体的操作。
main.rs

use std::str::FromStr;
use web3::types::{
    
    Address,U256};
use web3::contract::{
    
    Contract, Options};

mod convert;
use convert::ParseFormat;

#[tokio::main]
async fn main() -> web3::contract::Result<()> {
    
    
    const HTTP_URL: &str = "https://bsc-dataseed4.ninicoin.io";
    const SAFE_MOON: &str = "0x8076C74C5e3F5852037F31Ff0093Eeb8c8ADd8D3";   
    let transport = web3::transports::Http::new(HTTP_URL)?;
    let provider = web3::Web3::new(transport);
    let safe_addr = Address::from_str(SAFE_MOON).unwrap();
    let contract = Contract::from_json(provider.eth(), safe_addr, include_bytes!("../abi2.json")).unwrap();
    let result = contract.query("_taxFee",(),None,Options::default(),None);
    let tax_fee: U256 = result.await?;
    println!("_taxFee:{}",tax_fee);
    assert_eq!(tax_fee, 5.into());
    let balance = provider.eth().balance(safe_addr, None).await?;
    let balance = balance.format_units(18).unwrap();
    println!("Balance:{} BNB",balance);
    Ok(())
}

在上面的示例代码中,我们演示的了两个功能,第一个查询SafeMoon合约中的_taxFee,另一个是查询了SafeMoon合约的BNB余额,运行结果如下(结果可能不同):

_taxFee:5
Balance:4957.85048317300265945 BNB

可以看到,大致实现了我们的需求。
上面的convert.rs并不完善,特别是其中的错误处理很简单,这是下一步优化的方向。

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/121714974
今日推荐