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

Overflow when decoding multicall transaction #50

Open
lukerhoads opened this issue Mar 7, 2022 · 6 comments
Open

Overflow when decoding multicall transaction #50

lukerhoads opened this issue Mar 7, 2022 · 6 comments

Comments

@lukerhoads
Copy link

struct TestCase {
            data: String,
            types: Vec<Type>,
            results: Vec<Value>
        }

        let test_case = TestCase {
            data: "0000000000000000000000000000000000000000000000000000000062262ba1000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e404e45aaf000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000084b6a5c40000000000000000000000000000000000000000000000000bd373e0061c7e7f94000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000bd373e0061c7e7f9400000000000000000000000016ee789b50d3d49b8f71b5314c367e3fef24d74600000000000000000000000000000000000000000000000000000000".to_string(),
            types: vec![Type::Uint(256), Type::Array(Box::new(Type::Bytes))],
            results: vec![
                Value::Uint(EthersU256::from_dec_str("1646668705").unwrap(), 256),
                Value::Array(vec![
                    Value::Bytes(hex::decode("04e45aaf000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000084b6a5c40000000000000000000000000000000000000000000000000bd373e0061c7e7f940000000000000000000000000000000000000000000000000000000000000000").unwrap()),
                    Value::Bytes(hex::decode("49404b7c00000000000000000000000000000000000000000000000bd373e0061c7e7f9400000000000000000000000016ee789b50d3d49b8f71b5314c367e3fef24d746").unwrap()),
                ], Type::Bytes),
            ],
        };

        let values = Value::decode_from_slice(Vec::from_hex(test_case.data).unwrap().as_slice(), test_case.types.as_slice()).unwrap();
        for (idx, value) in values.iter().enumerate() {
            assert_eq!(value.clone(), test_case.results[idx]);
        }

Test case above

For a multicall transaction, there is an overflow when casting to usize. I believe this is because of the check if the type is dynamic in the fixedarray match arm, but taking it away will add problems to other cases. When in the branch, it already knows the size of the array, and has already accounted for offset. I am going to try to fix this myself but might need some help :)

Thanks

@FelipeRosa
Copy link
Owner

Hey @lukerhoads,

I'll have to catch up a bit since I didn't work in this project for some time. I'll take a look at your PR as soon as I can.

thanks for providing the test case :) btw.

@FelipeRosa
Copy link
Owner

@lukerhoads could you point me to some references about multicall? I'm not quite familiar with it.

@lukerhoads
Copy link
Author

For sure. Sorry for not clarifying earlier.
Most transactions sent to the Uniswap V3 Router call the multicall function in the input data example.
I am fairly confident that the reason there is an error is because of the bytes[] parameter, which is dynamic in both the array length and the content of the bytes.

@FelipeRosa
Copy link
Owner

Hey @lukerhoads,

Thanks a lot for pointing out this bug. I found out it was a problem with the decode logic for dynamic arrays (you can see it here #53, I made a regression test out of your provided test case).

I made a release and included you as a contributor in it (https://github.com/FelipeRosa/rust-ethereum-abi/releases/tag/v0.3.1). I also published the new version to crates.io.

Feel free to point out any related problems here or close the issue if everything is fine :)

@lukasdll
Copy link

I encountered a similar blocking error on CoW Protocol: GPv2Settlement transaction.

'Integer overflow when casting to usize'.

tx: https://etherscan.io/tx/0xaa0276520887cde6a4f1551f0ae6f7dd14db721abe209e2faf7cedbf8495284d
abi: https://etherscan.io/address/0x9008d19f58aabd9ed0d60971565aa8510560ab41#code

@FelipeRosa FelipeRosa reopened this Jan 21, 2023
@palinko91
Copy link

Yes I think it would be useful if the decode_input_from_.. function would decode the more complex inputs fully. Like until no Bytes anymore. Because for example if I want the previously mentioned multicall to decode fully after a simple decode I have to do something like this:

pub fn multicall(pendingtx:H256, abi: ethereum_abi::Abi, func: &Function, decoded_input: &DecodedParams) -> DecodedTransaction {
    let mut decoded_functions: Vec<Function> = Vec::new();
    let mut decoded_params_vec: Vec<DecodedParam> = Vec::new();
    for param in decoded_input.iter(){
        if param.param.name == "data".to_string() {
            if let Value::Array(value_array, _) = &param.value {
                for byte_value in value_array.iter() {
                    if let Value::Bytes(bytes) = byte_value {
                        // Decode
                        let (func_inner, decoded_input_inner) = abi
                        .decode_input_from_slice(&bytes)
                        .expect("Failed decoding input");

                        if !func_inner.name.is_empty() {
                            decoded_functions.push(func_inner.clone());
                        }
                        if !decoded_input_inner.is_empty() {
                            decoded_params_vec.push(decoded_input_inner[0].clone());
                        }                                    
                    }
                }
            }
        } else {
            decoded_functions.push(func.clone());
            decoded_params_vec.push(param.clone());
        }
    }

    let decoded_params_tuple: Vec<(Param, Value)> = decoded_params_vec
                                                    .into_iter()
                                                    .map(|decoded_param| (decoded_param.param, decoded_param.value))
                                                    .collect();

    let decoded_params: DecodedParams = DecodedParams::from(decoded_params_tuple);
}

Ofcourse it's looks messy and maybe not even working on every case, on some I tested worked, but it's getting complex to implement in every case, some contracts are using and different nested data.

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

4 participants