Candid - vec of enums only decodes the first element

Hello ICP community and DFINITY developers,

I’m working with a Rust canister and using Candid to encode/decode a vector of enums in the Init Args for the canister. My type definitions are:

#[derive(Deserialize, Serialize, CandidType, Debug)]
pub struct InitArgs {
    pub allowed_reward_tokens: Vec<TokenSymbol>,
}
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    CandidType,
    minicbor::Encode,
    minicbor::Decode,
)]
pub enum TokenSymbol {
    #[n(0)]
    ICP,
    #[n(1)]
    TOKEN2,
    #[n(2)]
    TOKEN3,
    #[n(3)]
    TOKEN4,
    #[n(4)]
    TOKEN5,
}

I’m trying to encode and decode values like:

vec { variant { ICP }; variant { TOKEN1 }; variant { TOKEN2 }; variant { TOKEN3 } }

Issue:
When I encode and decode this vector, only the first variant seems to be recognized/used. The rest are ignored or not decoded as expected.
So I decided to print this field:

            ic_cdk::println!(
                "tokens: {:#?}",
                init_args.tokens
            );

The output of print command:
[ICP, ICP, ICP, ICP,]

So it always prints the repeated first arg

Has anyone encountered a similar issue? Any insights or suggestions would be greatly appreciated!

This code successfully reproduces the original, with candid 0.10.17:

use candid::{CandidType, Decode, Encode};
use serde::Deserialize;

fn main() {
    let tokens = vec![
        TokenSymbol::ICP,
        TokenSymbol::TOKEN2,
        TokenSymbol::TOKEN3,
        TokenSymbol::TOKEN4,
        TokenSymbol::TOKEN5,
    ];
    let serialized = Encode!(&tokens).unwrap();
    let deserialized = Decode!(&serialized, Vec<TokenSymbol>).unwrap();
    println!("{deserialized:?}");
}

#[derive(CandidType, Deserialize, Debug)]
pub enum TokenSymbol {
    ICP,
    TOKEN2,
    TOKEN3,
    TOKEN4,
    TOKEN5,
}

Are you on the latest release of Candid?

1 Like

Thank you for reply!
I was using
candid = { version = “0.10.2”, features = [“value”] }

I ran your code for mine enum and it worked, same for 0.10.17. I’m not sure what’s happening under the hood, but maybe it’s because of parsing the exact candid string:

vec { variant { ICP }; variant { TOKEN1 }; variant { TOKEN2 }; variant { TOKEN3 } }

So I suppose what is happening when you serialize using CandidType - it also makes the conversion to such type of strig and only then to bytes, but is there any way to view the intermediate representation?

Can you write a self-contained program that reproduces the issue?

1 Like

It looks like the problem has something to do with the text representation “vec { variant { ICP }; variant { TOKEN1 }; variant { TOKEN2 }; variant { TOKEN3 } }”, rather than just encoding/decoding binary.

If I do:

$ didc encode "(record { allowed_reward_tokens = vec { variant { ICP }; variant {TOKEN1}; variant { TOKEN2 }; variant { TOKEN2 }; variant { TOKEN3 } }})"
4449444c036c01d381828501016d026b01b6bede017f0100050000000000
$ didc decode 4449444c036c01d381828501016d026b01b6bede017f0100050000000000
(
  record {
    278_954_195 = vec {
      variant { 3_645_238 };
      variant { 3_645_238 };
      variant { 3_645_238 };
      variant { 3_645_238 };
      variant { 3_645_238 };
    };
  },
)

It does look like 3_645_238 is the candid hash of “ICP”. This behavior does look a bit odd to me @AdamS

However, if I have

some.did

type InitArgs = record {
  allowed_reward_tokens : vec TokenSymbol;
};

type TokenSymbol = variant {
  ICP;
  TOKEN2;
  TOKEN3;
  TOKEN4;
  TOKEN5;
};

and then

$ didc encode "(record { allowed_reward_tokens = vec { variant { ICP }; variant { TOKEN2 }; variant { TOKEN3 }; variant { TOKEN4 } }})
" --defs some.did --types "(InitArgs)"
4449444c036c01d381828501016d026b05b6bede017fb986a7a3047fba86a7a3047fbb86a7a3047fbc86a7a3047f01000400010203
$ didc decode 4449444c036c01d381828501016d026b05b6bede017fb986a7a3047fba86a7a3047fbb86a7a3047fbc86a7a3047f01000400010203 --defs rs/sns/root/canister/root.did --types "(InitArgs)"
(
  record {
    allowed_reward_tokens = vec {
      variant { ICP };
      variant { TOKEN2 };
      variant { TOKEN3 };
      variant { TOKEN4 };
    };
  },
)

I think it still depend on what exactly you did, but I think one issue with the example by @VictoriaGrasshopper is that the type TokenSymbol does not have TOKEN1, but the string has (note in my above example it’s 2-4 instead of 1-3, otherwise I get “Error: variant field TOKEN1 not found”)

1 Like

Yes, it seems like the issue I faced with. The vec is filled with the clones of first vec element. I had to use a String representation of the TokenSymbol and then parse it to overcome this issue, so there is no rush, but I think it anyway looks like a bug

Regarding TOKEN1, I actually have real token names, just were trying to provide some abstract example, so I think I just made a typo here :sweat_smile:

Thanks for your answers!