Repository: tangle-network/gadget (language: rust)

Issue:
[BUG] My blueprint Job returns `Bytes` but it gets somehow transalated into an Array.
### Overview

Consider the following simple job:
```rust
// .. imports are hidden for simplicity ...

struct MyContext;

struct MyError;

#[gadget_sdk::job(
    id = 0,
    params(foo),
    result(_),
    event_listener(
        listener = TangleEventListener::<MyContext, JobCalled>,
        pre_processor = services_pre_processor,
        post_processor = services_post_processor,
    )
)]
pub async fn bar(context: ServiceContext, foo: u8) -> Result<Vec<u8>, Error> {
  Ok(vec![0u8; 42])
}
```

If you triggered this job on chain, it will not work and will actually return `Runtime error: Pallet error: Services::TypeCheck`

And if you looked on-chain, you will see that it submitted the result as `Field::Array` instead of `Field::Bytes`.

### Expected

The job should work as expected, the job proc-macro figured this out, the correct type of the result is `Bytes` and not an Array; thus I expected to submit Bytes not an Array.

### Update

After digging a bit, looks like this bug was introduced first in #363 and that implementation got later on changed in #399

The root of the bug is this:

https://github.com/tangle-network/gadget/blame/bdc4ec601c2b4aec3500613d36e9e053b02d58be/sdk/src/event_listener/tangle/mod.rs#L223-L231


Discussion / hints:

Fix patch:
diff --git a/sdk/src/event_listener/tangle/mod.rs b/sdk/src/event_listener/tangle/mod.rs
index 02d22aecd..c082fcb58 100644
--- a/sdk/src/event_listener/tangle/mod.rs
+++ b/sdk/src/event_listener/tangle/mod.rs
@@ -220,13 +220,28 @@ impl_value_to_field_type!(
     AccountId32 => Field::AccountId
 );
 
-impl<T: ValueIntoFieldType> ValueIntoFieldType for Vec<T> {
+impl<T: ValueIntoFieldType + 'static> ValueIntoFieldType for Vec<T> {
     fn into_field_type(self) -> Field<AccountId32> {
-        Field::Array(BoundedVec(
-            self.into_iter()
-                .map(ValueIntoFieldType::into_field_type)
-                .collect(),
-        ))
+        if core::any::TypeId::of::<T>() == core::any::TypeId::of::<u8>() {
+            let (ptr, length, capacity) = {
+                let mut me = core::mem::ManuallyDrop::new(self);
+                (me.as_mut_ptr() as *mut u8, me.len(), me.capacity())
+            };
+            // SAFETY: We are converting a Vec<T> to Vec<u8> only when T is u8.
+            // This is safe because the memory layout of Vec<u8> is the same as Vec<T> when T is u8.
+            // We use ManuallyDrop to prevent double-freeing the memory.
+            // Vec::from_raw_parts takes ownership of the raw parts, ensuring proper deallocation.
+            #[allow(unsafe_code)]
+            Field::Bytes(BoundedVec(unsafe {
+                Vec::from_raw_parts(ptr, length, capacity)
+            }))
+        } else {
+            Field::List(BoundedVec(
+                self.into_iter()
+                    .map(ValueIntoFieldType::into_field_type)
+                    .collect(),
+            ))
+        }
     }
 }
 


Test patch: