Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signed integer parameters are decoded as unsigned integers #249

Open
marmistrz opened this issue Oct 21, 2021 · 6 comments
Open

Signed integer parameters are decoded as unsigned integers #249

marmistrz opened this issue Oct 21, 2021 · 6 comments

Comments

@marmistrz
Copy link

marmistrz commented Oct 21, 2021

Consider the following function. In this context data is the data from the smart contract event logs and ethabi::decode is being used to decode this data.

fn decode_log(data: Vec<u8>) -> i128 {
        use ethabi::{ParamType, Token};

        let params = ethabi::decode(&[ParamType::Int(128)], data).unwrap();
        if let [Token::Int(change)] = params.as_slice() {
            change.as_i128()
        } else {
            panic!("Invalid decoded parameters: {:?}", params);
        }
}

This is supposed to parse the data of an event of the following signature:

event EventName(int128 change)

This code fails to compile because, counterintuitively, change is of the type U256, which is an unsigned type. This stems from the following line:

pub type Int = ethereum_types::U256;

which seems completely wrong.

Is this done on purpose and, if so, how should one decode signed parameters? There seems to be no mention of it in the docs.

@marmistrz marmistrz changed the title It's impossible to decode signed parameters Signed integer parameters are decoded as unsigned integers Oct 21, 2021
@gakonst
Copy link
Contributor

gakonst commented Jan 12, 2022

@nlordell this seems important for negative value logs right? should we add an I256 type to ethabi (either the one from ethers-core, and we can remove it from ethers, or the one you built in the past?) and use that as pub type Int?

@nlordell
Copy link
Contributor

nlordell commented Jan 12, 2022

should we add an I256 type to ethabi

My favourite place to add it would be ethereum-types or primitive-types crate alongside the U256 type. The argument being that signed 256-bit math is part of the EVM so it makes the most sense to me there.

That being said, I think including it here also makes a lot of sense and I just marginally prefer having it in the aforementioned crates over ethabi. Maybe we can start by moving it here and simultaneously asking around in those projects if they would accept including signed integers there?

@gakonst
Copy link
Contributor

gakonst commented Jan 12, 2022

Yep, supportive of importing to this package, and then seeing if they'd be willing to upstream.

@CRossel87a
Copy link

Need this fixed !

@0xFar5eer
Copy link

0xFar5eer commented Jul 31, 2023

Just solved this kind of problem. In case someone needs it:

fn convert_int256_to_f64(value: U256) -> Option<f64> {
    let half_of_max_u256 = U256::from(u128::MAX);

    let is_negative = value > half_of_max_u256;
    let value = if is_negative {
        let amount = U256::MAX - value;
        str::parse::<f64>(&format!("-{amount}")).unwrap()
    } else {
        str::parse::<f64>(&value.to_string()).unwrap()
    };

    Some(value)
}

@DirectX
Copy link

DirectX commented May 16, 2024

Just solved this kind of problem. In case someone needs it:

fn convert_int256_to_f64(value: U256) -> Option<f64> {
    let half_of_max_u256 = U256::from(u128::MAX);

    let is_negative = value > half_of_max_u256;
    let value = if is_negative {
        let amount = U256::MAX - value;
        str::parse::<f64>(&format!("-{amount}")).unwrap()
    } else {
        str::parse::<f64>(&value.to_string()).unwrap()
    };

    Some(value)
}

Thanks for idea. I need more precise numbers to store in json as a string so I've slightly changed this function. Please note that there is +1 term in formula because it's usually not visible in f64 but is notable in integer string representation.

fn convert_uint256_to_int256_string(value: &U256) -> String {
    let half_of_max_u256 = U256::from(u128::MAX);
    let is_negative = *value > half_of_max_u256;
    if is_negative {
        let amount = U256::MAX - *value + 1;
        format!("-{amount}")
    } else {
        value.to_string()
    }
}

I use this function in conjunction with matching Uint type versus Int (which implies for Uint) like this to make JSON of desired type depending of field type:

let json_value = match token {
    ethabi::Token::Address(address) => json!(format!("0x{}", address.encode_hex::<String>())),
    ethabi::Token::FixedBytes(bytes) => json!(format!("0x{}", bytes.encode_hex::<String>())),
    ethabi::Token::Bytes(bytes) => json!(format!("0x{}", bytes.encode_hex::<String>())),
    ethabi::Token::Int(uint_value) => {
        // Working around bug in ethabi library where int == uint in ethereum_types, so no negative values avaliable
        let int_value_string = convert_uint256_to_int256_string(uint_value);
        json!(int_value_string)
    },
    ethabi::Token::Uint(uint_value) => json!(uint_value.to_string()),
    ethabi::Token::Bool(bool_value) => json!(if *bool_value { "true" } else { "false" }),
    ethabi::Token::String(string_value) => json!(string_value),
    ...
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants