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

when using #[serde(untagged)] it compiles with Floats #43

Open
ratik opened this issue Aug 24, 2022 · 19 comments
Open

when using #[serde(untagged)] it compiles with Floats #43

ratik opened this issue Aug 24, 2022 · 19 comments

Comments

@ratik
Copy link

ratik commented Aug 24, 2022

When I use serde(untagged) for parsing mixed types into enum, something like this

#[derive(PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum TransactionFilterValue {
    String(String),
    Int(u128),
}

it compiles but on WASM execution it panics with

failed to execute message; message index: 0: Error calling the VM: Error compiling Wasm: Could not compile: WebAssembly translation error: Error in middleware Gatekeeper: Float operator detected: F64Load
@iboss-ptk
Copy link

iboss-ptk commented Sep 30, 2022

Found same issue, so will explain why and offer alternative approach as a workaround.

What happened?

Here is the generated code for Deserialize from your struct (I used cargo-expand to inspect)

#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
    #[allow(unused_extern_crates, clippy::useless_attribute)]
    extern crate serde as _serde;
    #[automatically_derived]
    impl<'de> _serde::Deserialize<'de> for TransactionFilterValue {
        fn deserialize<__D>(
            __deserializer: __D,
        ) -> _serde::__private::Result<Self, __D::Error>
        where
            __D: _serde::Deserializer<'de>,
        {
            // [1] `_serde::__private::de::Content` is where the problem lies
            let __content = match <_serde::__private::de::Content as _serde::Deserialize>::deserialize(
                __deserializer,
            ) {
                _serde::__private::Ok(__val) => __val,
                _serde::__private::Err(__err) => {
                    return _serde::__private::Err(__err);
                }
            };
            if let _serde::__private::Ok(__ok)
                = _serde::__private::Result::map(
                    <String as _serde::Deserialize>::deserialize(
                        _serde::__private::de::ContentRefDeserializer::<
                            __D::Error,
                        >::new(&__content),
                    ),
                    TransactionFilterValue::String,
                ) {
                return _serde::__private::Ok(__ok);
            }
            if let _serde::__private::Ok(__ok)
                = _serde::__private::Result::map(
                    
                    <u128 as _serde::Deserialize>::deserialize(
                        _serde::__private::de::ContentRefDeserializer::<
                            // [2] Error is also where the problem lies
                            __D::Error,
                        >::new(&__content),
                    ),
                    TransactionFilterValue::Int,
                ) {
                return _serde::__private::Ok(__ok);
            }
            _serde::__private::Err(
                _serde::de::Error::custom(
                    "data did not match any variant of untagged enum TransactionFilterValue",
                ),
            )
        }
    }
};

So for [1], Content contains variant that holds f64 hence f64.load in compiled wasm.

for [2] I described it here.

Workaround

Following the same logic as generated code but use https://github.com/CosmWasm/serde-cw-value instead of Content by changing

  • _serde::__private::de::Content as _serde::Deserialize -> serde_cw_value::Value
  • _serde::__private::de::ContentRefDeserializer -> serde_cw_value::ValueDeserializer
  • __D::Error -> serde_cw_value::DeserializerError

@ethanfrey
Copy link
Member

@hashedone would it be possible to create a macro like #[serde(untagged)] or #[serde(flatten)] that generates code using serde_cw_value?

@iboss-ptk explains how to make such code, but if his approach works, auto-generating with a macro would be awesome.

@hashedone
Copy link

@ethanfrey so #[serde(untagged)] and #[serde(flatten)] are not macros - they are attributes which are parsed by serde derive macro. And this is kind of interesting in this context because we could technically create a new macro which detects those attributes and generate code. Not this much of a problem. Maybe add it to #[cw_serde] in cosmwasm package? Maybe just macro in serde utils - anyway it should be technically possible I think.

@ethanfrey
Copy link
Member

Not urgent, but I'd love to see that added to the backlog

@iboss-ptk
Copy link

It seems that in rust 1.65, #[serde(untagged)] works just fine. Haven't dig into yet but my guess would be compiler optimization removes load f64 from llvm IR when enum variant that contains f64 is not being used.

@ethanfrey
Copy link
Member

Awesome!

Time to update our minimum supported rust version and close this issue (with a test or two)

@luca992
Copy link

luca992 commented Nov 19, 2022

It seems that in rust 1.65, #[serde(untagged)] works just fine. Haven't dig into yet but my guess would be compiler optimization removes load f64 from llvm IR when enum variant that contains f64 is not being used.

I just tried updating to 1.65 and removing the workaround here. But no luck :/
I'm getting:
rpc error: code = Unknown desc = failed to execute message; message index: 0: Execution error: Enclave: found floating point operation in module code: instantiate contract failed [enigmampc/SecretNetwork/x/compute/internal/keeper/keeper.go:409]

@nicolaslara
Copy link

I also ran this on 1.65 and still got floats in the result. @iboss-ptk maybe the optimization worked specifically for your use case but can't be generalized?

I tried with with:

  • cargo build --release --target wasm32-unknown-unknown
  • cw-optimizoor
  • workspace-optimizer 0.12.8 for arm64
  • workspace-optimizer 0.12.10 for intel (on docker on an M1)

@iboss-ptk
Copy link

interesting, can you share the compiler optimization profile.

here's mine

[profile.release]
codegen-units = 1
debug = false
debug-assertions = false
incremental = false
lto = true
opt-level = 3
overflow-checks = true
panic = 'abort'
rpath = false

@luca992
Copy link

luca992 commented Nov 22, 2022

interesting, can you share the compiler optimization profile.

here's mine

[profile.release]
codegen-units = 1
debug = false
debug-assertions = false
incremental = false
lto = true
opt-level = 3
overflow-checks = true
panic = 'abort'
rpath = false

I have the same values

@iboss-ptk
Copy link

for @nicolaslara DM-ed him and found that

  • my case use tuple variant enum and it works
enum SomeEnum {
    Variant(SomeType),
}
enum SomeEnum {
    Variant { something: SomeType },
}

wonder what's your case @luca992

@luca992
Copy link

luca992 commented Jan 22, 2023

@iboss-ptk

finally made an example. I made a branch that causes the error here: https://github.com/eqoty-labs/snip721-migrate-example/tree/serde-untagged-floats-error

Take a look at ExecuteMsg and QueryMsg

@luca992
Copy link

luca992 commented Mar 10, 2023

Still happens in rust 1.68.0

@ethanfrey
Copy link
Member

@luca992 could you try your branch using this PR #53

I have not yet had time to review it, but it if it fixes your case, then it is definitely much higher prio

@chris-ricketts
Copy link

@ethanfrey @luca992 I'm afraid #53 it doesn't fix #[serde(flatten)] not sure about untagged but I doubt it.

Easy way to try it in your own projects is adding this to the root Cargo.toml:

[patch.crates-io]
serde-json-wasm = { git = "https://github.com/chris-ricketts/serde-json-wasm", branch = "rm-err-fmt" }

@luca992
Copy link

luca992 commented Mar 25, 2023

@ethanfrey @luca992 I'm afraid #53 it doesn't fix #[serde(flatten)] not sure about untagged but I doubt it.

Easy way to try it in your own projects is adding this to the root Cargo.toml:

[patch.crates-io]
serde-json-wasm = { git = "https://github.com/chris-ricketts/serde-json-wasm", branch = "rm-err-fmt" }

Just tried. Unfortunately it didn't work for untagged as well.

@chris-ricketts
Copy link

I ended up using @iboss-ptk's workaround for #[serde(flatten)].

Workaround

Following the same logic as generated code but use https://github.com/CosmWasm/serde-cw-value instead of Content by changing

* `_serde::__private::de::Content as _serde::Deserialize` -> `serde_cw_value::Value`

* ` _serde::__private::de::ContentRefDeserializer` -> `serde_cw_value::ValueDeserializer`

* `__D::Error` -> `serde_cw_value::DeserializerError`

Here's the Deserialize impl if anyone else needs to do something similar.

Tested here.

@luca992
Copy link

luca992 commented Oct 9, 2023

What do you guys think of this approach? I really want to get rid of the serde_cw_value dependency in my project, it adds way too much size to the wasm output. If instead deserialize_bytes was supported for use with your own Visitor implementing visit_bytes you can deserialize an untagged enum by trial and error.

#63 (comment)

I tested it out and it works well and my wasm output size is down by a couple hundred kb

@Buckram123
Copy link

Here's the Deserialize impl if anyone else needs to do something similar.

Tested here.

Hey, thanks for your example, but I still end up having f64.load in my contract that's generated by Serialize instead. Specifically it's generated by FlatMapSerializer
Does anyone know any workarounds?

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

8 participants