diff --git a/src/codegen/encoding/soroban_encoding.rs b/src/codegen/encoding/soroban_encoding.rs index e3a210692..9469e2198 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))); @@ -209,6 +209,75 @@ pub fn soroban_decode_arg( } } + Type::Bytes(n) => { + 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, src_off_encoded, dest_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), } } @@ -721,6 +790,74 @@ 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(), + }, + + Type::Bytes(n) => { + 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()), }; 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. 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/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..beab1e0eb 100644 --- a/tests/soroban_testcases/mod.rs +++ b/tests/soroban_testcases/mod.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 mod alloc; mod array_args; +mod bytes_n_codec; +mod dynamic_bytes; mod atomic_swap; mod auth; mod constructor;