stellar / js-stellar-sdk

Main Stellar client library for the JavaScript language.
https://stellar.github.io/js-stellar-sdk/
Apache License 2.0
615 stars 296 forks source link

Struct encoding fails based on entries order #996

Closed re1ro closed 1 week ago

re1ro commented 1 week ago

Describe the bug When encoding nested structs we ran into an issue where encoding would fail if bytes type is not passed first.

What version are you on? 12.0.1

To Reproduce Here is example init function that takes struct as a param:

#[contracttype]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WeightedSigner {
    pub weight: U256,
    pub signer: BytesN<32>,
}

...
fn initialize(
  env: Env,
  signer: WeightedSigner,
) {
...

Now I am trying to encode data for this in JS. This works:

nativeToScVal(
    {
        signer: Buffer.alloc(32), // <---------------
        weight 1,
    },
    {
        type: {
            weight: ['symbol', 'u256'],
            signer: ['symbol', 'bytes'],
        },
    },
);

This doesn't work:

nativeToScVal(
    {
        weight: 1,
        signer: Buffer.alloc(32), // <---------------
    },
    {
        type: {
            weight: ['symbol', 'u256'],
            signer: ['symbol', 'bytes'],
        },
    },
);

Seems like encoding breaks if I don't pass bytes as the first entry in the JS object... Regardless in which order it is defined in Rust or in encoding type description.

Expected behavior Encoding works regardless of entries order in the passed object

willemneal commented 1 week ago

The issue here is the struct is encoded as a ScVal::Map. Unfortunately the issue is that XDR expects the keys in the map to be sorted, hence why weight must be second. This should be fixable in the JS code so that you don't need to worry about it.

I need to check since it's been a while since I was deep into the code, but the ContractClient might handle this when passing in init.

re1ro commented 1 week ago

@willemneal thank you for getting back to it so quickly!