From 0d69ced9cdedef1e0d23104d1ec3bb76df974bf1 Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Wed, 13 May 2026 16:52:35 +0530 Subject: [PATCH 1/6] fix(soroban): handle DynamicBytes in ABI spec entry, LLVM type guard, and codec Signed-off-by: Aryan Baranwal --- src/codegen/encoding/soroban_encoding.rs | 7 ++++++- src/emit/binary.rs | 7 ++----- src/emit/soroban/mod.rs | 6 ++++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/encoding/soroban_encoding.rs index e3a210692..7309e0dc2 100644 --- a/src/codegen/encoding/soroban_encoding.rs +++ b/src/codegen/encoding/soroban_encoding.rs @@ -142,7 +142,7 @@ pub fn soroban_decode_arg( }, Type::Uint(64) => decode_u64(wrapper_cfg, vartab, arg), - Type::Address(_) | Type::String => arg.clone(), + Type::Address(_) | Type::String | Type::DynamicBytes => arg.clone(), Type::Enum(enum_no) => { let decoded = soroban_decode_arg(arg, wrapper_cfg, vartab, ns, Some(Type::Uint(32))); @@ -721,6 +721,11 @@ pub fn soroban_encode_arg( res: obj, expr: encode_vector(item.clone(), cfg, vartab), }, + Type::DynamicBytes => Instr::Set { + loc: Loc::Codegen, + res: obj, + expr: item.clone(), + }, _ => todo!("Type not yet supported in soroban encoder: {:?}", item.ty()), }; diff --git a/src/emit/binary.rs b/src/emit/binary.rs index fa28f823d..84b59cd95 100644 --- a/src/emit/binary.rs +++ b/src/emit/binary.rs @@ -888,11 +888,8 @@ impl<'a> Binary<'a> { fn var_ty_uses_pointer_storage(&self, ty: &Type) -> bool { match ty.deref_memory() { - Type::Struct(_) - | Type::Array(..) - | Type::DynamicBytes - | Type::ExternalFunction { .. } => true, - Type::String => self.ns.target != Target::Soroban, + Type::Struct(_) | Type::Array(..) | Type::ExternalFunction { .. } => true, + Type::String | Type::DynamicBytes => self.ns.target != Target::Soroban, _ => false, } } diff --git a/src/emit/soroban/mod.rs b/src/emit/soroban/mod.rs index 542a2626c..c8d0b3881 100644 --- a/src/emit/soroban/mod.rs +++ b/src/emit/soroban/mod.rs @@ -338,7 +338,9 @@ impl SorobanTarget { ast::Type::Uint(256) => ScSpecTypeDef::U256, ast::Type::Bool => ScSpecTypeDef::Bool, ast::Type::Address(_) => ScSpecTypeDef::Address, - ast::Type::Bytes(_) => ScSpecTypeDef::Bytes, + ast::Type::Bytes(_) | ast::Type::DynamicBytes => { + ScSpecTypeDef::Bytes + } ast::Type::String => ScSpecTypeDef::String, ast::Type::Array(ty, _) => { let element = Self::vec_spec_type(ty.as_ref()); @@ -377,7 +379,7 @@ impl SorobanTarget { ast::Type::Int(_) => ScSpecTypeDef::I32, ast::Type::Bool => ScSpecTypeDef::Bool, ast::Type::Address(_) => ScSpecTypeDef::Address, - ast::Type::Bytes(_) => ScSpecTypeDef::Bytes, + ast::Type::Bytes(_) | ast::Type::DynamicBytes => ScSpecTypeDef::Bytes, ast::Type::String => ScSpecTypeDef::String, ast::Type::Void => ScSpecTypeDef::Void, ast::Type::Struct(_) => ScSpecTypeDef::Void, // TODO: Map struct types. From 7dcb5cadf966df81d7926834aad2c9801d5e37e5 Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Wed, 13 May 2026 19:49:40 +0530 Subject: [PATCH 2/6] test(soroban): add set_and_get_bytes test for DynamicBytes fix Signed-off-by: Aryan Baranwal --- tests/soroban_testcases/dynamic_bytes.rs | 32 ++++++++++++++++++++++++ tests/soroban_testcases/mod.rs | 1 + 2 files changed, 33 insertions(+) create mode 100644 tests/soroban_testcases/dynamic_bytes.rs diff --git a/tests/soroban_testcases/dynamic_bytes.rs b/tests/soroban_testcases/dynamic_bytes.rs new file mode 100644 index 000000000..070eeae75 --- /dev/null +++ b/tests/soroban_testcases/dynamic_bytes.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +use crate::build_solidity; +use soroban_sdk::{Bytes, FromVal, IntoVal}; + +#[test] +fn set_and_get_bytes() { + let src = build_solidity( + r#"contract BytesWriteStub { + bytes public data; + + function set_data(bytes memory d) public { + data = d; + } + }"#, + |_| {}, + ); + + let addr = src.contracts.last().unwrap(); + + // empty bytes round-trip + let empty = Bytes::from_slice(&src.env, b""); + src.invoke_contract(addr, "set_data", vec![empty.clone().into_val(&src.env)]); + let res = src.invoke_contract(addr, "data", vec![]); + assert_eq!(Bytes::from_val(&src.env, &res), empty); + + // non-empty bytes round-trip + let payload = Bytes::from_slice(&src.env, b"hello"); + src.invoke_contract(addr, "set_data", vec![payload.clone().into_val(&src.env)]); + let res = src.invoke_contract(addr, "data", vec![]); + assert_eq!(Bytes::from_val(&src.env, &res), payload); +} diff --git a/tests/soroban_testcases/mod.rs b/tests/soroban_testcases/mod.rs index cdd404e6f..755b67441 100644 --- a/tests/soroban_testcases/mod.rs +++ b/tests/soroban_testcases/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 mod alloc; mod array_args; +mod dynamic_bytes; mod atomic_swap; mod auth; mod constructor; From fa75cd2e6dd9de66c58242ba38045443e10f951b Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Sun, 17 May 2026 11:03:47 +0530 Subject: [PATCH 3/6] =?UTF-8?q?feat(soroban):=20support=20bytes1=E2=80=93b?= =?UTF-8?q?ytes32=20as=20function=20params=20and=20return=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Aryan Baranwal --- src/codegen/encoding/soroban_encoding.rs | 180 +++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/encoding/soroban_encoding.rs index 7309e0dc2..21a9f3c64 100644 --- a/src/codegen/encoding/soroban_encoding.rs +++ b/src/codegen/encoding/soroban_encoding.rs @@ -209,6 +209,91 @@ pub fn soroban_decode_arg( } } + Type::Bytes(n) if n <= 4 => Expression::Trunc { + loc: Loc::Codegen, + ty: Type::Bytes(n), + expr: Box::new(Expression::ShiftRight { + loc: Loc::Codegen, + ty: Type::Uint(64), + left: Box::new(arg), + right: Box::new(Expression::NumberLiteral { + loc: Loc::Codegen, + ty: Type::Uint(64), + value: 32u64.into(), + }), + signed: false, + }), + }, + + Type::Bytes(n) if n > 4 => { + let n_expr = Expression::NumberLiteral { + loc: Loc::Codegen, + ty: Type::Uint(32), + value: BigInt::from(n as u64), + }; + + let buf_var = vartab.temp_name("bytes_buf", &Type::DynamicBytes); + wrapper_cfg.add( + vartab, + Instr::Set { + loc: Loc::Codegen, + res: buf_var, + expr: Expression::AllocDynamicBytes { + loc: Loc::Codegen, + ty: Type::DynamicBytes, + size: Box::new(n_expr.clone()), + initializer: None, + }, + }, + ); + let buf = Expression::Variable { + loc: Loc::Codegen, + ty: Type::DynamicBytes, + var_no: buf_var, + }; + + let dest_ptr = Expression::VectorData { + pointer: Box::new(buf.clone()), + }; + let dest_encoded = zext_shift_add(Loc::Codegen, dest_ptr, 32, 4); + let src_off_encoded = zext_shift_add( + Loc::Codegen, + Expression::NumberLiteral { + loc: Loc::Codegen, + ty: Type::Uint(32), + value: BigInt::from(0u64), + }, + 32, + 4, + ); + let len_encoded = zext_shift_add(Loc::Codegen, n_expr, 32, 4); + + let unused = vartab.temp_name("bytes_copy_ret", &Type::Uint(64)); + wrapper_cfg.add( + vartab, + Instr::Call { + res: vec![unused], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::BytesCopyToLinearMemory.name().to_string(), + }, + args: vec![arg, dest_encoded, src_off_encoded, len_encoded], + }, + ); + + Expression::Load { + loc: Loc::Codegen, + ty: Type::Bytes(n), + expr: Box::new(Expression::Cast { + loc: Loc::Codegen, + ty: Type::Ref(Box::new(Type::DynamicBytes)), + expr: Box::new(Expression::VectorData { + pointer: Box::new(buf), + }), + }), + } + } + _ => unimplemented!("unimplemented ty {:#?} in soroban decoder", ty), } } @@ -726,6 +811,101 @@ pub fn soroban_encode_arg( res: obj, expr: item.clone(), }, + Type::Bytes(n) if n <= 4 => { + let widened = Expression::ZeroExt { + loc: item.loc(), + ty: Type::Uint(64), + expr: Box::new(item.clone()), + }; + let shifted = Expression::ShiftLeft { + loc: item.loc(), + ty: Type::Uint(64), + left: Box::new(widened), + right: Box::new(Expression::NumberLiteral { + loc: item.loc(), + ty: Type::Uint(64), + value: 32u64.into(), + }), + }; + Instr::Set { + loc: item.loc(), + res: obj, + expr: Expression::Add { + loc: item.loc(), + ty: Type::Uint(64), + left: Box::new(shifted), + right: Box::new(Expression::NumberLiteral { + loc: item.loc(), + ty: Type::Uint(64), + value: 4u64.into(), + }), + overflowing: false, + }, + } + } + + Type::Bytes(n) if n > 4 => { + let n_val = BigInt::from(n as u64); + + let n_expr = Expression::NumberLiteral { + loc: item.loc(), + ty: Type::Uint(32), + value: n_val.clone(), + }; + + let buf_var = vartab.temp_name("bytes_spill", &Type::DynamicBytes); + cfg.add( + vartab, + Instr::Set { + loc: item.loc(), + res: buf_var, + expr: Expression::AllocDynamicBytes { + loc: item.loc(), + ty: Type::DynamicBytes, + size: Box::new(n_expr.clone()), + initializer: None, + }, + }, + ); + let buf = Expression::Variable { + loc: item.loc(), + ty: Type::DynamicBytes, + var_no: buf_var, + }; + + cfg.add( + vartab, + Instr::Store { + dest: Expression::Cast { + loc: item.loc(), + ty: Type::Ref(Box::new(Type::DynamicBytes)), + expr: Box::new(Expression::VectorData { + pointer: Box::new(buf.clone()), + }), + }, + data: item.clone(), + }, + ); + + let encoded_ptr = zext_shift_add( + item.loc(), + Expression::VectorData { + pointer: Box::new(buf), + }, + 32, + 4, + ); + let encoded_len = zext_shift_add(item.loc(), n_expr, 32, 4); + + Instr::Call { + res: vec![obj], + return_tys: vec![Type::Uint(64)], + call: InternalCallTy::HostFunction { + name: HostFunctions::BytesNewFromLinearMemory.name().to_string(), + }, + args: vec![encoded_ptr, encoded_len], + } + } _ => todo!("Type not yet supported in soroban encoder: {:?}", item.ty()), }; From bc20d69ac54aa2e0f7dad71525bb22e2c9aae196 Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Sun, 17 May 2026 14:53:56 +0530 Subject: [PATCH 4/6] fix(soroban): fix params order for bytes_copy_to_linear_memory Signed-off-by: Aryan Baranwal --- src/codegen/encoding/soroban_encoding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/encoding/soroban_encoding.rs index 21a9f3c64..5ec96dc6e 100644 --- a/src/codegen/encoding/soroban_encoding.rs +++ b/src/codegen/encoding/soroban_encoding.rs @@ -277,7 +277,7 @@ pub fn soroban_decode_arg( call: InternalCallTy::HostFunction { name: HostFunctions::BytesCopyToLinearMemory.name().to_string(), }, - args: vec![arg, dest_encoded, src_off_encoded, len_encoded], + args: vec![arg, src_off_encoded, dest_encoded, len_encoded], }, ); From e49a5f78af6c5fe9040cc487681960220db19d97 Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Sun, 17 May 2026 15:06:20 +0530 Subject: [PATCH 5/6] fix(soroban): drop sdk incompatible U32Val arm for Bytes(n) where n<=4 Signed-off-by: Aryan Baranwal --- src/codegen/encoding/soroban_encoding.rs | 52 +----------------------- 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/encoding/soroban_encoding.rs index 5ec96dc6e..9469e2198 100644 --- a/src/codegen/encoding/soroban_encoding.rs +++ b/src/codegen/encoding/soroban_encoding.rs @@ -209,23 +209,7 @@ pub fn soroban_decode_arg( } } - Type::Bytes(n) if n <= 4 => Expression::Trunc { - loc: Loc::Codegen, - ty: Type::Bytes(n), - expr: Box::new(Expression::ShiftRight { - loc: Loc::Codegen, - ty: Type::Uint(64), - left: Box::new(arg), - right: Box::new(Expression::NumberLiteral { - loc: Loc::Codegen, - ty: Type::Uint(64), - value: 32u64.into(), - }), - signed: false, - }), - }, - - Type::Bytes(n) if n > 4 => { + Type::Bytes(n) => { let n_expr = Expression::NumberLiteral { loc: Loc::Codegen, ty: Type::Uint(32), @@ -811,40 +795,8 @@ pub fn soroban_encode_arg( res: obj, expr: item.clone(), }, - Type::Bytes(n) if n <= 4 => { - let widened = Expression::ZeroExt { - loc: item.loc(), - ty: Type::Uint(64), - expr: Box::new(item.clone()), - }; - let shifted = Expression::ShiftLeft { - loc: item.loc(), - ty: Type::Uint(64), - left: Box::new(widened), - right: Box::new(Expression::NumberLiteral { - loc: item.loc(), - ty: Type::Uint(64), - value: 32u64.into(), - }), - }; - Instr::Set { - loc: item.loc(), - res: obj, - expr: Expression::Add { - loc: item.loc(), - ty: Type::Uint(64), - left: Box::new(shifted), - right: Box::new(Expression::NumberLiteral { - loc: item.loc(), - ty: Type::Uint(64), - value: 4u64.into(), - }), - overflowing: false, - }, - } - } - Type::Bytes(n) if n > 4 => { + Type::Bytes(n) => { let n_val = BigInt::from(n as u64); let n_expr = Expression::NumberLiteral { From 833cfd93c516626e30ca35dc4a05bf6378ca5095 Mon Sep 17 00:00:00 2001 From: Aryan Baranwal Date: Sun, 17 May 2026 16:17:40 +0530 Subject: [PATCH 6/6] test(soroban): add Bytes(n) codec round trip tests for bytes1, bytes5, bytes32 Signed-off-by: Aryan Baranwal --- tests/soroban_testcases/bytes_n_codec.rs | 74 ++++++++++++++++++++++++ tests/soroban_testcases/mod.rs | 1 + 2 files changed, 75 insertions(+) create mode 100644 tests/soroban_testcases/bytes_n_codec.rs diff --git a/tests/soroban_testcases/bytes_n_codec.rs b/tests/soroban_testcases/bytes_n_codec.rs new file mode 100644 index 000000000..e1a40eba7 --- /dev/null +++ b/tests/soroban_testcases/bytes_n_codec.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 + +use crate::build_solidity; +use soroban_sdk::{BytesN, FromVal, IntoVal}; + +#[test] +fn echo_bytes1() { + let src = build_solidity( + r#"contract BytesNCodec { + function echo_bytes1(bytes1 x) public pure returns (bytes1) { + return x; + } + }"#, + |_| {}, + ); + let addr = src.contracts.last().unwrap(); + + let payload = BytesN::<1>::from_array(&src.env, &[0xAB]); + let res = src.invoke_contract( + addr, + "echo_bytes1", + vec![payload.clone().into_val(&src.env)], + ); + assert_eq!(BytesN::<1>::from_val(&src.env, &res), payload); +} + +#[test] +fn echo_bytes5() { + let src = build_solidity( + r#"contract BytesNCodec { + function echo_bytes5(bytes5 x) public pure returns (bytes5) { + return x; + } + }"#, + |_| {}, + ); + let addr = src.contracts.last().unwrap(); + + let payload = BytesN::<5>::from_array(&src.env, &[0x01, 0x02, 0x03, 0x04, 0x05]); + let res = src.invoke_contract( + addr, + "echo_bytes5", + vec![payload.clone().into_val(&src.env)], + ); + assert_eq!(BytesN::<5>::from_val(&src.env, &res), payload); +} + +#[test] +fn echo_bytes32() { + let src = build_solidity( + r#"contract BytesNCodec { + function echo_bytes32(bytes32 x) public pure returns (bytes32) { + return x; + } + }"#, + |_| {}, + ); + let addr = src.contracts.last().unwrap(); + + let payload = BytesN::<32>::from_array( + &src.env, + &[ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, + 0x33, 0x22, 0x11, 0x00, + ], + ); + let res = src.invoke_contract( + addr, + "echo_bytes32", + vec![payload.clone().into_val(&src.env)], + ); + assert_eq!(BytesN::<32>::from_val(&src.env, &res), payload); +} diff --git a/tests/soroban_testcases/mod.rs b/tests/soroban_testcases/mod.rs index 755b67441..beab1e0eb 100644 --- a/tests/soroban_testcases/mod.rs +++ b/tests/soroban_testcases/mod.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 mod alloc; mod array_args; +mod bytes_n_codec; mod dynamic_bytes; mod atomic_swap; mod auth;