使用Rust模拟ethers.js中的parseUnits

Posted MateZero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用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并不完善,特别是其中的错误处理很简单,这是下一步优化的方向。

以上是关于使用Rust模拟ethers.js中的parseUnits的主要内容,如果未能解决你的问题,请参考以下文章

Web3 签名验证失败 - ethers.js

使用ethers.js部署Solidity智能合约

使用ethers.js执行读函数与写函数

如何使用 Ethers.js 制作自定义链连接按钮

使用ethers.js直接读取智能合约中插槽内容

如何使用来自独立 node.js 应用程序的 ethers.js 连接到 Metamask 钱包?