Bläddra i källkod

Create abi.borshDecode method for Solana (#961)

* Create borsh decoding

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Create codegen tests

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Add runtime tests

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Add invalid data tests

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Reorgnize code

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

* Write doc comments

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel 3 år sedan
förälder
incheckning
a553c0b008

+ 1 - 0
Cargo.toml

@@ -18,6 +18,7 @@ cc = "1.0"
 regex = "1"
 num-bigint = "0.4"
 num-traits = "0.2"
+num-integer = "0.1.44"
 parity-wasm = "0.45"
 clap = "3.2"
 clap_complete = "3.2"

+ 0 - 1
src/codegen/cfg.rs

@@ -779,7 +779,6 @@ impl ControlFlowGraph {
             Expression::AdvancePointer {
                 pointer,
                 bytes_offset,
-                ..
             } => {
                 format!(
                     "(advance ptr: {}, by: {})",

+ 27 - 11
src/codegen/constant_folding.rs

@@ -301,6 +301,20 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, ns: &mut Namespace) {
                         topic_tys: topic_tys.clone(),
                     }
                 }
+                Instr::MemCopy {
+                    source,
+                    destination,
+                    bytes,
+                } => {
+                    let bytes = expression(bytes, Some(&vars), cfg, ns);
+                    let source = expression(source, Some(&vars), cfg, ns);
+                    let destination = expression(destination, Some(&vars), cfg, ns);
+                    cfg.blocks[block_no].instr[instr_no] = Instr::MemCopy {
+                        source: source.0,
+                        destination: destination.0,
+                        bytes: bytes.0,
+                    };
+                }
                 _ => (),
             }
 
@@ -366,22 +380,24 @@ fn expression(
             }
         }
         Expression::AdvancePointer {
-            loc,
-            ty,
             pointer,
             bytes_offset: offset,
         } => {
             // Only the offset can be simplified
             let offset = expression(offset, vars, cfg, ns);
-            (
-                Expression::AdvancePointer {
-                    loc: *loc,
-                    ty: ty.clone(),
-                    pointer: pointer.clone(),
-                    bytes_offset: Box::new(offset.0),
-                },
-                offset.1,
-            )
+
+            match &offset.0 {
+                // There is no reason to advance the pointer by a zero offset
+                Expression::NumberLiteral(_, _, num) if num.is_zero() => (*pointer.clone(), false),
+
+                _ => (
+                    Expression::AdvancePointer {
+                        pointer: pointer.clone(),
+                        bytes_offset: Box::new(offset.0),
+                    },
+                    offset.1,
+                ),
+            }
         }
         Expression::Multiply(loc, ty, unchecked, left, right) => {
             let left = expression(left, vars, cfg, ns);

+ 641 - 54
src/codegen/encoding/borsh_encoding.rs

@@ -1,9 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
+use crate::codegen::encoding::buffer_validator::BufferValidator;
 use crate::codegen::encoding::{
-    calculate_size_args, finish_array_loop, increment_four, load_array_item, load_struct_member,
-    load_sub_array, set_array_loop, AbiEncoding,
+    allocate_array, allow_direct_copy, calculate_array_bytes_size,
+    calculate_direct_copy_bytes_size, calculate_size_args, finish_array_loop, increment_four,
+    load_array_item, load_struct_member, load_sub_array, retrieve_array_length, set_array_loop,
+    AbiEncoding,
 };
 use crate::codegen::vartable::Vartable;
 use crate::codegen::{Builtin, Expression};
@@ -68,6 +71,57 @@ impl AbiEncoding for BorshEncoding {
         buffer
     }
 
+    fn abi_decode(
+        &self,
+        loc: &Loc,
+        buffer: &Expression,
+        types: &[Type],
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> Vec<Expression> {
+        let buffer_size = vartab.temp_anonymous(&Type::Uint(32));
+        cfg.add(
+            vartab,
+            Instr::Set {
+                loc: Loc::Codegen,
+                res: buffer_size,
+                expr: Expression::Builtin(
+                    Loc::Codegen,
+                    vec![Type::Uint(32)],
+                    Builtin::ArrayLength,
+                    vec![buffer.clone()],
+                ),
+            },
+        );
+
+        let mut validator = BufferValidator::new(buffer_size, types);
+
+        let mut read_items: Vec<Expression> = vec![Expression::Poison; types.len()];
+        let mut offset = Expression::NumberLiteral(*loc, Type::Uint(32), BigInt::zero());
+
+        validator.initialize_validation(&offset, ns, vartab, cfg);
+
+        for (item_no, item) in types.iter().enumerate() {
+            validator.set_argument_number(item_no);
+            validator.validate_buffer(&offset, ns, vartab, cfg);
+            let (read_item, advance) =
+                self.read_from_buffer(buffer, &offset, item, &mut validator, ns, vartab, cfg);
+            read_items[item_no] = read_item;
+            offset = Expression::Add(
+                *loc,
+                Type::Uint(32),
+                false,
+                Box::new(offset),
+                Box::new(advance),
+            );
+        }
+
+        validator.validate_all_bytes_read(offset, vartab, cfg);
+
+        read_items
+    }
+
     fn cache_storage_loaded(&mut self, arg_no: usize, expr: Expression) {
         self.storage_cache.insert(arg_no, expr);
     }
@@ -85,7 +139,7 @@ impl AbiEncoding for BorshEncoding {
                 Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), size)
             }
 
-            Type::String | Type::DynamicBytes | Type::Slice(_) => {
+            Type::String | Type::DynamicBytes => {
                 // When encoding a variable length array, the total size is "length (u32)" + elements
                 let length = Expression::Builtin(
                     Loc::Codegen,
@@ -194,7 +248,7 @@ impl BorshEncoding {
                 Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), BigInt::from(*length))
             }
 
-            Type::String | Type::DynamicBytes | Type::Slice(_) => {
+            Type::String | Type::DynamicBytes => {
                 let get_size = Expression::Builtin(
                     Loc::Codegen,
                     vec![Type::Uint(32)],
@@ -223,8 +277,6 @@ impl BorshEncoding {
 
                 // ptr + offset + size_of_integer
                 let dest_address = Expression::AdvancePointer {
-                    loc: Loc::Codegen,
-                    ty: Type::BufferPointer,
                     pointer: Box::new(buffer.clone()),
                     bytes_offset: Box::new(increment_four(offset.clone())),
                 };
@@ -258,7 +310,6 @@ impl BorshEncoding {
                 expr,
                 buffer,
                 offset.clone(),
-                &expr_ty,
                 struct_ty,
                 arg_no,
                 ns,
@@ -266,6 +317,13 @@ impl BorshEncoding {
                 cfg,
             ),
 
+            Type::Slice(ty) => {
+                let dims = vec![ArrayLength::Dynamic];
+                self.encode_array(
+                    expr, &expr_ty, ty, &dims, arg_no, buffer, offset, ns, vartab, cfg,
+                )
+            }
+
             Type::Array(ty, dims) => self.encode_array(
                 expr, &expr_ty, ty, dims, arg_no, buffer, offset, ns, vartab, cfg,
             ),
@@ -315,7 +373,6 @@ impl BorshEncoding {
                         expr,
                         buffer,
                         offset.clone(),
-                        &expr_ty,
                         struct_ty,
                         arg_no,
                         ns,
@@ -348,21 +405,6 @@ impl BorshEncoding {
         vartab: &mut Vartable,
         cfg: &mut ControlFlowGraph,
     ) -> Expression {
-        // Checks if the elements are of Type::Bytes
-        let bytes_length = if let Type::Bytes(n) = elem_ty { *n } else { 0 };
-
-        // Check if we can MemCpy elements into the buffer
-        let direct_encoding = if array_ty.is_dynamic(ns) {
-            // If this is a dynamic array, we can only MemCpy if its elements are of
-            // any primitive type and we don't need to index it.
-            // Elements whose type is Bytes cannot be directly encoded, because we must
-            // reverse the bytes order.
-            dims.len() == 1 && elem_ty.is_primitive() && bytes_length < 2
-        } else {
-            // If the array is not dynamic, we can MemCpy elements if their are primitive.
-            elem_ty.is_primitive() && bytes_length < 2
-        };
-
         let size = if dims.is_empty() {
             // Array has no dimension
             cfg.add(
@@ -379,17 +421,10 @@ impl BorshEncoding {
             );
 
             Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), BigInt::from(4u8))
-        } else if direct_encoding {
+        } else if allow_direct_copy(array_ty, elem_ty, dims, ns) {
             // Calculate number of elements
             let (bytes_size, offset) = if matches!(dims.last(), Some(&ArrayLength::Fixed(_))) {
-                let mut elem_no = BigInt::from(1u8);
-                for item in dims {
-                    assert!(matches!(item, &ArrayLength::Fixed(_)));
-                    elem_no.mul_assign(item.array_length().unwrap());
-                }
-
-                let bytes = elem_ty.memory_size_of(ns);
-                elem_no.mul_assign(&bytes);
+                let elem_no = calculate_direct_copy_bytes_size(dims, elem_ty, ns);
                 (
                     Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), elem_no),
                     offset.clone(),
@@ -421,29 +456,13 @@ impl BorshEncoding {
                     },
                 );
 
-                let size = Expression::Multiply(
-                    Loc::Codegen,
-                    Type::Uint(32),
-                    false,
-                    Box::new(Expression::Variable(
-                        Loc::Codegen,
-                        Type::Uint(32),
-                        size_temp,
-                    )),
-                    Box::new(Expression::NumberLiteral(
-                        Loc::Codegen,
-                        Type::Uint(32),
-                        elem_ty.memory_size_of(ns),
-                    )),
-                );
+                let size = calculate_array_bytes_size(size_temp, elem_ty, ns);
 
                 (size, increment_four(offset.clone()))
             };
 
             let dest_address = Expression::AdvancePointer {
-                loc: Loc::Codegen,
                 pointer: Box::new(buffer.clone()),
-                ty: Type::BufferPointer,
                 bytes_offset: Box::new(offset),
             };
 
@@ -612,7 +631,7 @@ impl BorshEncoding {
                 cfg,
                 indexes,
             )
-        };
+        }
 
         finish_array_loop(&for_loop, vartab, cfg);
     }
@@ -623,7 +642,6 @@ impl BorshEncoding {
         expr: &Expression,
         buffer: &Expression,
         mut offset: Expression,
-        expr_ty: &Type,
         struct_ty: &StructType,
         arg_no: usize,
         ns: &Namespace,
@@ -631,14 +649,12 @@ impl BorshEncoding {
         cfg: &mut ControlFlowGraph,
     ) -> Expression {
         let size = if let Some(no_padding_size) = ns.calculate_struct_non_padded_size(struct_ty) {
-            let padded_size = expr_ty.solana_storage_size(ns);
+            let padded_size = struct_ty.struct_padded_size(ns);
             // If the size without padding equals the size with padding, we
             // can memcpy this struct directly.
             if padded_size.eq(&no_padding_size) {
                 let size = Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), no_padding_size);
                 let dest_address = Expression::AdvancePointer {
-                    loc: Loc::Codegen,
-                    ty: Type::BufferPointer,
                     pointer: Box::new(buffer.clone()),
                     bytes_offset: Box::new(offset),
                 };
@@ -693,4 +709,575 @@ impl BorshEncoding {
 
         size.unwrap_or(runtime_size)
     }
+
+    /// Read a value of type 'ty' from the buffer at a given offset. Returns an expression
+    /// containing the read value and the number of bytes read.
+    fn read_from_buffer(
+        &self,
+        buffer: &Expression,
+        offset: &Expression,
+        ty: &Type,
+        validator: &mut BufferValidator,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> (Expression, Expression) {
+        match ty {
+            Type::Uint(_)
+            | Type::Int(_)
+            | Type::Bool
+            | Type::Address(_)
+            | Type::Contract(_)
+            | Type::Enum(_)
+            | Type::Value
+            | Type::Bytes(_) => {
+                let read_bytes = ty.memory_size_of(ns);
+
+                let size = Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), read_bytes);
+                validator.validate_offset_plus_size(offset, &size, vartab, cfg);
+
+                let read_value = Expression::Builtin(
+                    Loc::Codegen,
+                    vec![ty.clone()],
+                    Builtin::ReadFromBuffer,
+                    vec![buffer.clone(), offset.clone()],
+                );
+
+                (read_value, size)
+            }
+
+            Type::DynamicBytes | Type::String => {
+                // String and Dynamic bytes are encoded as size (uint32) + elements
+                validator.validate_offset(increment_four(offset.clone()), vartab, cfg);
+
+                let array_length = retrieve_array_length(buffer, offset, vartab, cfg);
+
+                let size = increment_four(Expression::Variable(
+                    Loc::Codegen,
+                    Type::Uint(32),
+                    array_length,
+                ));
+                let offset_to_validate = Expression::Add(
+                    Loc::Codegen,
+                    Type::Uint(32),
+                    false,
+                    Box::new(size.clone()),
+                    Box::new(offset.clone()),
+                );
+
+                validator.validate_offset(offset_to_validate, vartab, cfg);
+                let allocated_array = allocate_array(ty, array_length, vartab, cfg);
+
+                let advanced_pointer = Expression::AdvancePointer {
+                    pointer: Box::new(buffer.clone()),
+                    bytes_offset: Box::new(increment_four(offset.clone())),
+                };
+
+                cfg.add(
+                    vartab,
+                    Instr::MemCopy {
+                        source: advanced_pointer,
+                        destination: Expression::Variable(
+                            Loc::Codegen,
+                            ty.clone(),
+                            allocated_array,
+                        ),
+                        bytes: Expression::Variable(Loc::Codegen, Type::Uint(32), array_length),
+                    },
+                );
+
+                (
+                    Expression::Variable(Loc::Codegen, ty.clone(), allocated_array),
+                    size,
+                )
+            }
+
+            Type::UserType(type_no) => {
+                let usr_type = ns.user_types[*type_no].ty.clone();
+                self.read_from_buffer(buffer, offset, &usr_type, validator, ns, vartab, cfg)
+            }
+
+            Type::ExternalFunction { .. } => {
+                // Extneral function has selector + address
+                let size = Expression::NumberLiteral(
+                    Loc::Codegen,
+                    Type::Uint(32),
+                    BigInt::from(ns.address_length + 4),
+                );
+
+                validator.validate_offset_plus_size(offset, &size, vartab, cfg);
+
+                let selector = Expression::Builtin(
+                    Loc::Codegen,
+                    vec![Type::Bytes(4)],
+                    Builtin::ReadFromBuffer,
+                    vec![buffer.clone(), offset.clone()],
+                );
+
+                let address = Expression::Builtin(
+                    Loc::Codegen,
+                    vec![Type::Address(false)],
+                    Builtin::ReadFromBuffer,
+                    vec![buffer.clone(), increment_four(offset.clone())],
+                );
+
+                let external_func = Expression::Cast(
+                    Loc::Codegen,
+                    ty.clone(),
+                    Box::new(Expression::StructLiteral(
+                        Loc::Codegen,
+                        Type::Struct(StructType::ExternalFunction),
+                        vec![selector, address],
+                    )),
+                );
+
+                (external_func, size)
+            }
+
+            Type::Array(elem_ty, dims) => self.decode_array(
+                buffer, offset, ty, elem_ty, dims, validator, ns, vartab, cfg,
+            ),
+
+            Type::Slice(elem_ty) => {
+                let dims = vec![ArrayLength::Dynamic];
+                self.decode_array(
+                    buffer, offset, ty, elem_ty, &dims, validator, ns, vartab, cfg,
+                )
+            }
+
+            Type::Struct(struct_ty) => self.decode_struct(
+                buffer,
+                offset.clone(),
+                ty,
+                struct_ty,
+                validator,
+                ns,
+                vartab,
+                cfg,
+            ),
+
+            Type::Rational
+            | Type::Ref(_)
+            | Type::StorageRef(..)
+            | Type::BufferPointer
+            | Type::Unresolved
+            | Type::InternalFunction { .. }
+            | Type::Unreachable
+            | Type::Void
+            | Type::Mapping(..) => unreachable!("Type should not appear on an encoded buffer"),
+        }
+    }
+
+    /// Given the buffer and the offers, decode an array.
+    /// The function returns an expression containing the array and the number of bytes read.
+    fn decode_array(
+        &self,
+        buffer: &Expression,
+        offset: &Expression,
+        array_ty: &Type,
+        elem_ty: &Type,
+        dims: &[ArrayLength],
+        validator: &mut BufferValidator,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> (Expression, Expression) {
+        // Checks if we can memcpy the elements from the buffer directly to the allocated array
+        if allow_direct_copy(array_ty, elem_ty, dims, ns) {
+            // Calculate number of elements
+            let (bytes_size, offset, var_no) =
+                if matches!(dims.last(), Some(&ArrayLength::Fixed(_))) {
+                    let elem_no = calculate_direct_copy_bytes_size(dims, elem_ty, ns);
+                    let allocated_vector = vartab.temp_anonymous(array_ty);
+                    cfg.add(
+                        vartab,
+                        Instr::Set {
+                            loc: Loc::Codegen,
+                            res: allocated_vector,
+                            expr: Expression::ArrayLiteral(
+                                Loc::Codegen,
+                                array_ty.clone(),
+                                vec![],
+                                vec![],
+                            ),
+                        },
+                    );
+
+                    (
+                        Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), elem_no),
+                        offset.clone(),
+                        allocated_vector,
+                    )
+                } else {
+                    validator.validate_offset(increment_four(offset.clone()), vartab, cfg);
+                    let array_length = retrieve_array_length(buffer, offset, vartab, cfg);
+
+                    let allocated_array = allocate_array(array_ty, array_length, vartab, cfg);
+
+                    let size = calculate_array_bytes_size(array_length, elem_ty, ns);
+                    (size, increment_four(offset.clone()), allocated_array)
+                };
+
+            validator.validate_offset_plus_size(&offset, &bytes_size, vartab, cfg);
+
+            let source_address = Expression::AdvancePointer {
+                pointer: Box::new(buffer.clone()),
+                bytes_offset: Box::new(offset),
+            };
+
+            let array_expr = Expression::Variable(Loc::Codegen, array_ty.clone(), var_no);
+            cfg.add(
+                vartab,
+                Instr::MemCopy {
+                    source: source_address,
+                    destination: array_expr.clone(),
+                    bytes: bytes_size.clone(),
+                },
+            );
+
+            let bytes_size = if matches!(dims.last(), Some(ArrayLength::Dynamic)) {
+                increment_four(bytes_size)
+            } else {
+                bytes_size
+            };
+
+            (array_expr, bytes_size)
+        } else {
+            let mut indexes: Vec<usize> = Vec::new();
+            let array_var = vartab.temp_anonymous(array_ty);
+
+            // The function decode_complex_array assumes that, if the dimension is fixed,
+            // there is no need to allocate an array
+            if matches!(dims.last(), Some(ArrayLength::Fixed(_))) {
+                cfg.add(
+                    vartab,
+                    Instr::Set {
+                        loc: Loc::Codegen,
+                        res: array_var,
+                        expr: Expression::ArrayLiteral(
+                            Loc::Codegen,
+                            array_ty.clone(),
+                            vec![],
+                            vec![],
+                        ),
+                    },
+                );
+            }
+
+            let offset_var = vartab.temp_anonymous(&Type::Uint(32));
+            cfg.add(
+                vartab,
+                Instr::Set {
+                    loc: Loc::Codegen,
+                    res: offset_var,
+                    expr: offset.clone(),
+                },
+            );
+            let array_var_expr = Expression::Variable(Loc::Codegen, array_ty.clone(), array_var);
+            let offset_expr = Expression::Variable(Loc::Codegen, Type::Uint(32), offset_var);
+            self.decode_complex_array(
+                &array_var_expr,
+                buffer,
+                offset_var,
+                &offset_expr,
+                dims.len() - 1,
+                elem_ty,
+                dims,
+                validator,
+                ns,
+                vartab,
+                cfg,
+                &mut indexes,
+            );
+            // Subtract the original offset from
+            // the offset variable to obtain the vector size in bytes
+            cfg.add(
+                vartab,
+                Instr::Set {
+                    loc: Loc::Codegen,
+                    res: offset_var,
+                    expr: Expression::Subtract(
+                        Loc::Codegen,
+                        Type::Uint(32),
+                        false,
+                        Box::new(offset_expr.clone()),
+                        Box::new(offset.clone()),
+                    ),
+                },
+            );
+            (array_var_expr, offset_expr)
+        }
+    }
+
+    /// Decodes a complex array from a borsh encoded buffer
+    /// Complex arrays are either dynamic arrays or arrays of dynamic types, like structs.
+    /// If this is an array of structs, whose representation in memory is padded, the array is
+    /// also complex, because it cannot be memcpy'ed
+    fn decode_complex_array(
+        &self,
+        array_var: &Expression,
+        buffer: &Expression,
+        offset_var: usize,
+        offset_expr: &Expression,
+        dimension: usize,
+        elem_ty: &Type,
+        dims: &[ArrayLength],
+        validator: &mut BufferValidator,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+        indexes: &mut Vec<usize>,
+    ) {
+        // If we have a 'int[3][4][] vec', we can only validate the buffer after we have
+        // allocated the outer dimension, i.e., we are about to read a 'int[3][4]' item.
+        if validator.validation_necessary()
+            && !dims[0..(dimension + 1)]
+                .iter()
+                .any(|d| *d == ArrayLength::Dynamic)
+        {
+            let mut elems = BigInt::one();
+            for item in &dims[0..(dimension + 1)] {
+                elems.mul_assign(item.array_length().unwrap());
+            }
+            elems.mul_assign(elem_ty.memory_size_of(ns));
+            let elems_size = Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), elems);
+            validator.validate_offset_plus_size(offset_expr, &elems_size, vartab, cfg);
+            validator.validate_array();
+        }
+
+        // Dynamic dimensions mean that the subarray we are processing must be allocated in memory.
+        if dims[dimension] == ArrayLength::Dynamic {
+            let offset_to_validate = increment_four(offset_expr.clone());
+            validator.validate_offset(offset_to_validate, vartab, cfg);
+            let array_length = retrieve_array_length(buffer, offset_expr, vartab, cfg);
+            cfg.add(
+                vartab,
+                Instr::Set {
+                    loc: Loc::Codegen,
+                    res: offset_var,
+                    expr: increment_four(offset_expr.clone()),
+                },
+            );
+            let new_ty = Type::Array(Box::new(elem_ty.clone()), dims[0..(dimension + 1)].to_vec());
+            let allocated_array = allocate_array(&new_ty, array_length, vartab, cfg);
+
+            if indexes.is_empty() {
+                if let Expression::Variable(_, _, var_no) = array_var {
+                    cfg.add(
+                        vartab,
+                        Instr::Set {
+                            loc: Loc::Codegen,
+                            res: *var_no,
+                            expr: Expression::Variable(
+                                Loc::Codegen,
+                                new_ty.clone(),
+                                allocated_array,
+                            ),
+                        },
+                    );
+                } else {
+                    unreachable!("array_var must be a variable");
+                }
+            } else {
+                // TODO: This is wired up for multidimensional dynamic arrays, but they do no work yet
+                // Check https://github.com/hyperledger-labs/solang/issues/932 for more information
+                let (sub_arr, _) = load_sub_array(
+                    array_var.clone(),
+                    &dims[(dimension + 1)..dims.len()],
+                    indexes,
+                    true,
+                );
+                cfg.add(
+                    vartab,
+                    Instr::Store {
+                        dest: sub_arr,
+                        data: Expression::Variable(Loc::Codegen, new_ty.clone(), allocated_array),
+                    },
+                );
+            }
+        }
+
+        let for_loop = set_array_loop(array_var, dims, dimension, indexes, vartab, cfg);
+        cfg.set_basic_block(for_loop.body_block);
+        if 0 == dimension {
+            let (read_expr, advance) =
+                self.read_from_buffer(buffer, offset_expr, elem_ty, validator, ns, vartab, cfg);
+            let ptr = load_array_item(array_var, dims, indexes);
+
+            cfg.add(
+                vartab,
+                Instr::Store {
+                    dest: ptr,
+                    data: if matches!(read_expr.ty(), Type::Struct(_)) {
+                        // Type::Struct is a pointer to a struct. If we are dealing with a vector
+                        // of structs, we need to dereference the pointer before storing it at a
+                        // given vector index.
+                        Expression::Load(Loc::Codegen, read_expr.ty(), Box::new(read_expr))
+                    } else {
+                        read_expr
+                    },
+                },
+            );
+            cfg.add(
+                vartab,
+                Instr::Set {
+                    loc: Loc::Codegen,
+                    res: offset_var,
+                    expr: Expression::Add(
+                        Loc::Codegen,
+                        Type::Uint(32),
+                        false,
+                        Box::new(advance),
+                        Box::new(offset_expr.clone()),
+                    ),
+                },
+            );
+        } else {
+            self.decode_complex_array(
+                array_var,
+                buffer,
+                offset_var,
+                offset_expr,
+                dimension - 1,
+                elem_ty,
+                dims,
+                validator,
+                ns,
+                vartab,
+                cfg,
+                indexes,
+            );
+        }
+
+        finish_array_loop(&for_loop, vartab, cfg);
+    }
+
+    /// Read a struct from the buffer
+    fn decode_struct(
+        &self,
+        buffer: &Expression,
+        mut offset: Expression,
+        expr_ty: &Type,
+        struct_ty: &StructType,
+        validator: &mut BufferValidator,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> (Expression, Expression) {
+        let size = if let Some(no_padding_size) = ns.calculate_struct_non_padded_size(struct_ty) {
+            let padded_size = struct_ty.struct_padded_size(ns);
+            // If the size without padding equals the size with padding,
+            // we can memcpy this struct directly.
+            if padded_size.eq(&no_padding_size) {
+                let size = Expression::NumberLiteral(Loc::Codegen, Type::Uint(32), no_padding_size);
+                validator.validate_offset_plus_size(&offset, &size, vartab, cfg);
+                let source_address = Expression::AdvancePointer {
+                    pointer: Box::new(buffer.clone()),
+                    bytes_offset: Box::new(offset),
+                };
+                let allocated_struct = vartab.temp_anonymous(expr_ty);
+                cfg.add(
+                    vartab,
+                    Instr::Set {
+                        loc: Loc::Codegen,
+                        res: allocated_struct,
+                        expr: Expression::StructLiteral(Loc::Codegen, expr_ty.clone(), vec![]),
+                    },
+                );
+                let struct_var =
+                    Expression::Variable(Loc::Codegen, expr_ty.clone(), allocated_struct);
+                cfg.add(
+                    vartab,
+                    Instr::MemCopy {
+                        source: source_address,
+                        destination: struct_var.clone(),
+                        bytes: size.clone(),
+                    },
+                );
+                return (struct_var, size);
+            } else {
+                // This struct has a fixed size, but we cannot memcpy it due to
+                // its padding in memory
+                Some(Expression::NumberLiteral(
+                    Loc::Codegen,
+                    Type::Uint(32),
+                    no_padding_size,
+                ))
+            }
+        } else {
+            None
+        };
+
+        let struct_tys = struct_ty
+            .definition(ns)
+            .fields
+            .iter()
+            .map(|item| item.ty.clone())
+            .collect::<Vec<Type>>();
+
+        // If it was not possible to validate the struct beforehand, we validate each field
+        // during recursive calls to 'read_from_buffer'
+        let mut struct_validator = validator.create_sub_validator(&struct_tys);
+
+        let qty = struct_ty.definition(ns).fields.len();
+
+        if validator.validation_necessary() {
+            struct_validator.initialize_validation(&offset, ns, vartab, cfg);
+        }
+
+        let (mut read_expr, mut advance) = self.read_from_buffer(
+            buffer,
+            &offset,
+            &struct_tys[0],
+            &mut struct_validator,
+            ns,
+            vartab,
+            cfg,
+        );
+        let mut runtime_size = advance.clone();
+
+        let mut read_items = vec![Expression::Poison; qty];
+        read_items[0] = read_expr;
+        for i in 1..qty {
+            struct_validator.set_argument_number(i);
+            struct_validator.validate_buffer(&offset, ns, vartab, cfg);
+            offset = Expression::Add(
+                Loc::Codegen,
+                Type::Uint(32),
+                false,
+                Box::new(offset.clone()),
+                Box::new(advance),
+            );
+            (read_expr, advance) = self.read_from_buffer(
+                buffer,
+                &offset,
+                &struct_tys[i],
+                &mut struct_validator,
+                ns,
+                vartab,
+                cfg,
+            );
+            read_items[i] = read_expr;
+            runtime_size = Expression::Add(
+                Loc::Codegen,
+                Type::Uint(32),
+                false,
+                Box::new(runtime_size),
+                Box::new(advance.clone()),
+            );
+        }
+
+        let allocated_struct = vartab.temp_anonymous(expr_ty);
+        cfg.add(
+            vartab,
+            Instr::Set {
+                loc: Loc::Codegen,
+                res: allocated_struct,
+                expr: Expression::StructLiteral(Loc::Codegen, expr_ty.clone(), read_items),
+            },
+        );
+
+        let struct_var = Expression::Variable(Loc::Codegen, expr_ty.clone(), allocated_struct);
+        (struct_var, size.unwrap_or(runtime_size))
+    }
 }

+ 230 - 0
src/codegen/encoding/buffer_validator.rs

@@ -0,0 +1,230 @@
+use crate::codegen::cfg::{ControlFlowGraph, Instr};
+use crate::codegen::vartable::Vartable;
+use crate::codegen::Expression;
+use crate::sema::ast::{Namespace, Type};
+use num_bigint::BigInt;
+use num_traits::Zero;
+use solang_parser::pt::Loc;
+use std::ops::AddAssign;
+
+/// When we are decoding serialized data from a bytes array, we must constantly verify if
+/// we are not reading past its ending. This struct helps us decrease the number of checks we do,
+/// by merging checks when we can determine the size of what to read beforehand.
+pub(super) struct BufferValidator<'a> {
+    /// Saves the codegen::Expression that contains the buffer length.
+    buffer_length: Expression,
+    /// The types we are supposed to decode
+    types: &'a [Type],
+    /// The argument whose size has already been accounted for when verifying the buffer
+    verified_until: Option<usize>,
+    /// The argument we are analysing presently
+    current_arg: usize,
+}
+
+impl BufferValidator<'_> {
+    pub fn new(buffer_size_var: usize, types: &[Type]) -> BufferValidator {
+        BufferValidator {
+            buffer_length: Expression::Variable(Loc::Codegen, Type::Uint(32), buffer_size_var),
+            types,
+            verified_until: None,
+            current_arg: 0,
+        }
+    }
+
+    /// Set which item we are currently reading from the buffer
+    pub(super) fn set_argument_number(&mut self, arg_no: usize) {
+        self.current_arg = arg_no;
+    }
+
+    /// Initialize the validator, by verifying every type that has a fixed size
+    pub(super) fn initialize_validation(
+        &mut self,
+        offset: &Expression,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        // -1 means nothing has been verified yet
+        self.verified_until = None;
+        self._verify_buffer(offset, ns, vartab, cfg);
+    }
+
+    /// Validate the buffer for the current argument, if necessary.
+    pub(super) fn validate_buffer(
+        &mut self,
+        offset: &Expression,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        // We may have already verified this
+        if self.verified_until.is_some() && self.current_arg <= self.verified_until.unwrap() {
+            return;
+        }
+
+        self._verify_buffer(offset, ns, vartab, cfg);
+    }
+
+    /// Validate if a given offset is within the buffer's bound.
+    pub(super) fn validate_offset(
+        &self,
+        offset: Expression,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        self.build_branch(offset, vartab, cfg);
+    }
+
+    /// Checks if a buffer validation is necessary
+    pub(super) fn validation_necessary(&self) -> bool {
+        self.verified_until.is_none() || self.current_arg > self.verified_until.unwrap()
+    }
+
+    /// After an array validation, we do not need to re-check its elements.
+    pub(super) fn validate_array(&mut self) {
+        self.verified_until = Some(self.current_arg);
+    }
+
+    /// Validate if offset + size is within the buffer's boundaries
+    pub(super) fn validate_offset_plus_size(
+        &mut self,
+        offset: &Expression,
+        size: &Expression,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        if self.validation_necessary() {
+            let offset_to_validate = Expression::Add(
+                Loc::Codegen,
+                Type::Uint(32),
+                false,
+                Box::new(offset.clone()),
+                Box::new(size.clone()),
+            );
+            self.validate_offset(offset_to_validate, vartab, cfg);
+        }
+    }
+
+    /// Validates if we have read all the bytes in a buffer
+    pub(super) fn validate_all_bytes_read(
+        &self,
+        end_offset: Expression,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        let cond = Expression::UnsignedLess(
+            Loc::Codegen,
+            Box::new(end_offset),
+            Box::new(self.buffer_length.clone()),
+        );
+
+        let invalid = cfg.new_basic_block("not_all_bytes_read".to_string());
+        let valid = cfg.new_basic_block("buffer_read".to_string());
+        cfg.add(
+            vartab,
+            Instr::BranchCond {
+                cond,
+                true_block: invalid,
+                false_block: valid,
+            },
+        );
+
+        cfg.set_basic_block(invalid);
+
+        // TODO: This needs a proper error message
+        cfg.add(vartab, Instr::AssertFailure { expr: None });
+
+        cfg.set_basic_block(valid);
+    }
+
+    /// Auxiliary function to verify if the offset is valid.
+    fn _verify_buffer(
+        &mut self,
+        offset: &Expression,
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) {
+        // Calculate the what arguments we can validate
+        let mut maximum_verifiable = self.current_arg;
+        for i in self.current_arg..self.types.len() {
+            if !self.types[i].is_dynamic(ns) {
+                maximum_verifiable = i;
+            } else {
+                break;
+            }
+        }
+
+        // It is not possible to validate anything
+        if maximum_verifiable == self.current_arg {
+            return;
+        }
+
+        // Create validation check
+        let mut advance = BigInt::zero();
+        for i in self.current_arg..=maximum_verifiable {
+            advance.add_assign(self.types[i].memory_size_of(ns));
+        }
+
+        let reach = Expression::Add(
+            Loc::Codegen,
+            Type::Uint(32),
+            false,
+            Box::new(offset.clone()),
+            Box::new(Expression::NumberLiteral(
+                Loc::Codegen,
+                Type::Uint(32),
+                advance,
+            )),
+        );
+
+        self.verified_until = Some(maximum_verifiable);
+        self.build_branch(reach, vartab, cfg);
+    }
+
+    /// Builds a branch for failing if we are out of bounds
+    fn build_branch(&self, offset: Expression, vartab: &mut Vartable, cfg: &mut ControlFlowGraph) {
+        let cond = Expression::LessEqual(
+            Loc::Codegen,
+            Box::new(offset),
+            Box::new(self.buffer_length.clone()),
+        );
+
+        let inbounds_block = cfg.new_basic_block("inbounds".to_string());
+        let out_of_bounds_block = cfg.new_basic_block("out_of_bounds".to_string());
+
+        cfg.add(
+            vartab,
+            Instr::BranchCond {
+                cond,
+                true_block: inbounds_block,
+                false_block: out_of_bounds_block,
+            },
+        );
+
+        cfg.set_basic_block(out_of_bounds_block);
+        // TODO: Add an error message here
+        cfg.add(vartab, Instr::AssertFailure { expr: None });
+        cfg.set_basic_block(inbounds_block);
+    }
+
+    /// Create a new buffer validator to validate struct fields.
+    pub(super) fn create_sub_validator<'a>(&self, types: &'a [Type]) -> BufferValidator<'a> {
+        // If the struct has been previously validated, there is no need to validate it again,
+        // so verified_until and current_arg are set to type.len() to avoid any further validation.
+        BufferValidator {
+            buffer_length: self.buffer_length.clone(),
+            types,
+            verified_until: if self.validation_necessary() {
+                None
+            } else {
+                Some(types.len())
+            },
+            current_arg: if self.validation_necessary() {
+                0
+            } else {
+                types.len()
+            },
+        }
+    }
+}

+ 167 - 1
src/codegen/encoding/mod.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 mod borsh_encoding;
+mod buffer_validator;
 
 use crate::codegen::cfg::{ControlFlowGraph, Instr};
 use crate::codegen::encoding::borsh_encoding::BorshEncoding;
@@ -10,8 +11,10 @@ use crate::codegen::{Builtin, Expression};
 use crate::sema::ast::{ArrayLength, Namespace, RetrieveType, StructType, Type};
 use crate::Target;
 use num_bigint::BigInt;
+use num_integer::Integer;
+use num_traits::{One, Zero};
 use solang_parser::pt::Loc;
-use std::ops::AddAssign;
+use std::ops::{AddAssign, MulAssign, Sub};
 
 /// This trait should be implemented by all encoding methods (ethabi, Scale and Borsh), so that
 /// we have the same interface for creating encode and decode functions.
@@ -26,6 +29,16 @@ pub(super) trait AbiEncoding {
         cfg: &mut ControlFlowGraph,
     ) -> Expression;
 
+    fn abi_decode(
+        &self,
+        loc: &Loc,
+        buffer: &Expression,
+        types: &[Type],
+        ns: &Namespace,
+        vartab: &mut Vartable,
+        cfg: &mut ControlFlowGraph,
+    ) -> Vec<Expression>;
+
     /// Cache items loaded from storage to reuse them later, so we avoid the expensive operation
     /// of loading from storage twice. We need the data in two different passes: first to
     /// calculate its size and then to copy it to the buffer.
@@ -88,6 +101,11 @@ fn get_expr_size<T: AbiEncoding>(
             calculate_struct_size(encoder, arg_no, expr, struct_ty, ns, vartab, cfg)
         }
 
+        Type::Slice(ty) => {
+            let dims = vec![ArrayLength::Dynamic];
+            calculate_array_size(encoder, expr, ty, &dims, arg_no, ns, vartab, cfg)
+        }
+
         Type::Array(ty, dims) => {
             calculate_array_size(encoder, expr, ty, dims, arg_no, ns, vartab, cfg)
         }
@@ -581,3 +599,151 @@ fn increment_four(expr: Expression) -> Expression {
         )),
     )
 }
+
+/// Check if we can MemCpy elements of an array to/from a buffer
+fn allow_direct_copy(
+    array_ty: &Type,
+    elem_ty: &Type,
+    dims: &[ArrayLength],
+    ns: &Namespace,
+) -> bool {
+    let type_direct_copy: bool = if let Type::Struct(struct_ty) = elem_ty {
+        if let Some(no_padded_size) = ns.calculate_struct_non_padded_size(struct_ty) {
+            let padded_size = struct_ty.struct_padded_size(ns);
+            // This remainder tells us if padding is needed between the elements of an array
+            let remainder = padded_size.mod_floor(&elem_ty.struct_elem_alignment(ns));
+
+            no_padded_size.eq(&padded_size) && ns.target == Target::Solana && remainder.is_zero()
+        } else {
+            false
+        }
+    } else if let Type::Bytes(n) = elem_ty {
+        // When n >=2, the bytes must be reversed
+        *n < 2
+    } else {
+        elem_ty.is_primitive()
+    };
+
+    if array_ty.is_dynamic(ns) {
+        // If this is a dynamic array, we can only MemCpy if its elements are of
+        // any primitive type and we don't need to index it.
+        dims.len() == 1 && type_direct_copy
+    } else {
+        // If the array is not dynamic, we can MemCpy elements if their are primitive.
+        type_direct_copy
+    }
+}
+
+/// Calculate the number of bytes needed to memcpy an entire vector
+fn calculate_direct_copy_bytes_size(
+    dims: &[ArrayLength],
+    elem_ty: &Type,
+    ns: &Namespace,
+) -> BigInt {
+    let mut elem_no = BigInt::one();
+    for item in dims {
+        debug_assert!(matches!(item, &ArrayLength::Fixed(_)));
+        elem_no.mul_assign(item.array_length().unwrap());
+    }
+    let bytes = elem_ty.memory_size_of(ns);
+    elem_no.mul_assign(bytes);
+
+    elem_no
+}
+
+/// Calculate the size in bytes of a dynamic array, whose dynamic dimension is the outer.
+/// It needs the variable saving the array's length.
+fn calculate_array_bytes_size(
+    length_variable: usize,
+    elem_ty: &Type,
+    ns: &Namespace,
+) -> Expression {
+    Expression::Multiply(
+        Loc::Codegen,
+        Type::Uint(32),
+        false,
+        Box::new(Expression::Variable(
+            Loc::Codegen,
+            Type::Uint(32),
+            length_variable,
+        )),
+        Box::new(Expression::NumberLiteral(
+            Loc::Codegen,
+            Type::Uint(32),
+            elem_ty.memory_size_of(ns),
+        )),
+    )
+}
+
+/// Retrieve a dynamic array length from the encoded buffer. It returns the variable number in which
+/// the length has been stored
+fn retrieve_array_length(
+    buffer: &Expression,
+    offset: &Expression,
+    vartab: &mut Vartable,
+    cfg: &mut ControlFlowGraph,
+) -> usize {
+    let array_length = vartab.temp_anonymous(&Type::Uint(32));
+    cfg.add(
+        vartab,
+        Instr::Set {
+            loc: Loc::Codegen,
+            res: array_length,
+            expr: Expression::Builtin(
+                Loc::Codegen,
+                vec![Type::Uint(32)],
+                Builtin::ReadFromBuffer,
+                vec![buffer.clone(), offset.clone()],
+            ),
+        },
+    );
+
+    array_length
+}
+
+/// Allocate an array in memory and return its variable number.
+fn allocate_array(
+    ty: &Type,
+    length_variable: usize,
+    vartab: &mut Vartable,
+    cfg: &mut ControlFlowGraph,
+) -> usize {
+    let array_var = vartab.temp_anonymous(ty);
+
+    cfg.add(
+        vartab,
+        Instr::Set {
+            loc: Loc::Codegen,
+            res: array_var,
+            expr: Expression::AllocDynamicArray(
+                Loc::Codegen,
+                ty.clone(),
+                Box::new(Expression::Variable(
+                    Loc::Codegen,
+                    Type::Uint(32),
+                    length_variable,
+                )),
+                None,
+            ),
+        },
+    );
+
+    array_var
+}
+
+impl StructType {
+    /// Calculate a struct size in memory considering the padding, if necessary
+    fn struct_padded_size(&self, ns: &Namespace) -> BigInt {
+        let mut total = BigInt::zero();
+        for item in &self.definition(ns).fields {
+            let ty_align = item.ty.struct_elem_alignment(ns);
+            let remainder = total.mod_floor(&ty_align);
+            if !remainder.is_zero() {
+                let padding = ty_align.sub(remainder);
+                total.add_assign(padding);
+            }
+            total.add_assign(item.ty.memory_size_of(ns));
+        }
+        total
+    }
+}

+ 7 - 1
src/codegen/expression.rs

@@ -436,7 +436,8 @@ pub fn expression(
         ast::Expression::InternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCall { .. }
         | ast::Expression::ExternalFunctionCallRaw { .. }
-        | ast::Expression::Builtin(_, _, ast::Builtin::AbiDecode, _) => {
+        | ast::Expression::Builtin(_, _, ast::Builtin::AbiDecode, _)
+        | ast::Expression::Builtin(_, _, ast::Builtin::AbiBorshDecode, _) => {
             let mut returns = emit_function_call(expr, contract_no, cfg, func, ns, vartab, opt);
 
             returns.remove(0)
@@ -2486,6 +2487,11 @@ pub fn emit_function_call(
                 unreachable!();
             }
         }
+        ast::Expression::Builtin(loc, tys, ast::Builtin::AbiBorshDecode, args) => {
+            let data = expression(&args[0], cfg, callee_contract_no, func, ns, vartab, opt);
+            let encoder = create_encoder(ns);
+            encoder.abi_decode(loc, &data, tys, ns, vartab, cfg)
+        }
         ast::Expression::Builtin(loc, tys, ast::Builtin::AbiDecode, args) => {
             let data = expression(&args[0], cfg, callee_contract_no, func, ns, vartab, opt);
 

+ 7 - 13
src/codegen/mod.rs

@@ -427,8 +427,6 @@ pub enum Expression {
     Variable(pt::Loc, Type, usize),
     ZeroExt(pt::Loc, Type, Box<Expression>),
     AdvancePointer {
-        loc: pt::Loc,
-        ty: Type,
         pointer: Box<Expression>,
         bytes_offset: Box<Expression>,
     },
@@ -489,12 +487,12 @@ impl CodeLocation for Expression {
             | Expression::BytesCast(loc, ..)
             | Expression::SignedMore(loc, ..)
             | Expression::UnsignedMore(loc, ..)
-            | Expression::ZeroExt(loc, ..)
-            | Expression::AdvancePointer { loc, .. } => *loc,
+            | Expression::ZeroExt(loc, ..) => *loc,
 
-            Expression::InternalFunctionCfg(_) | Expression::Poison | Expression::Undefined(_) => {
-                pt::Loc::Codegen
-            }
+            Expression::InternalFunctionCfg(_)
+            | Expression::Poison
+            | Expression::Undefined(_)
+            | Expression::AdvancePointer { .. } => pt::Loc::Codegen,
         }
     }
 }
@@ -639,8 +637,7 @@ impl RetrieveType for Expression {
             | Expression::AllocDynamicArray(_, ty, ..)
             | Expression::BytesCast(_, ty, ..)
             | Expression::RationalNumberLiteral(_, ty, ..)
-            | Expression::Subscript(_, ty, ..)
-            | Expression::AdvancePointer { ty, .. } => ty.clone(),
+            | Expression::Subscript(_, ty, ..) => ty.clone(),
 
             Expression::BoolLiteral(..)
             | Expression::MoreEqual(..)
@@ -660,6 +657,7 @@ impl RetrieveType for Expression {
                 list[0].ty()
             }
 
+            Expression::AdvancePointer { .. } => Type::BufferPointer,
             Expression::FormatString(..) => Type::String,
             Expression::InternalFunctionCfg(_) => Type::Unreachable,
             Expression::Poison => unreachable!("Expression does not have a type"),
@@ -1102,13 +1100,9 @@ impl Expression {
                     Box::new(filter(right, ctx)),
                 ),
                 Expression::AdvancePointer {
-                    loc,
-                    ty,
                     pointer,
                     bytes_offset: offset,
                 } => Expression::AdvancePointer {
-                    loc: *loc,
-                    ty: ty.clone(),
                     pointer: Box::new(filter(pointer, ctx)),
                     bytes_offset: Box::new(filter(offset, ctx)),
                 },

+ 1 - 4
src/codegen/subexpression_elimination/expression.rs

@@ -135,9 +135,7 @@ impl Expression {
                 Expression::LessEqual(*loc, Box::new(left.clone()), Box::new(right.clone()))
             }
 
-            Expression::AdvancePointer { loc, ty, .. } => Expression::AdvancePointer {
-                loc: *loc,
-                ty: ty.clone(),
+            Expression::AdvancePointer { .. } => Expression::AdvancePointer {
                 pointer: Box::new(left.clone()),
                 bytes_offset: Box::new(right.clone()),
             },
@@ -252,7 +250,6 @@ impl Expression {
             | Expression::AdvancePointer {
                 pointer: left,
                 bytes_offset: right,
-                ..
             }
             | Expression::LessEqual(_, left, right) => Some((left, right)),
 

+ 47 - 17
src/emit/mod.rs

@@ -2537,14 +2537,44 @@ pub trait TargetRuntime<'a> {
 
                 let start = unsafe { bin.builder.build_gep(data, &[offset], "start") };
 
-                let start = bin.builder.build_pointer_cast(
-                    start,
-                    bin.llvm_type(&returns[0], ns)
-                        .ptr_type(AddressSpace::Generic),
-                    "start",
-                );
+                if let Type::Bytes(n) = &returns[0] {
+                    let store = bin.build_alloca(
+                        function,
+                        bin.context.custom_width_int_type(*n as u32 * 8),
+                        "stack",
+                    );
+                    bin.builder.build_call(
+                        bin.module.get_function("__beNtoleN").unwrap(),
+                        &[
+                            bin.builder
+                                .build_pointer_cast(
+                                    start,
+                                    bin.context.i8_type().ptr_type(AddressSpace::Generic),
+                                    "",
+                                )
+                                .into(),
+                            bin.builder
+                                .build_pointer_cast(
+                                    store,
+                                    bin.context.i8_type().ptr_type(AddressSpace::Generic),
+                                    "",
+                                )
+                                .into(),
+                            bin.context.i32_type().const_int(*n as u64, false).into(),
+                        ],
+                        "",
+                    );
+                    bin.builder.build_load(store, &format!("bytes{}", *n))
+                } else {
+                    let start = bin.builder.build_pointer_cast(
+                        start,
+                        bin.llvm_type(&returns[0], ns)
+                            .ptr_type(AddressSpace::Generic),
+                        "start",
+                    );
 
-                bin.builder.build_load(start, "value")
+                    bin.builder.build_load(start, "value")
+                }
             }
             Expression::Keccak256(_, _, exprs) => {
                 let mut length = bin.context.i32_type().const_zero();
@@ -2940,7 +2970,6 @@ pub trait TargetRuntime<'a> {
             Expression::AdvancePointer {
                 pointer,
                 bytes_offset,
-                ..
             } => {
                 let pointer = if pointer.ty().is_dynamic_memory() {
                     bin.vector_bytes(self.expression(bin, pointer, vartab, function, ns))
@@ -3443,7 +3472,6 @@ pub trait TargetRuntime<'a> {
                         let dest_ref = self
                             .expression(bin, dest, &w.vars, function, ns)
                             .into_pointer_value();
-
                         bin.builder.build_store(dest_ref, value_ref);
                     }
                     Instr::BranchCond {
@@ -4584,17 +4612,19 @@ pub trait TargetRuntime<'a> {
                                 .into_pointer_value()
                         };
 
-                        let dest = self.expression(bin, to, &w.vars, function, ns);
+                        let dest = if to.ty().is_dynamic_memory() {
+                            bin.vector_bytes(self.expression(bin, to, &w.vars, function, ns))
+                        } else {
+                            self.expression(bin, to, &w.vars, function, ns)
+                                .into_pointer_value()
+                        };
+
                         let size = self.expression(bin, bytes, &w.vars, function, ns);
 
                         if matches!(bytes, Expression::NumberLiteral(..)) {
-                            let _ = bin.builder.build_memcpy(
-                                dest.into_pointer_value(),
-                                1,
-                                src,
-                                1,
-                                size.into_int_value(),
-                            );
+                            let _ =
+                                bin.builder
+                                    .build_memcpy(dest, 1, src, 1, size.into_int_value());
                         } else {
                             bin.builder.build_call(
                                 bin.module.get_function("__memcpy").unwrap(),

+ 3 - 0
src/sema/ast.rs

@@ -1126,6 +1126,9 @@ pub enum Builtin {
     MinimumBalance,
     TombstoneDeposit,
     AbiDecode,
+    // TODO: AbiBorshDecode is temporary and should be removed once Brosh encoding is fully
+    // wired for Solana
+    AbiBorshDecode,
     AbiEncode,
     AbiEncodePacked,
     AbiEncodeWithSelector,

+ 32 - 8
src/sema/builtin.rs

@@ -31,7 +31,7 @@ pub struct Prototype {
 }
 
 // A list of all Solidity builtins functions
-static BUILTIN_FUNCTIONS: Lazy<[Prototype; 27]> = Lazy::new(|| {
+static BUILTIN_FUNCTIONS: Lazy<[Prototype; 28]> = Lazy::new(|| {
     [
         Prototype {
             builtin: Builtin::Assert,
@@ -209,6 +209,17 @@ static BUILTIN_FUNCTIONS: Lazy<[Prototype; 27]> = Lazy::new(|| {
             doc: "Abi decode byte array with the given types",
             constant: false,
         },
+        Prototype {
+            builtin: Builtin::AbiBorshDecode,
+            namespace: Some("abi"),
+            method: None,
+            name: "borshDecode",
+            params: vec![Type::DynamicBytes],
+            ret: vec![],
+            target: vec![Target::Solana],
+            doc: "Abi decode byte array with the given types, using Borsh decoder",
+            constant: false,
+        },
         Prototype {
             builtin: Builtin::AbiEncode,
             namespace: Some("abi"),
@@ -1035,6 +1046,7 @@ pub fn resolve_namespace_call(
     let builtin = match name {
         "decode" => Builtin::AbiDecode,
         "encode" => Builtin::AbiEncode,
+        "borshDecode" => Builtin::AbiBorshDecode,
         "encodePacked" => Builtin::AbiEncodePacked,
         "encodeWithSelector" => Builtin::AbiEncodeWithSelector,
         "encodeWithSignature" => Builtin::AbiEncodeWithSignature,
@@ -1042,7 +1054,7 @@ pub fn resolve_namespace_call(
         _ => unreachable!(),
     };
 
-    if builtin == Builtin::AbiDecode {
+    if matches!(builtin, Builtin::AbiDecode | Builtin::AbiBorshDecode) {
         if args.len() != 2 {
             diagnostics.push(Diagnostic::error(
                 *loc,
@@ -1131,15 +1143,27 @@ pub fn resolve_namespace_call(
             }
         }
 
+        if builtin == Builtin::AbiBorshDecode {
+            // TODO: This is temporary and must be removed once Borsh encoding is fully wired up
+            // for Solana.
+            if ns.target != Target::Solana {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "'abi.borshDecode' is only available for Solana".to_string(),
+                ));
+            } else {
+                ns.diagnostics.push(Diagnostic::warning(
+                    *loc,
+                    "'abi.borshDecode' is experimental and is not yet fully compatible with Solana"
+                        .to_string(),
+                ));
+            }
+        }
+
         return if broken {
             Err(())
         } else {
-            Ok(Expression::Builtin(
-                *loc,
-                tys,
-                Builtin::AbiDecode,
-                vec![data],
-            ))
+            Ok(Expression::Builtin(*loc, tys, builtin, vec![data]))
         };
     }
 

+ 52 - 0
src/sema/types.rs

@@ -1056,6 +1056,57 @@ impl Type {
         }
     }
 
+    /// Retrieve the alignment for each type, if it is a struct member.
+    /// Arrays are always reference types when declared as local variables. Inside structs, however,
+    /// they are the object itself, if they are of fixed length.
+    pub fn struct_elem_alignment(&self, ns: &Namespace) -> BigInt {
+        match self {
+            Type::Bool
+            // Contract and address are arrays of u8, so they align with one.
+            | Type::Contract(_)
+            | Type::Address(_)
+            | Type::Enum(_) => BigInt::one(),
+
+            // Bytes are custom width type in LLVM, so they fit in the smallest integer type
+            // whose bitwidth is larger than what is needed.
+            Type::Bytes(n) => {
+                BigInt::from(n.next_power_of_two())
+            }
+
+            // The same reasoning as above applies for value
+            Type::Value => {
+                BigInt::from(ns.value_length.next_power_of_two())
+            }
+            Type::Int(n) | Type::Uint(n) => BigInt::from(n / 8),
+            Type::Rational => unreachable!(),
+            Type::Array(ty, dims) => {
+                if dims.iter().any(|d| *d == ArrayLength::Dynamic) {
+                    BigInt::from(ns.target.ptr_size() / 8)
+                } else {
+                    ty.struct_elem_alignment(ns)
+                }
+            }
+
+            Type::Struct(def) => {
+                def.definition(ns).fields.iter().map(|d| d.ty.struct_elem_alignment(ns)).max().unwrap()
+            }
+
+            Type::String
+            | Type::DynamicBytes
+            | Type::InternalFunction { .. }
+            | Type::Ref(_)
+            | Type::StorageRef(..) => BigInt::from(ns.target.ptr_size() / 8),
+
+            Type::ExternalFunction { .. } => {
+                Type::Address(false).struct_elem_alignment(ns)
+            }
+            Type::UserType(no) => ns.user_types[*no].ty.struct_elem_alignment(ns),
+
+            _ => unreachable!("Type should not appear on a struct"),
+
+        }
+    }
+
     /// Calculate how much memory this type occupies in Solana's storage.
     /// Depending on the llvm implementation there might be padding between elements
     /// which is not accounted for.
@@ -1341,6 +1392,7 @@ impl Type {
                 .iter()
                 .any(|f| f.ty.is_dynamic(ns)),
             Type::StorageRef(_, r) => r.is_dynamic(ns),
+            Type::Slice(_) => true,
             _ => false,
         }
     }

+ 238 - 0
tests/codegen_testcases/solidity/borsh_decoding_complex_types.sol

@@ -0,0 +1,238 @@
+// RUN: --target solana --emit cfg
+
+contract Testing {
+    struct NonConstantStruct {
+        uint64 a;
+        string[] b;
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::nonCteArray__bytes
+    function nonCteArray(bytes memory buffer) public pure returns (string[] memory) {
+        string[] memory a = abi.borshDecode(buffer, (string[]));
+	    
+        // CHECK: ty:uint32 %temp.10 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: ty:uint32 %temp.12 = uint32 0
+	    // CHECK: branchcond (uint32 4 <= %temp.10), block1, block2
+    
+        // CHECK: block1: # inbounds
+	    // CHECK: ty:uint32 %temp.13 = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:uint32 %temp.12 = uint32 4
+	    // CHECK: ty:string[] %temp.14 = (alloc string[] len %temp.13)
+	    // CHECK: ty:string[] %temp.11 = %temp.14
+	    // CHECK: ty:uint32 %for_i_0.temp.15 = uint32 0
+	    // CHECK: branch block3
+    
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block3: # cond
+	    // CHECK: branchcond (unsigned less %for_i_0.temp.15 < (builtin ArrayLength (%temp.11))), block5, block6
+
+        // CHECK: block4: # next
+	    // CHECK: ty:uint32 %for_i_0.temp.15 = (%for_i_0.temp.15 + uint32 1)
+	    // CHECK: branch block3
+        
+        // CHECK: block5: # body
+	    // CHECK: ty:uint32 %1.cse_temp = (%temp.12 + uint32 4)
+	    // CHECK: branchcond (%1.cse_temp <= %temp.10), block7, block8
+        
+        // CHECK: block6: # end_for
+	    // CHECK: ty:uint32 %temp.12 = (%temp.12 - uint32 0)
+	    // CHECK: branchcond (unsigned less (uint32 0 + %temp.12) < %temp.10), block11, block12
+
+        // CHECK: block7: # inbounds
+	    // CHECK: ty:uint32 %temp.16 = (builtin ReadFromBuffer ((arg #0), %temp.12))
+	    // CHECK: ty:uint32 %2.cse_temp = ((%temp.16 + uint32 4) + %temp.12)
+	    // CHECK: branchcond (%2.cse_temp <= %temp.10), block9, block10
+        
+        // CHECK: block8: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block9: # inbounds
+	    // CHECK: ty:string %temp.17 = (alloc string len %temp.16)
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: %1.cse_temp), dest: %temp.17, bytes_len: %temp.16
+	    // CHECK: store (subscript string[] %temp.11[%for_i_0.temp.15]), %temp.17
+	    // CHECK: ty:uint32 %temp.12 = %2.cse_temp
+	    // CHECK: branch block4
+        
+        // CHECK: block10: # out_of_bounds
+	    // CHECK: assert-failure
+
+		// CHECK: block11: # not_all_bytes_read
+		// CHECK: assert-failure
+		// CHECK: block12: # buffer_read
+		// CHECK: ty:string[] %a = %temp.11
+
+        return a;
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::complexStruct__bytes
+    function complexStruct(bytes memory buffer) public pure returns (NonConstantStruct memory) {
+        NonConstantStruct memory cte = abi.borshDecode(buffer, (NonConstantStruct));
+
+	    // CHECK: ty:uint32 %temp.20 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 8 <= %temp.20), block1, block2
+        
+        // CHECK: block1: # inbounds
+	    // CHECK: ty:uint32 %temp.22 = uint32 8
+	    // CHECK: branchcond (uint32 12 <= %temp.20), block3, block4
+
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block3: # inbounds
+	    // CHECK: ty:uint32 %temp.23 = (builtin ReadFromBuffer ((arg #0), uint32 8))
+	    // CHECK: ty:uint32 %temp.22 = uint32 12
+	    // CHECK: ty:string[] %temp.24 = (alloc string[] len %temp.23)
+	    // CHECK: ty:string[] %temp.21 = %temp.24
+	    // CHECK: ty:uint32 %for_i_0.temp.25 = uint32 0
+	    // CHECK: branch block5
+        
+        // CHECK: block4: # out_of_bounds
+	    // CHECK: assert-failure
+    
+        // CHECK: block5: # cond
+	    // CHECK: branchcond (unsigned less %for_i_0.temp.25 < (builtin ArrayLength (%temp.21))), block7, block8
+        
+        // CHECK: block6: # next
+	    // CHECK: ty:uint32 %for_i_0.temp.25 = (%for_i_0.temp.25 + uint32 1)
+	    // CHECK: branch block5
+
+        // CHECK: block7: # body
+	    // CHECK: ty:uint32 %1.cse_temp = (%temp.22 + uint32 4)
+	    // CHECK: branchcond (%1.cse_temp <= %temp.20), block9, block10
+
+        // CHECK: block8: # end_for
+    	// CHECK: ty:uint32 %temp.22 = (%temp.22 - uint32 8)
+	    // CHECK: ty:struct Testing.NonConstantStruct %temp.28 = struct { (builtin ReadFromBuffer ((arg #0), uint32 0)), %temp.21 }
+	    // CHECK: branchcond (unsigned less (uint32 0 + (uint32 8 + %temp.22)) < %temp.20), block13, block14
+
+        // CHECK: block9: # inbounds
+	    // CHECK: ty:uint32 %temp.26 = (builtin ReadFromBuffer ((arg #0), %temp.22))
+	    // CHECK: ty:uint32 %2.cse_temp = ((%temp.26 + uint32 4) + %temp.22)
+	    // CHECK: branchcond (%2.cse_temp <= %temp.20), block11, block12
+        
+        // CHECK: block10: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block11: # inbounds
+	    // CHECK: ty:string %temp.27 = (alloc string len %temp.26)
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: %1.cse_temp), dest: %temp.27, bytes_len: %temp.26
+	    // CHECK: store (subscript string[] %temp.21[%for_i_0.temp.25]), %temp.27
+	    // CHECK: ty:uint32 %temp.22 = %2.cse_temp
+	    // CHECK: branch block6
+    
+        // CHECK: block12: # out_of_bounds
+        // CHECK: assert-failure
+
+		// CHECK: block13: # not_all_bytes_read
+		// CHECK: assert-failure
+
+		// CHECK: block14: # buffer_read
+		// CHECK: ty:struct Testing.NonConstantStruct %cte = %temp.28
+        return cte;
+    }
+
+    NonConstantStruct[] storage_vec;
+    // BEGIN-CHECK: Testing::Testing::function::complexArray__bytes
+    function complexArray(bytes memory buffer) public {
+        NonConstantStruct[] memory arr = abi.borshDecode(buffer, (NonConstantStruct[]));
+
+	    // CHECK: ty:uint32 %temp.31 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: ty:uint32 %temp.33 = uint32 0
+	    // CHECK: branchcond (uint32 4 <= %temp.31), block1, block2
+    
+        // CHECK: block1: # inbounds
+    	// CHECK: ty:uint32 %temp.34 = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:uint32 %temp.33 = uint32 4
+	    // CHECK: ty:struct Testing.NonConstantStruct[] %temp.35 = (alloc struct Testing.NonConstantStruct[] len %temp.34)
+	    // CHECK: ty:struct Testing.NonConstantStruct[] %temp.32 = %temp.35
+	    // CHECK: ty:uint32 %for_i_0.temp.36 = uint32 0
+	    // CHECK: branch block3
+        
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block3: # cond
+	    // CHECK: branchcond (unsigned less %for_i_0.temp.36 < (builtin ArrayLength (%temp.32))), block5, block6
+        
+        // CHECK: block4: # next
+	    // CHECK: ty:uint32 %for_i_0.temp.36 = (%for_i_0.temp.36 + uint32 1)
+	    // CHECK: branch block3
+
+        // CHECK: block5: # body
+	    // CHECK: ty:uint32 %1.cse_temp = (%temp.33 + uint32 8)
+	    // CHECK: branchcond (%1.cse_temp <= %temp.31), block7, block8
+        
+        // CHECK: block6: # end_for
+	    // CHECK: ty:uint32 %temp.33 = (%temp.33 - uint32 0)
+	    // CHECK: branchcond (unsigned less (uint32 0 + %temp.33) < %temp.31), block19, block20
+
+        // CHECK: block7: # inbounds
+	    // CHECK: ty:uint32 %temp.38 = %1.cse_temp
+	    // CHECK: ty:uint32 %2.cse_temp = (%temp.38 + uint32 4)
+	    // CHECK: branchcond (%2.cse_temp <= %temp.31), block9, block10
+
+        // CHECK: block8: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block9: # inbounds
+	    // CHECK: ty:uint32 %temp.39 = (builtin ReadFromBuffer ((arg #0), %temp.38))
+	    // CHECK: ty:uint32 %temp.38 = %2.cse_temp
+	    // CHECK: ty:string[] %temp.40 = (alloc string[] len %temp.39)
+	    // CHECK: ty:string[] %temp.37 = %temp.40
+	    // CHECK: ty:uint32 %for_i_0.temp.41 = uint32 0
+	    // CHECK: branch block11
+
+        // CHECK: block10: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block11: # cond
+	    // CHECK: branchcond (unsigned less %for_i_0.temp.41 < (builtin ArrayLength (%temp.37))), block13, block14
+
+        // CHECK: block12: # next
+	    // CHECK: ty:uint32 %for_i_0.temp.41 = (%for_i_0.temp.41 + uint32 1)
+	    // CHECK: branch block11
+
+        // CHECK: block13: # body
+	    // CHECK: ty:uint32 %3.cse_temp = (%temp.38 + uint32 4)
+	    // CHECK: branchcond (%3.cse_temp <= %temp.31), block15, block16
+
+        // CHECK: block14: # end_for
+	    // CHECK: ty:uint32 %temp.38 = (%temp.38 - (%temp.33 + uint32 8))
+	    // CHECK: ty:struct Testing.NonConstantStruct %temp.44 = struct { (builtin ReadFromBuffer ((arg #0), %temp.33)), %temp.37 }
+	    // CHECK: store (subscript struct Testing.NonConstantStruct[] %temp.32[%for_i_0.temp.36]), (load %temp.44)
+	    // CHECK: ty:uint32 %temp.33 = ((uint32 8 + %temp.38) + %temp.33)
+        // CHECK: branch block4
+
+        // CHECK: block15: # inbounds
+	    // CHECK: ty:uint32 %temp.42 = (builtin ReadFromBuffer ((arg #0), %temp.38))
+	    // CHECK: ty:uint32 %4.cse_temp = ((%temp.42 + uint32 4) + %temp.38)
+	    // CHECK: branchcond (%4.cse_temp <= %temp.31), block17, block18
+    
+        // CHECK: block16: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block17: # inbounds
+	    // CHECK: ty:string %temp.43 = (alloc string len %temp.42)
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: %3.cse_temp), dest: %temp.43, bytes_len: %temp.42
+	    // CHECK: store (subscript string[] %temp.37[%for_i_0.temp.41]), %temp.43
+	    // CHECK: ty:uint32 %temp.38 = %4.cse_temp
+	    // CHECK: branch block12
+    
+        // CHECK: block18: # out_of_bounds
+	    // CHECK: assert-failure
+
+		// CHECK: block19: # not_all_bytes_read
+		// CHECK: assert-failure
+
+		// CHECK: block20: # buffer_read
+		// CHECK: ty:struct Testing.NonConstantStruct[] %arr = %temp.32
+		// CHECK: ty:struct Testing.NonConstantStruct[] storage %temp.45 = %arr
+        storage_vec = arr;
+    }
+
+    function getItem(uint32 idx) public view returns (NonConstantStruct memory) {
+        return storage_vec[idx];
+    }
+}

+ 261 - 0
tests/codegen_testcases/solidity/borsh_decoding_simple_types.sol

@@ -0,0 +1,261 @@
+// RUN: --target solana --emit cfg
+contract Other {
+
+}
+
+contract Testing {
+    // BEGIN-CHECK: Testing::Testing::function::addressContract__bytes
+    function addressContract(bytes memory buffer) public pure returns (address, Other) {
+        (address a, Other b) = abi.borshDecode(buffer, (address, Other));
+	    // CHECK: ty:bytes %buffer = (arg #0)
+	    // CHECK: ty:uint32 %temp.60 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 64 <= %temp.60), block1, block2
+        // CHECK: block1: # inbounds
+    	// CHECK: branchcond (unsigned less uint32 64 < %temp.60), block3, block4
+        
+        // CHECK: block2: # out_of_bounds
+        // CHECK: assert-failure
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+
+        // CHECK: block4: # buffer_read
+	    // CHECK: ty:address %a = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:contract Other %b = (builtin ReadFromBuffer ((arg #0), uint32 32))
+        return (a, b);
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::unsignedInteger__bytes
+    function unsignedInteger(bytes memory buffer) public pure returns (uint8, uint16,
+     uint32, uint64, uint128, uint256) {
+        (uint8 a, uint16 b, uint32 c, uint64 d, uint128 e, uint256 f) = 
+        abi.borshDecode(buffer, (uint8, uint16, uint32, uint64, uint128, uint256));
+
+        // CHECK: ty:uint32 %temp.61 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 63 <= %temp.61), block1, block2
+        // CHECK: block1: # inbounds
+        // CHECK: branchcond (unsigned less uint32 63 < %temp.61), block3, block4
+
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+        
+        // CHECK:  block4: # buffer_read
+	    // CHECK: ty:uint8 %a = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:uint16 %b = (builtin ReadFromBuffer ((arg #0), uint32 1))
+	    // CHECK: ty:uint32 %c = (builtin ReadFromBuffer ((arg #0), uint32 3))
+	    // CHECK: ty:uint64 %d = (builtin ReadFromBuffer ((arg #0), uint32 7))
+	    // CHECK: ty:uint128 %e = (builtin ReadFromBuffer ((arg #0), uint32 15))
+	    // CHECK: ty:uint256 %f = (builtin ReadFromBuffer ((arg #0), uint32 31))
+
+        return (a, b, c, d, e, f);
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::signedInteger__bytes
+    function signedInteger(bytes memory buffer) public pure returns (int8, int16, int32,
+     int64, int128, int256) {
+        (int8 a, int16 b, int32 c, int64 d, int128 e, int256 f) = 
+        abi.borshDecode(buffer, (int8, int16, int32, int64, int128, int256));
+
+        // CHECK: ty:uint32 %temp.62 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 63 <= %temp.62), block1, block2
+        // CHECK: block1: # inbounds
+    	// CHECK: branchcond (unsigned less uint32 63 < %temp.62), block3, block4
+	    
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+
+
+        // CHECK: block4: # buffer_read
+	    // CHECK: ty:int8 %a = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:int16 %b = (builtin ReadFromBuffer ((arg #0), uint32 1))
+	    // CHECK: ty:int32 %c = (builtin ReadFromBuffer ((arg #0), uint32 3))
+	    // CHECK: ty:int64 %d = (builtin ReadFromBuffer ((arg #0), uint32 7))
+	    // CHECK: ty:int128 %e = (builtin ReadFromBuffer ((arg #0), uint32 15))
+	    // CHECK: ty:int256 %f = (builtin ReadFromBuffer ((arg #0), uint32 31))
+
+        return (a, b, c, d, e, f);
+     }
+
+    // BEGIN-CHECK: Testing::Testing::function::fixedBytes__bytes
+    function fixedBytes(bytes memory buffer) public pure returns (bytes1, bytes5, bytes20, bytes32) {
+        (bytes1 a, bytes5 b, bytes20 c, bytes32 d) = abi.borshDecode(buffer, (bytes1, bytes5, bytes20, bytes32));
+
+        // CHECK: ty:uint32 %temp.63 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 58 <= %temp.63), block1, block2
+        // CHECK: block1: # inbounds
+        // CHECK: branchcond (unsigned less uint32 58 < %temp.63), block3, block4
+	    
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+        
+        // CHECK: block4: # buffer_read
+	    // CHECK: ty:bytes1 %a = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:bytes5 %b = (builtin ReadFromBuffer ((arg #0), uint32 1))
+	    // CHECK: ty:bytes20 %c = (builtin ReadFromBuffer ((arg #0), uint32 6))
+	    // CHECK: ty:bytes32 %d = (builtin ReadFromBuffer ((arg #0), uint32 26))
+        return (a, b, c, d);
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::stringAndBytes__bytes
+    function stringAndBytes(bytes memory buffer) public pure returns (bytes memory, string memory) {
+        (bytes memory a, string memory b) = abi.borshDecode(buffer, (bytes, string));
+    	// CHECK: ty:bytes %buffer = (arg #0)
+	    // CHECK: ty:uint32 %temp.64 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 4 <= %temp.64), block1, block2
+        // CHECK: block1: # inbounds
+	    // CHECK: ty:uint32 %temp.65 = (builtin ReadFromBuffer ((arg #0), uint32 0))
+	    // CHECK: ty:uint32 %1.cse_temp = ((%temp.65 + uint32 4) + uint32 0)
+	    // CHECK: branchcond (%1.cse_temp <= %temp.64), block3, block4
+        
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+        
+        // CHECK: block3: # inbounds
+	    // CHECK: ty:bytes %temp.66 = (alloc bytes len %temp.65)
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 4), dest: %temp.66, bytes_len: %temp.65
+	    // CHECK: ty:uint32 %2.cse_temp = (%1.cse_temp + uint32 4)
+	    // check: branchcond (%2.cse_temp <= %temp.64), block5, block6
+        
+        // CHECK: block4: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block5: # inbounds
+	    // CHECK: ty:uint32 %temp.67 = (builtin ReadFromBuffer ((arg #0), (uint32 0 + (%temp.65 + uint32 4))))
+        // CHECK: ty:uint32 %3.cse_temp = ((%temp.67 + uint32 4) + %1.cse_temp)
+	    // CHECK: branchcond (%3.cse_temp <= %temp.64), block7, block8
+
+        // CHECK: block6: # out_of_bounds
+        // CHECK: assert-failure
+
+        // CHECK: block7: # inbounds
+	    // CHECK: ty:string %temp.68 = (alloc string len %temp.67)
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: %2.cse_temp), dest: %temp.68, bytes_len: %temp.67
+        // CHECK: branchcond (unsigned less %3.cse_temp < %temp.64), block9, block10
+
+        // CHECK: block8: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block9: # not_all_bytes_read
+	    // CHECK: assert-failure
+
+        // CHECK: block10: # buffer_read
+	    // CHECK: ty:bytes %a = %temp.66
+	    // CHECK: ty:string %b = %temp.68
+        return (a, b);
+    }
+
+    enum WeekDays {
+        sunday, monday, tuesday, wednesday, thursday, friday, saturday
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::decodeEnum__bytes
+    function decodeEnum(bytes memory buffer) public pure returns (WeekDays) {
+        WeekDays a = abi.borshDecode(buffer, (WeekDays));
+	    // CHECK: ty:uint32 %temp.72 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 1 <= %temp.72), block1, block2
+        // CHECK: block1: # inbounds
+        // CHECK: branchcond (unsigned less uint32 1 < %temp.72), block3, block4
+        
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+
+        // CHECK: block4: # buffer_read
+	    // CHECK: ty:enum Testing.WeekDays %a = (builtin ReadFromBuffer ((arg #0), uint32 0))        
+        return a;
+    }
+
+    struct noPadStruct {
+        uint32 a;
+        uint32 b;
+    }
+
+    struct PaddedStruct {
+        uint128 a;
+        uint8 b;
+        bytes32 c;
+    }
+
+    // BEGIN-CHECK:Testing::Testing::function::decodeStruct__bytes
+    function decodeStruct(bytes memory buffer) public pure returns (noPadStruct memory, PaddedStruct memory) {
+        (noPadStruct memory a, PaddedStruct memory b) = abi.borshDecode(buffer, (noPadStruct, PaddedStruct));
+        
+        // CHECK: ty:uint32 %temp.73 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 57 <= %temp.73), block1, block2
+        // CHECK: block1: # inbounds
+	    
+        // CHECK: ty:struct Testing.noPadStruct %temp.74 = struct {  }
+	    // CHECK: memcpy src: %buffer, dest: %temp.74, bytes_len: uint32 8
+	    // CHECK: ty:struct Testing.PaddedStruct %temp.75 = struct { (builtin ReadFromBuffer ((arg #0), uint32 8)), (builtin ReadFromBuffer ((arg #0), uint32 24)), (builtin ReadFromBuffer ((arg #0), uint32 25)) }
+	    // CHECK: branchcond (unsigned less uint32 57 < %temp.73), block3, block4
+
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block3: # not_all_bytes_read
+	    // CHECK: assert-failure
+        
+        // CHECK: block4: # buffer_read
+	    // CHECK: ty:struct Testing.noPadStruct %a = %temp.74
+	    // CHECK: ty:struct Testing.PaddedStruct %b = %temp.75
+        return (a, b);
+    }
+
+    // BEGIN-CHECK: Testing::Testing::function::primitiveStruct__bytes
+    function primitiveStruct(bytes memory buffer) public pure returns (uint32[4] memory, noPadStruct[2] memory, noPadStruct[] memory) {
+        (uint32[4] memory a, noPadStruct[2] memory b, noPadStruct[] memory c) = 
+        abi.borshDecode(buffer, (uint32[4], noPadStruct[2], noPadStruct[]));
+
+    	// CHECK: ty:uint32 %temp.76 = (builtin ArrayLength ((arg #0)))
+	    // CHECK: branchcond (uint32 32 <= %temp.76), block1, block2
+        // CHECK: block1: # inbounds
+	    
+        // CHECK: ty:uint32[4] %temp.77 =  [  ]
+	    // CHECK: memcpy src: %buffer, dest: %temp.77, bytes_len: uint32 16
+	    // CHECK: ty:struct Testing.noPadStruct[2] %temp.78 =  [  ]
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 16), dest: %temp.78, bytes_len: uint32 16
+	    // CHECK: branchcond (uint32 36 <= %temp.76), block3, block4
+        
+        // CHECK: block2: # out_of_bounds
+	    // CHECK: assert-failure
+    
+    
+        // CHECK: block3: # inbounds
+	    // CHECK: ty:uint32 %temp.79 = (builtin ReadFromBuffer ((arg #0), uint32 32))
+	    // CHECK: ty:struct Testing.noPadStruct[] %temp.80 = (alloc struct Testing.noPadStruct[] len %temp.79)
+	    // CHECK: ty:uint32 %1.cse_temp = (%temp.79 * uint32 8)
+	    // CHECK: branchcond ((uint32 36 + %1.cse_temp) <= %temp.76), block5, block6
+        
+        // CHECK: block4: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block5: # inbounds
+	    // CHECK: memcpy src: (advance ptr: %buffer, by: uint32 36), dest: %temp.80, bytes_len: %1.cse_temp
+        // CHECK: branchcond (unsigned less (uint32 32 + (%1.cse_temp + uint32 4)) < %temp.76), block7, block8
+
+        // CHECK: block6: # out_of_bounds
+	    // CHECK: assert-failure
+
+        // CHECK: block7: # not_all_bytes_read
+	    // CHECK: assert-failure  
+        // CHECK: block8: # buffer_read
+
+	    // CHECK: ty:uint32[4] %a = %temp.77
+	    // CHECK: ty:struct Testing.noPadStruct[2] %b = %temp.78
+	    // CHECK: ty:struct Testing.noPadStruct[] %c = %temp.80
+
+        return (a, b, c);
+    }
+}

+ 12 - 48
tests/codegen_testcases/solidity/borsh_encoding_simple_types.sol

@@ -118,7 +118,7 @@ contract EncodingTest {
         // CHECK: ty:bytes %abi_encoded.temp.83 = (alloc bytes len (((builtin ArrayLength (%a)) + uint32 4) + ((builtin ArrayLength (%b)) + uint32 4)))
         // CHECK: ty:uint32 %temp.84 = (builtin ArrayLength (%a))
         // CHECK: writebuffer buffer:%abi_encoded.temp.83 offset:uint32 0 value:%temp.84
-        // CHECK: memcpy src: %a, dest: (advance ptr: %abi_encoded.temp.83, by: (uint32 0 + uint32 4)), bytes_len: %temp.84
+        // CHECK: memcpy src: %a, dest: (advance ptr: %abi_encoded.temp.83, by: uint32 4), bytes_len: %temp.84
         // CHECK: ty:uint32 %temp.85 = (builtin ArrayLength (%b))
         // CHECK: ty:uint32 %1.cse_temp = (uint32 0 + (%temp.84 + uint32 4))
         // CHECK: writebuffer buffer:%abi_encoded.temp.83 offset:%1.cse_temp value:%temp.85
@@ -163,7 +163,7 @@ contract EncodingTest {
         bytes memory b = abi.encode(test_vec_1[2], ss);
         // CHECK: %temp.91 = load storage slot((subscript struct EncodingTest.noPadStruct[] storage uint32 16[uint32 2])) ty:struct EncodingTest.noPadStruct
         // CHECK: ty:bytes %abi_encoded.temp.92 = (alloc bytes len (uint32 8 + uint32 49))
-        // CHECK: memcpy src: %temp.91, dest: (advance ptr: %abi_encoded.temp.92, by: uint32 0), bytes_len: uint32 8
+        // CHECK: memcpy src: %temp.91, dest: %abi_encoded.temp.92, bytes_len: uint32 8
         // CHECK: ty:uint32 %1.cse_temp = (uint32 0 + uint32 8)
         // CHECK: writebuffer buffer:%abi_encoded.temp.92 offset:%1.cse_temp value:(load (struct %ss field 0))
         // CHECK: ty:uint32 %2.cse_temp = (%1.cse_temp + uint32 16)
@@ -184,49 +184,13 @@ contract EncodingTest {
 	    // CHECK: ty:uint32 %temp.97 = uint32 16
 	    // CHECK: ty:uint32 %temp.98 = uint32 16
 	    // CHECK: ty:bytes %abi_encoded.temp.99 = (alloc bytes len ((%temp.96 + %temp.97) + %temp.98))
-        // CHECK: ty:uint32 %temp.100 = uint32 0
-        // CHECK: writebuffer buffer:%abi_encoded.temp.99 offset:%temp.100 value:(builtin ArrayLength (%temp.95))
-
-        // CHECK: ty:uint32 %temp.100 = uint32 4
-        // CHECK: ty:uint32 %for_i_0.temp.101 = uint32 0
-        // CHECK: branch block1
-
-        // CHECK: block1: # cond
-        // CHECK: branchcond (unsigned less %for_i_0.temp.101 < (builtin ArrayLength (%temp.95))), block3, block4
-
-        // CHECK: block2: # next
-        // CHECK: ty:uint32 %for_i_0.temp.101 = (%for_i_0.temp.101 + uint32 1)
-        // CHECK: branch block1
-
-        // CHECK: block3: # body
-        // CHECK: memcpy src: (subscript struct EncodingTest.noPadStruct[] %temp.95[%for_i_0.temp.101]), dest: (advance ptr: %abi_encoded.temp.99, by: %temp.100), bytes_len: uint32 8
-        // CHECK: ty:uint32 %temp.100 = (uint32 8 + %temp.100)
-        // CHECK: branch block2
+        // CHECK: ty:uint32 %temp.100 = (builtin ArrayLength (%temp.95))
+        // CHECK: memcpy src: %temp.95, dest: (advance ptr: %abi_encoded.temp.99, by: uint32 4), bytes_len: (%temp.100 * uint32 8)
         
-        // CHECK: block4: # end_for
-        // CHECK: ty:uint32 %temp.100 = (%temp.100 - uint32 0)
-        // CHECK: memcpy src: %mem_vec, dest: (advance ptr: %abi_encoded.temp.99, by: (uint32 0 + %temp.100)), bytes_len: uint32 16
-        // CHECK: ty:uint32 %2.cse_temp = ((uint32 0 + %temp.100) + uint32 16)
-        // CHECK: ty:uint32 %temp.102 = %2.cse_temp
-        // CHECK: ty:uint32 %for_i_0.temp.103 = uint32 0
-        // CHECK: branch block5
-
-        // CHECK: block5: # cond
-        // CHECK: branchcond (unsigned less %for_i_0.temp.103 < uint32 2), block7, block8
-
-        // CHECK: block6: # next
-        // CHECK: ty:uint32 %for_i_0.temp.103 = (%for_i_0.temp.103 + uint32 1)
-        // CHECK: branch block5
-
-        // CHECK: block7: # body
-        // CHECK: memcpy src: (subscript struct EncodingTest.noPadStruct[2] %str_vec[%for_i_0.temp.103]), dest: (advance ptr: %abi_encoded.temp.99, by: %temp.102), bytes_len: uint32 8
-        // CHECK: ty:uint32 %temp.102 = (uint32 8 + %temp.102)
-        // CHECK: branch block6
-
-        // CHECK: block8: # end_for
-        // CHECK: ty:uint32 %temp.102 = (%temp.102 - %2.cse_temp)
-        // CHECK: ty:bytes %b1 = %abi_encoded.temp.99
-        // CHECK: return %b1
+        // CHECK: memcpy src: %mem_vec, dest: (advance ptr: %abi_encoded.temp.99, by: (uint32 0 + ((%temp.100 * uint32 8) + uint32 4))), bytes_len: uint32 16
+        // CHECK: memcpy src: %str_vec, dest: (advance ptr: %abi_encoded.temp.99, by: ((uint32 0 + ((%temp.100 * uint32 8) + uint32 4)) + uint32 16)), bytes_len: uint32 16
+	    // CHECK: ty:bytes %b1 = %abi_encoded.temp.99
+	    // CHECK: return %b1
         return b1;
     }
 
@@ -239,10 +203,10 @@ contract EncodingTest {
         function (int64, int64) external returns (int64) fPtr = this.doThis;
         uint64 pr = 9234;
 
-        // CHECK: ty:bytes %abi_encoded.temp.106 = (alloc bytes len (uint32 36 + uint32 8))
-        // CHECK: writebuffer buffer:%abi_encoded.temp.106 offset:uint32 0 value:(load (struct %fPtr field 0))
-        // CHECK: writebuffer buffer:%abi_encoded.temp.106 offset:(uint32 0 + uint32 4) value:(load (struct %fPtr field 1))
-        // CHECK: writebuffer buffer:%abi_encoded.temp.106 offset:(uint32 0 + uint32 36) value:%pr
+        // CHECK: ty:bytes %abi_encoded.temp.103 = (alloc bytes len (uint32 36 + uint32 8))
+        // CHECK: writebuffer buffer:%abi_encoded.temp.103 offset:uint32 0 value:(load (struct %fPtr field 0))
+        // CHECK: writebuffer buffer:%abi_encoded.temp.103 offset:(uint32 0 + uint32 4) value:(load (struct %fPtr field 1))
+        // CHECK: writebuffer buffer:%abi_encoded.temp.103 offset:(uint32 0 + uint32 36) value:%pr
 
         bytes memory b = abi.encode(fPtr, pr);
         return b;

+ 5 - 1
tests/solana.rs

@@ -1580,7 +1580,11 @@ impl VirtualMachine {
         println!("input: {}", hex::encode(&calldata));
 
         let res = self.execute(&calldata, seeds);
-        assert_eq!(res, Ok(0));
+        match res {
+            Ok(0) => (),
+            Ok(error_code) => panic!("unexpected return {:#x}", error_code),
+            Err(e) => panic!("error: {:?}", e),
+        };
 
         if let Some((_, return_data)) = &self.return_data {
             println!("return: {}", hex::encode(&return_data));

+ 1175 - 0
tests/solana_tests/abi_decode.rs

@@ -0,0 +1,1175 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::build_solidity;
+use crate::solana_tests::abi_encode::create_response;
+use borsh::BorshSerialize;
+use ethabi::Token;
+
+#[test]
+fn integers_bool_enum() {
+    #[derive(BorshSerialize, PartialEq, Eq, Debug)]
+    #[allow(unused)]
+    enum WeekDay {
+        Sunday,
+        Monday,
+        Tuesday,
+        Wednesday,
+        Thursday,
+        Friday,
+        Saturday,
+    }
+
+    #[derive(BorshSerialize, Debug)]
+    struct Res1 {
+        a: u8,
+        b: u64,
+        c: u128,
+        d: i16,
+        e: i32,
+        day: WeekDay,
+        h: bool,
+    }
+
+    #[derive(BorshSerialize, Debug)]
+    struct Res2 {
+        item_1: WeekDay,
+        item_2: WeekDay,
+        item_3: WeekDay,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        enum WeekDay {
+            Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
+        }
+
+        function decodeTest1(bytes memory buffer) public pure {
+            (uint8 a, uint64 b, uint128 c, int16 d, int32 e, WeekDay day, bool h) =
+            abi.borshDecode(buffer, (uint8, uint64, uint128, int16, int32, WeekDay, bool));
+
+            assert(a == 45);
+            assert(b == 9965956609890);
+            assert(c == 88);
+            assert(d == -29);
+            assert(e == -88);
+            assert(day == WeekDay.Wednesday);
+            assert(h == false);
+        }
+
+        function decodeTest2(bytes memory buffer) public pure {
+            (WeekDay a, WeekDay b, WeekDay c) =
+            abi.borshDecode(buffer, (WeekDay, WeekDay, WeekDay));
+            assert(a == WeekDay.Sunday);
+            assert(b == WeekDay.Saturday);
+            assert(c == WeekDay.Friday);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Res1 {
+        a: 45,
+        b: 9965956609890,
+        c: 88,
+        d: -29,
+        e: -88,
+        day: WeekDay::Wednesday,
+        h: false,
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("decodeTest1", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Res2 {
+        item_1: WeekDay::Sunday,
+        item_2: WeekDay::Saturday,
+        item_3: WeekDay::Friday,
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("decodeTest2", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn decode_address() {
+    #[derive(BorshSerialize, Debug)]
+    struct Data {
+        address: [u8; 32],
+        this: [u8; 32],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function testAddress(bytes memory buffer) public view {
+            (address a, Testing b) = abi.borshDecode(buffer, (address, Testing));
+
+            assert(a == address(this));
+            assert(b == this);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Data {
+        address: vm.programs[0].data,
+        this: vm.programs[0].data,
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testAddress", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn string_and_bytes() {
+    #[derive(BorshSerialize, Debug)]
+    struct Data {
+        a: String,
+        b: Vec<u8>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function testStringAndBytes(bytes memory buffer) public view {
+            (string memory a, bytes memory b) = abi.borshDecode(buffer, (string, bytes));
+
+            assert(a == "coffee");
+            assert(b == "tea");
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let data = Data {
+        a: "coffee".to_string(),
+        b: b"tea".to_vec(),
+    };
+    let encoded = data.try_to_vec().unwrap();
+    let _ = vm.function("testStringAndBytes", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn primitive_struct() {
+    #[derive(Debug, BorshSerialize)]
+    struct NoPadStruct {
+        a: u32,
+        b: u32,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct PaddedStruct {
+        a: u128,
+        b: u8,
+        c: [u8; 32],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct NoPadStruct {
+            uint32 a;
+            uint32 b;
+        }
+
+        struct PaddedStruct {
+            uint128 a;
+            uint8 b;
+            bytes32 c;
+        }
+
+        function testNoPadStruct(bytes memory buffer) public pure {
+            NoPadStruct memory str = abi.borshDecode(buffer, (NoPadStruct));
+            assert(str.a == 1238);
+            assert(str.b == 87123);
+        }
+
+        function testPaddedStruct(bytes memory buffer) public pure {
+            PaddedStruct memory str = abi.borshDecode(buffer, (PaddedStruct));
+            assert(str.a == 12998);
+            assert(str.b == 240);
+            assert(str.c == "tea_is_good");
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let data = NoPadStruct { a: 1238, b: 87123 };
+    let encoded = data.try_to_vec().unwrap();
+    let _ = vm.function("testNoPadStruct", &[Token::Bytes(encoded)], &[], None);
+
+    let mut elem = b"tea_is_good".to_vec();
+    elem.append(&mut vec![0; 21]);
+    let data = PaddedStruct {
+        a: 12998,
+        b: 240,
+        c: <[u8; 32]>::try_from(&elem[0..32]).unwrap(),
+    };
+    let encoded = data.try_to_vec().unwrap();
+    let _ = vm.function("testPaddedStruct", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn returned_string() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        rr: String,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+           function returnedString(bytes memory buffer) public pure returns (string memory) {
+                string memory s = abi.borshDecode(buffer, (string));
+                return s;
+           }
+    }
+        "#,
+    );
+    vm.constructor("Testing", &[]);
+    let data = Input {
+        rr: "cortado".to_string(),
+    };
+    let encoded = data.try_to_vec().unwrap();
+    let returns = vm.function("returnedString", &[Token::Bytes(encoded)], &[], None);
+    let string = returns[0].clone().into_string().unwrap();
+    assert_eq!(string, "cortado");
+}
+
+#[test]
+fn test_string_array() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        a: Vec<String>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function testStringVector(bytes memory buffer) public pure returns (string[] memory) {
+                string[] memory vec = abi.borshDecode(buffer, (string[]));
+                return vec;
+            }
+        }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let data = Input {
+        a: vec![
+            "coffee".to_string(),
+            "tea".to_string(),
+            "cappuccino".to_string(),
+        ],
+    };
+    let encoded = data.try_to_vec().unwrap();
+    let returns = vm.function("testStringVector", &[Token::Bytes(encoded)], &[], None);
+    let vec = returns[0].clone().into_array().unwrap();
+    assert_eq!(vec.len(), 3);
+    assert_eq!(vec[0].clone().into_string().unwrap(), "coffee");
+    assert_eq!(vec[1].clone().into_string().unwrap(), "tea");
+    assert_eq!(vec[2].clone().into_string().unwrap(), "cappuccino");
+}
+
+#[test]
+fn struct_within_struct() {
+    #[derive(Debug, BorshSerialize)]
+    struct NoPadStruct {
+        a: u32,
+        b: u32,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct PaddedStruct {
+        a: u128,
+        b: u8,
+        c: [u8; 32],
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct NonConstantStruct {
+        a: u64,
+        b: Vec<String>,
+        no_pad: NoPadStruct,
+        pad: PaddedStruct,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct noPadStruct {
+            uint32 a;
+            uint32 b;
+        }
+
+        struct PaddedStruct {
+            uint128 a;
+            uint8 b;
+            bytes32 c;
+        }
+
+        struct NonConstantStruct {
+            uint64 a;
+            string[] b;
+            noPadStruct noPad;
+            PaddedStruct pad;
+        }
+
+        function testStruct(bytes memory buffer) public pure {
+            NonConstantStruct memory str = abi.borshDecode(buffer, (NonConstantStruct));
+            assert(str.a == 890234);
+            assert(str.b.length == 2);
+            assert(str.b[0] == "tea");
+            assert(str.b[1] == "coffee");
+            assert(str.noPad.a == 89123);
+            assert(str.noPad.b == 12354);
+            assert(str.pad.a == 988834);
+            assert(str.pad.b == 129);
+            assert(str.pad.c == "tea_is_good");
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let no_pad = NoPadStruct { a: 89123, b: 12354 };
+    let mut tea_is_good = b"tea_is_good".to_vec();
+    tea_is_good.append(&mut vec![0; 21]);
+    let pad = PaddedStruct {
+        a: 988834,
+        b: 129,
+        c: <[u8; 32]>::try_from(tea_is_good).unwrap(),
+    };
+    let data = NonConstantStruct {
+        a: 890234,
+        b: vec!["tea".to_string(), "coffee".to_string()],
+        no_pad,
+        pad,
+    };
+    let encoded = data.try_to_vec().unwrap();
+    let _ = vm.function("testStruct", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn struct_in_array() {
+    #[derive(Debug, BorshSerialize)]
+    struct NoPadStruct {
+        a: u32,
+        b: u32,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct PaddedStruct {
+        a: u128,
+        b: u8,
+        c: [u8; 32],
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input1 {
+        item_1: NoPadStruct,
+        item_2: PaddedStruct,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input2 {
+        item_1: [i32; 4],
+        item_2: [NoPadStruct; 2],
+        item_3: Vec<NoPadStruct>,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input3 {
+        vec: Vec<NoPadStruct>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct NoPadStruct {
+            uint32 a;
+            uint32 b;
+        }
+
+        struct PaddedStruct {
+            uint128 a;
+            uint8 b;
+            bytes32 c;
+        }
+
+        function twoStructs(bytes memory buffer) public pure {
+            (NoPadStruct memory a, PaddedStruct memory b) = abi.borshDecode(buffer, (NoPadStruct, PaddedStruct));
+            assert(a.a == 945);
+            assert(a.b == 7453);
+            assert(b.a == 1);
+            assert(b.b == 3);
+            assert(b.c == "there_is_padding_here");
+        }
+
+        function fixedArrays(bytes memory buffer) public pure {
+            (int32[4] memory a, NoPadStruct[2] memory b, NoPadStruct[] memory c) =
+            abi.borshDecode(buffer, (int32[4], NoPadStruct[2], NoPadStruct[]));
+
+            assert(a[0] == 1);
+            assert(a[1] == -298);
+            assert(a[2] == 3);
+            assert(a[3] == -434);
+
+            assert(b[0].a == 1);
+            assert(b[0].b == 2);
+            assert(b[1].a == 3);
+            assert(b[1].b == 4);
+
+            assert(c.length == 3);
+            assert(c[0].a == 1623);
+            assert(c[0].b == 43279);
+            assert(c[1].a == 41234);
+            assert(c[1].b == 98375);
+            assert(c[2].a == 945);
+            assert(c[2].b == 7453);
+        }
+
+        function primitiveDynamic(bytes memory buffer) public pure {
+            NoPadStruct[] memory vec = abi.borshDecode(buffer, (NoPadStruct[]));
+
+            assert(vec.length == 2);
+            assert(vec[0].a == 5);
+            assert(vec[0].b == 6);
+            assert(vec[1].a == 7);
+            assert(vec[1].b == 8);
+        }
+
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let mut bytes_string = b"there_is_padding_here".to_vec();
+    bytes_string.append(&mut vec![0; 11]);
+    let input = Input1 {
+        item_1: NoPadStruct { a: 945, b: 7453 },
+        item_2: PaddedStruct {
+            a: 1,
+            b: 3,
+            c: <[u8; 32]>::try_from(bytes_string).unwrap(),
+        },
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("twoStructs", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input2 {
+        item_1: [1, -298, 3, -434],
+        item_2: [NoPadStruct { a: 1, b: 2 }, NoPadStruct { a: 3, b: 4 }],
+        item_3: vec![
+            NoPadStruct { a: 1623, b: 43279 },
+            NoPadStruct { a: 41234, b: 98375 },
+            NoPadStruct { a: 945, b: 7453 },
+        ],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("fixedArrays", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input3 {
+        vec: vec![NoPadStruct { a: 5, b: 6 }, NoPadStruct { a: 7, b: 8 }],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("primitiveDynamic", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn arrays() {
+    #[derive(Debug, BorshSerialize, Default, Clone)]
+    struct NonConstantStruct {
+        a: u64,
+        b: Vec<String>,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input1 {
+        complex_array: Vec<NonConstantStruct>,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input2 {
+        vec: Vec<i16>,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input3 {
+        multi_dim: [[i8; 2]; 3],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct NonConstantStruct {
+            uint64 a;
+            string[] b;
+        }
+
+        function decodeComplex(bytes memory buffer) public view {
+            NonConstantStruct[] memory vec = abi.borshDecode(buffer, (NonConstantStruct[]));
+
+            assert(vec.length == 2);
+
+            assert(vec[0].a == 897);
+            assert(vec[0].b[0] == "tea");
+            assert(vec[0].b[1] == "coffee");
+
+            assert(vec[1].a == 74123);
+            assert(vec[1].b[0] == "cortado");
+            assert(vec[1].b[1] == "cappuccino");
+        }
+
+        function dynamicArray(bytes memory buffer) public view {
+            int16[] memory vec = abi.borshDecode(buffer, (int16[]));
+
+            assert(vec.length == 3);
+
+            assert(vec[0] == -90);
+            assert(vec[1] == 5523);
+            assert(vec[2] == -89);
+        }
+
+        function decodeMultiDim(bytes memory buffer) public view {
+            int8[2][3] memory vec = abi.borshDecode(buffer, (int8[2][3]));
+
+            print("{}".format(vec[0][1]));
+            assert(vec[0][0] == 1);
+            assert(vec[0][1] == 2);
+            assert(vec[1][0] == 4);
+            assert(vec[1][1] == 5);
+            assert(vec[2][0] == 6);
+            assert(vec[2][1] == 7);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input1 {
+        complex_array: vec![
+            NonConstantStruct {
+                a: 897,
+                b: vec!["tea".to_string(), "coffee".to_string()],
+            },
+            NonConstantStruct {
+                a: 74123,
+                b: vec!["cortado".to_string(), "cappuccino".to_string()],
+            },
+        ],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("decodeComplex", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input2 {
+        vec: vec![-90, 5523, -89],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("dynamicArray", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input3 {
+        multi_dim: [[1, 2], [4, 5], [6, 7]],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("decodeMultiDim", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn multi_dimensional_arrays() {
+    #[derive(Debug, BorshSerialize)]
+    struct PaddedStruct {
+        a: u128,
+        b: u8,
+        c: [u8; 32],
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input1 {
+        item_1: Vec<[[PaddedStruct; 2]; 3]>,
+        item_2: i16,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input2 {
+        vec: Vec<[[u16; 4]; 2]>,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input3 {
+        vec: Vec<u16>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct PaddedStruct {
+            uint128 a;
+            uint8 b;
+            bytes32 c;
+        }
+
+        function multiDimStruct(bytes memory buffer) public pure {
+            (PaddedStruct[2][3][] memory vec, int16 g) = abi.borshDecode(buffer, (PaddedStruct[2][3][], int16));
+
+            assert(vec.length == 1);
+
+            assert(vec[0][0][0].a == 56);
+            assert(vec[0][0][0].b == 1);
+            assert(vec[0][0][0].c == "oi");
+
+            assert(vec[0][0][1].a == 78);
+            assert(vec[0][0][1].b == 6);
+            assert(vec[0][0][1].c == "bc");
+
+            assert(vec[0][1][0].a == 89);
+            assert(vec[0][1][0].b == 4);
+            assert(vec[0][1][0].c == "sn");
+
+            assert(vec[0][1][1].a == 42);
+            assert(vec[0][1][1].b == 56);
+            assert(vec[0][1][1].c == "cn");
+
+            assert(vec[0][2][0].a == 23);
+            assert(vec[0][2][0].b == 78);
+            assert(vec[0][2][0].c == "fr");
+
+            assert(vec[0][2][1].a == 445);
+            assert(vec[0][2][1].b == 46);
+            assert(vec[0][2][1].c == "br");
+
+            assert(g == -90);
+        }
+
+        function multiDimInt(bytes memory buffer) public pure {
+            uint16[4][2][] memory vec = abi.borshDecode(buffer, (uint16[4][2][]));
+
+            assert(vec.length == 2);
+
+            assert(vec[0][0][0] == 1);
+            assert(vec[0][0][1] == 2);
+            assert(vec[0][0][2] == 3);
+            assert(vec[0][0][3] == 4);
+
+            assert(vec[0][1][0] == 5);
+            assert(vec[0][1][1] == 6);
+            assert(vec[0][1][2] == 7);
+            assert(vec[0][1][3] == 8);
+
+            assert(vec[1][0][0] == 9);
+            assert(vec[1][0][1] == 10);
+            assert(vec[1][0][2] == 11);
+            assert(vec[1][0][3] == 12);
+
+            assert(vec[1][1][0] == 13);
+            assert(vec[1][1][1] == 14);
+            assert(vec[1][1][2] == 15);
+            assert(vec[1][1][3] == 16);
+        }
+
+        function uniqueDim(bytes memory buffer) public pure {
+            uint16[] memory vec = abi.borshDecode(buffer, (uint16[]));
+
+            assert(vec.length == 5);
+
+            assert(vec[0] == 9);
+            assert(vec[1] == 3);
+            assert(vec[2] == 4);
+            assert(vec[3] == 90);
+            assert(vec[4] == 834);
+        }
+    }
+        "#,
+    );
+    vm.constructor("Testing", &[]);
+    let mut response: Vec<u8> = vec![0; 32];
+
+    let input = Input1 {
+        item_1: vec![[
+            [
+                PaddedStruct {
+                    a: 56,
+                    b: 1,
+                    c: create_response(&mut response, b"oi"),
+                },
+                PaddedStruct {
+                    a: 78,
+                    b: 6,
+                    c: create_response(&mut response, b"bc"),
+                },
+            ],
+            [
+                PaddedStruct {
+                    a: 89,
+                    b: 4,
+                    c: create_response(&mut response, b"sn"),
+                },
+                PaddedStruct {
+                    a: 42,
+                    b: 56,
+                    c: create_response(&mut response, b"cn"),
+                },
+            ],
+            [
+                PaddedStruct {
+                    a: 23,
+                    b: 78,
+                    c: create_response(&mut response, b"fr"),
+                },
+                PaddedStruct {
+                    a: 445,
+                    b: 46,
+                    c: create_response(&mut response, b"br"),
+                },
+            ],
+        ]],
+        item_2: -90,
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("multiDimStruct", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input2 {
+        vec: vec![
+            [[1, 2, 3, 4], [5, 6, 7, 8]],
+            [[9, 10, 11, 12], [13, 14, 15, 16]],
+        ],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("multiDimInt", &[Token::Bytes(encoded)], &[], None);
+
+    let input = Input3 {
+        vec: vec![9, 3, 4, 90, 834],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("uniqueDim", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn empty_arrays() {
+    #[derive(Debug, BorshSerialize)]
+    struct S {
+        f1: i64,
+        f2: String,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        vec_1: Vec<S>,
+        vec_2: Vec<String>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct S {
+            int64 f1;
+            string f2;
+        }
+
+        function testEmpty(bytes memory buffer) public pure {
+            (S[] memory vec_1, string[] memory vec_2) = abi.borshDecode(buffer, (S[], string[]));
+
+            assert(vec_1.length == 0);
+            assert(vec_2.length == 0);
+        }
+    }
+        "#,
+    );
+    vm.constructor("Testing", &[]);
+
+    let input = Input {
+        vec_1: vec![],
+        vec_2: vec![],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testEmpty", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn external_function() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        selector: [u8; 4],
+        address: [u8; 32],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function testExternalFunction(bytes memory buffer) public view returns (bytes4, address) {
+            function (uint8) external returns (int8) fPtr = abi.borshDecode(buffer, (function (uint8) external returns (int8)));
+            return (fPtr.selector, fPtr.address);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input {
+        selector: [1, 2, 3, 4],
+        address: [
+            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+            24, 25, 26, 27, 28, 29, 30, 31,
+        ],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let returns = vm.function("testExternalFunction", &[Token::Bytes(encoded)], &[], None);
+
+    let selector = returns[0].clone().into_fixed_bytes().unwrap();
+    assert_eq!(selector, input.selector);
+
+    let address = returns[1].clone().into_fixed_bytes().unwrap();
+    assert_eq!(address, input.address);
+}
+
+#[test]
+fn bytes_arrays() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        item_1: [[u8; 4]; 2],
+        item_2: Vec<[u8; 5]>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function testByteArrays(bytes memory buffer) public view {
+                (bytes4[2] memory arr, bytes5[] memory vec) = abi.borshDecode(buffer, (bytes4[2], bytes5[]));
+
+                assert(arr[0] == "abcd");
+                assert(arr[1] == "efgh");
+
+                assert(vec.length == 2);
+                assert(vec[0] == "12345");
+                assert(vec[1] == "67890");
+            }
+        }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input {
+        item_1: [b"abcd".to_owned(), b"efgh".to_owned()],
+        item_2: vec![b"12345".to_owned(), b"67890".to_owned()],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testByteArrays", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn different_types() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input1 {
+        a: i32,
+        b: u64,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function testByteArrays(bytes memory buffer) public view {
+            (bytes4[2] memory arr, bytes5[] memory vec) = abi.borshDecode(buffer, (bytes4[2], bytes5[]));
+
+            assert(arr[0] == "abcd");
+            assert(arr[1] == "efgh");
+
+            assert(vec.length == 2);
+            assert(vec[0] == "12345");
+            assert(vec[1] == "67890");
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input1 { a: -789, b: 14234 };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testByteArrays", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn more_elements() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        vec: [i64; 4],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function wrongNumber(bytes memory buffer) public view {
+               int64[5] memory vec = abi.borshDecode(buffer, (int64[5]));
+
+               assert(vec[1] == 0);
+            }
+        }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+
+    let input = Input { vec: [1, 4, 5, 6] };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("wrongNumber", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn extra_element() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        vec: Vec<i64>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function extraElement(bytes memory buffer) public pure {
+               (int64[] memory vec, int32 g) = abi.borshDecode(buffer, (int64[], int32));
+
+               assert(vec[1] == 0);
+               assert(g == 3);
+            }
+        }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input {
+        vec: vec![-90, 89, -2341],
+    };
+
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("extraElement", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn invalid_type() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        item: u64,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function invalidType(bytes memory buffer) public pure {
+           int64[] memory vec = abi.borshDecode(buffer, (int64[]));
+
+           assert(vec[1] == 0);
+        }
+    }
+    "#,
+    );
+
+    vm.constructor("Testing", &[]);
+
+    let input = Input { item: 5 };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("invalidType", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn longer_buffer() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        item_1: u64,
+        item_2: u64,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        function testLongerBuffer(bytes memory buffer) public view {
+            uint64 a = abi.borshDecode(buffer, (uint64));
+
+            assert(a == 4);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+
+    let input = Input {
+        item_1: 4,
+        item_2: 5,
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testLongerBuffer", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn longer_buffer_array() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        item_1: u64,
+        item_2: [u32; 4],
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function testLongerBuffer(bytes memory buffer) public view {
+                (uint64 a, uint32[3] memory b) = abi.borshDecode(buffer, (uint64, uint32[3]));
+
+                assert(a == 4);
+                assert(b[0] == 1);
+                assert(b[1] == 2);
+                assert(b[2] == 3);
+            }
+        }        "#,
+    );
+    vm.constructor("Testing", &[]);
+
+    let input = Input {
+        item_1: 23434,
+        item_2: [1, 2, 3, 4],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testLongerBuffer", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn dynamic_array_of_array() {
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        vec: Vec<[i32; 2]>,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+        contract Testing {
+            function testArrayAssign(bytes memory buffer) public pure {
+                int32[2][] memory vec = abi.borshDecode(buffer, (int32[2][]));
+
+                assert(vec.length == 2);
+
+                assert(vec[0][0] == 0);
+                assert(vec[0][1] == 1);
+                assert(vec[1][0] == 2);
+                assert(vec[1][1] == -3);
+            }
+        }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let input = Input {
+        vec: vec![[0, 1], [2, -3]],
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("testArrayAssign", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+fn test_struct_validation() {
+    #[derive(Debug, BorshSerialize)]
+    struct MyStruct {
+        b: [u8; 32],
+        c: i8,
+        d: String,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        b: u128,
+        m_str: MyStruct,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct myStruct {
+            bytes32 b;
+            int8 c;
+            string d;
+        }
+
+
+        function test(bytes memory buffer) public pure {
+            (uint128 b, myStruct memory m_str) = abi.borshDecode(buffer, (uint128, myStruct));
+
+            assert(m_str.b == "struct");
+            assert(m_str.c == 1);
+            assert(m_str.d == "string");
+            assert(b == 3);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let mut bytes_string = b"struct".to_vec();
+    bytes_string.append(&mut vec![0; 26]);
+
+    let input = Input {
+        b: 3,
+        m_str: MyStruct {
+            b: <[u8; 32]>::try_from(bytes_string).unwrap(),
+            c: 1,
+            d: "string".to_string(),
+        },
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("test", &[Token::Bytes(encoded)], &[], None);
+}
+
+#[test]
+#[should_panic(expected = "unexpected return 0x100000000")]
+fn test_struct_validation_invalid() {
+    #[derive(Debug, BorshSerialize)]
+    struct MyStruct {
+        b: [u8; 32],
+        c: i8,
+        d: String,
+    }
+
+    #[derive(Debug, BorshSerialize)]
+    struct Input {
+        m_str: MyStruct,
+    }
+
+    let mut vm = build_solidity(
+        r#"
+    contract Testing {
+        struct myStruct {
+            bytes32 b;
+            int8 c;
+            string d;
+        }
+
+
+        function test(bytes memory buffer) public pure {
+            (uint128 b, myStruct memory m_str) = abi.borshDecode(buffer, (uint128, myStruct));
+
+            assert(m_str.b == "struct");
+            assert(m_str.c == 1);
+            assert(m_str.d == "string");
+            assert(b == 3);
+        }
+    }
+        "#,
+    );
+
+    vm.constructor("Testing", &[]);
+    let mut bytes_string = b"struct".to_vec();
+    bytes_string.append(&mut vec![0; 26]);
+
+    let input = Input {
+        m_str: MyStruct {
+            b: <[u8; 32]>::try_from(bytes_string).unwrap(),
+            c: 1,
+            d: "string".to_string(),
+        },
+    };
+    let encoded = input.try_to_vec().unwrap();
+    let _ = vm.function("test", &[Token::Bytes(encoded)], &[], None);
+}

+ 22 - 1
tests/solana_tests/abi_encode.rs

@@ -384,6 +384,11 @@ fn struct_in_array() {
         item_3: [NoPadStruct; 2],
     }
 
+    #[derive(Debug, BorshDeserialize)]
+    struct Res3 {
+        item_1: Vec<NoPadStruct>,
+    }
+
     let mut vm = build_solidity(
         r#"
     contract Testing {
@@ -425,6 +430,14 @@ fn struct_in_array() {
             bytes memory b1 = abi.encode(test_vec_1, mem_vec, str_vec);
             return b1;
         }
+
+        function primitiveDynamicArray() public view returns (bytes memory) {
+            noPadStruct[] memory str_vec = new noPadStruct[](2);
+            str_vec[0] = noPadStruct(5, 6);
+            str_vec[1] = noPadStruct(7, 8);
+            bytes memory b2 = abi.encode(str_vec);
+            return b2;
+        }
     }
         "#,
     );
@@ -453,6 +466,14 @@ fn struct_in_array() {
     assert_eq!(decoded.item_2, [1, -298, 3, -434]);
     assert_eq!(decoded.item_3[0], NoPadStruct { a: 1, b: 2 });
     assert_eq!(decoded.item_3[1], NoPadStruct { a: 3, b: 4 });
+
+    let returns = vm.function("primitiveDynamicArray", &[], &[], None);
+    let encoded = returns[0].clone().into_bytes().unwrap();
+    let decoded = Res3::try_from_slice(&encoded).unwrap();
+
+    assert_eq!(decoded.item_1.len(), 2);
+    assert_eq!(decoded.item_1[0], NoPadStruct { a: 5, b: 6 });
+    assert_eq!(decoded.item_1[1], NoPadStruct { a: 7, b: 8 });
 }
 
 #[test]
@@ -709,7 +730,7 @@ contract Testing {
     assert_eq!(decoded.item, vec![9, 3, 4, 90, 834]);
 }
 
-fn create_response(vec: &mut [u8], string: &[u8; 2]) -> [u8; 32] {
+pub(super) fn create_response(vec: &mut [u8], string: &[u8; 2]) -> [u8; 32] {
     vec[0] = string[0];
     vec[1] = string[1];
     <[u8; 32]>::try_from(vec.to_owned()).unwrap()

+ 1 - 0
tests/solana_tests/mod.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 mod abi;
+mod abi_decode;
 mod abi_encode;
 mod accessor;
 mod account_info;