stellar / rs-soroban-sdk

Rust SDK for Soroban contracts.
Apache License 2.0
124 stars 67 forks source link

UDTs used inside Options are not convertible to ScVal/Sc* #1385

Open leighmcculloch opened 5 hours ago

leighmcculloch commented 5 hours ago

What version are you using?

21.7.6 22.0.0-rc.3

What did you do?

#![no_std]
use soroban_sdk::contracttype;

#[contracttype]
#[derive(Clone)]
pub struct Struct1(pub ());

#[contracttype]
#[derive(Clone, Debug, PartialEq)]
pub enum Enum1 {
    V0(Option<Struct1>),
}

#[contracttype]
pub struct Struct2(pub Option<Struct1>);

What did you expect to see?

Build success when building for tests.

What did you see instead?

❯ cargo test
error[E0277]: the trait bound `soroban_sdk::xdr::ScVec: TryFrom<(soroban_sdk::xdr::ScSymbol, &core::option::Option<Struct1>)>` is not satisfied
 --> contracts/hello_world/src/lib.rs:8:1
  |
8 | #[contracttype]
  | ^^^^^^^^^^^^^^^ the trait `TryFrom<(soroban_sdk::xdr::ScSymbol, &core::option::Option<Struct1>)>` is not implemented for `soroban_sdk::xdr::ScVec`, which is required by `(soroban_sdk::xdr::ScSymbol, &core::option::Option<Struct1>): TryInto<_>`
  |
  = help: the following other types implement trait `TryFrom<T>`:
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3, T4)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3, T4, T5)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3, T4, T5, T6)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3, T4, T5, T6, T7)>`
            `soroban_sdk::xdr::ScVec` implements `TryFrom<&(T0, T1, T2, T3, T4, T5, T6, T7, T8)>`
          and 32 others
  = note: required for `(soroban_sdk::xdr::ScSymbol, &core::option::Option<Struct1>)` to implement `TryInto<soroban_sdk::xdr::ScVec>`
  = note: this error originates in the attribute macro `contracttype` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `soroban_sdk::xdr::ScVal: TryFrom<&core::option::Option<Struct1>>` is not satisfied
  --> contracts/hello_world/src/lib.rs:14:1
   |
14 | #[contracttype]
   | ^^^^^^^^^^^^^^^ the trait `From<Struct1>` is not implemented for `soroban_sdk::xdr::ScVal`, which is required by `&core::option::Option<Struct1>: TryInto<_>`
   |
   = help: the following other types implement trait `From<T>`:
             `&'a soroban_sdk::xdr::ScVal` implements `From<soroban_env_common::object::ScValObjRef<'a>>`
             `soroban_sdk::xdr::ScVal` implements `From<&()>`
             `soroban_sdk::xdr::ScVal` implements `From<&bool>`
             `soroban_sdk::xdr::ScVal` implements `From<&core::option::Option<T>>`
             `soroban_sdk::xdr::ScVal` implements `From<&i128>`
             `soroban_sdk::xdr::ScVal` implements `From<&i32>`
             `soroban_sdk::xdr::ScVal` implements `From<&i64>`
             `soroban_sdk::xdr::ScVal` implements `From<&soroban_sdk::Address>`
           and 36 others
   = note: required for `Struct1` to implement `Into<soroban_sdk::xdr::ScVal>`
   = note: required for `soroban_sdk::xdr::ScVal` to implement `From<&core::option::Option<Struct1>>`
   = note: 1 redundant requirement hidden
   = note: required for `&core::option::Option<Struct1>` to implement `Into<soroban_sdk::xdr::ScVal>`
   = note: required for `soroban_sdk::xdr::ScVal` to implement `TryFrom<&core::option::Option<Struct1>>`
   = note: required for `&core::option::Option<Struct1>` to implement `TryInto<soroban_sdk::xdr::ScVal>`
   = note: this error originates in the attribute macro `contracttype` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `hello-world` (lib test) due to 2 previous errors
✘ exit status 101                                                                                                  

cc @kalepail

leighmcculloch commented 5 hours ago

I believe the issue is that when we build for tests we also code generate implementations for converting types from a variety of Sc* XDR types like ScVal and ScVec. Those conversion are only implemented for conversion from borrows, e.g. &ScVal, not from owned types, e.g. ScVal.

I don't remember why the conversions are limited to borrows, we could potentially go and add in conversions without the borrows too.

Another way to fix this may be to alter the generated code to appropriately "as_ref" each value, which would result in a the Option<ScVal> being used as a Option<&ScVal>. This solution would be preferred because very likely the reason we are borrowing is to not deconstruct the type, so even if we filled in the other above, it probably wouldn't be sufficient.

leighmcculloch commented 3 hours ago

I took a look to see if there was a quick fix here. The as_ref idea won’t work because not all types support it. I tried adding support for impl From<Option<T>> for ScVal where&Tis convertible, instead of the existing whereT` is convertible, but ended up with recursion issues. This requires a more significant time investment to solve.