Browse Source

Soroban: support mappings (#1807)

This PR brings:
- Support for `mappings` in Soroban. 
- Bug fix: support constructors in `codegen`. This will allow
constructors to be called with parameters

---------

Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
salaheldinsoliman 5 months ago
parent
commit
41b93d3072

+ 2 - 1
solang-parser/src/lib.rs

@@ -24,7 +24,8 @@ mod tests;
     clippy::type_complexity,
     clippy::ptr_arg,
     clippy::just_underscores_and_digits,
-    clippy::empty_line_after_outer_attr
+    clippy::empty_line_after_outer_attr,
+    clippy::large_enum_variant
 )]
 mod solidity {
     include!(concat!(env!("OUT_DIR"), "/solidity.rs"));

+ 42 - 23
src/codegen/dispatch/soroban.rs

@@ -27,27 +27,31 @@ pub fn function_dispatch(
     let mut wrapper_cfgs = Vec::new();
 
     for cfg in all_cfg.iter_mut() {
-        let function = match &cfg.function_no {
-            ASTFunction::SolidityFunction(no) => &ns.functions[*no],
-            _ => continue,
-        };
+        let wrapper_name = if cfg.public {
+            if cfg.name.contains("constructor") {
+                "__constructor".to_string()
+            } else {
+                let function = match &cfg.function_no {
+                    ASTFunction::SolidityFunction(no) => &ns.functions[*no],
+
+                    // untill this stage, we have processed constructor and storage_initializer, so all that is left is the solidity functions
+                    _ => unreachable!(),
+                };
 
-        let wrapper_name = {
-            if cfg.public {
                 if function.mangled_name_contracts.contains(&contract_no) {
                     function.mangled_name.clone()
                 } else {
                     function.id.name.clone()
                 }
-            } else {
-                continue;
             }
+        } else {
+            continue;
         };
 
         let mut wrapper_cfg = ControlFlowGraph::new(wrapper_name.to_string(), ASTFunction::None);
 
         let mut params = Vec::new();
-        for p in function.params.as_ref() {
+        for p in cfg.params.as_ref() {
             let type_ref = Type::Ref(Box::new(p.ty.clone()));
             let mut param = ast::Parameter::new_default(type_ref);
             param.id = p.id.clone();
@@ -55,7 +59,7 @@ pub fn function_dispatch(
         }
 
         let mut returns = Vec::new();
-        for ret in function.returns.as_ref() {
+        for ret in cfg.returns.as_ref() {
             let type_ref = Type::Ref(Box::new(ret.ty.clone()));
             let ret = ast::Parameter::new_default(type_ref);
             returns.push(ret);
@@ -77,7 +81,7 @@ pub fn function_dispatch(
         let mut return_tys = Vec::new();
 
         let mut call_returns = Vec::new();
-        for arg in function.returns.iter() {
+        for arg in cfg.returns.iter() {
             let new = vartab.temp_anonymous(&arg.ty);
             value.push(Expression::Variable {
                 loc: arg.loc,
@@ -88,21 +92,36 @@ pub fn function_dispatch(
             call_returns.push(new);
         }
 
-        let cfg_no = match cfg.function_no {
-            ASTFunction::SolidityFunction(no) => no,
-            _ => 0,
-        };
-
         let decoded = decode_args(&mut wrapper_cfg, &mut vartab);
 
-        let placeholder = Instr::Call {
-            res: call_returns,
-            call: InternalCallTy::Static { cfg_no },
-            return_tys,
-            args: decoded,
-        };
+        // call storage initializer if needed
+        if wrapper_cfg.name == "__constructor" {
+            let placeholder = Instr::Call {
+                res: vec![],
+                call: InternalCallTy::HostFunction {
+                    name: "storage_initializer".to_string(),
+                },
+                return_tys: vec![],
+                args: vec![],
+            };
+            wrapper_cfg.add(&mut vartab, placeholder);
+        }
 
-        wrapper_cfg.add(&mut vartab, placeholder);
+        if wrapper_cfg.name != "__constructor" {
+            let cfg_no = match cfg.function_no {
+                ASTFunction::SolidityFunction(no) => no,
+                _ => unreachable!(),
+            };
+
+            // add a call to the storage initializer
+            let placeholder = Instr::Call {
+                res: call_returns,
+                call: InternalCallTy::Static { cfg_no },
+                return_tys,
+                args: decoded,
+            };
+            wrapper_cfg.add(&mut vartab, placeholder);
+        }
 
         let ret = encode_return(value, ns, &mut vartab, &mut wrapper_cfg);
         wrapper_cfg.add(&mut vartab, Instr::Return { value: vec![ret] });

+ 93 - 80
src/codegen/encoding/soroban_encoding.rs

@@ -141,7 +141,7 @@ pub fn soroban_decode_arg(
             signed: false,
         },
 
-        Type::Address(_) => arg.clone(),
+        Type::Address(_) | Type::String => arg.clone(),
 
         Type::Int(128) | Type::Uint(128) => decode_i128(wrapper_cfg, vartab, arg),
         Type::Uint(32) => {
@@ -220,104 +220,117 @@ pub fn soroban_encode_arg(
             }
         }
         Type::String => {
-            let inp = Expression::VectorData {
-                pointer: Box::new(item.clone()),
-            };
-
-            let inp_extend = Expression::ZeroExt {
-                loc: Loc::Codegen,
-                ty: Type::Uint(64),
-                expr: Box::new(inp),
-            };
-
-            let encoded = Expression::ShiftLeft {
-                loc: Loc::Codegen,
-                ty: Uint(64),
-                left: Box::new(inp_extend),
-                right: Box::new(Expression::NumberLiteral {
+            if let Expression::Variable {
+                loc: _,
+                ty: _,
+                var_no: _,
+            } = item.clone()
+            {
+                Instr::Set {
                     loc: Loc::Codegen,
-                    ty: Type::Uint(64),
-                    value: BigInt::from(32),
-                }),
-            };
+                    res: obj,
+                    expr: item.clone(),
+                }
+            } else {
+                let inp = Expression::VectorData {
+                    pointer: Box::new(item.clone()),
+                };
 
-            let encoded = Expression::Add {
-                loc: Loc::Codegen,
-                ty: Type::Uint(64),
-                overflowing: true,
-                left: Box::new(encoded),
-                right: Box::new(Expression::NumberLiteral {
+                let inp_extend = Expression::ZeroExt {
                     loc: Loc::Codegen,
                     ty: Type::Uint(64),
-                    value: BigInt::from(4),
-                }),
-            };
+                    expr: Box::new(inp),
+                };
 
-            let len = match item {
-                Expression::AllocDynamicBytes { size, .. } => {
-                    let sesa = Expression::ShiftLeft {
+                let encoded = Expression::ShiftLeft {
+                    loc: Loc::Codegen,
+                    ty: Uint(64),
+                    left: Box::new(inp_extend),
+                    right: Box::new(Expression::NumberLiteral {
                         loc: Loc::Codegen,
-                        ty: Uint(64),
-                        left: Box::new(size.clone().cast(&Type::Uint(64), ns)),
-                        right: Box::new(Expression::NumberLiteral {
-                            loc: Loc::Codegen,
-                            ty: Type::Uint(64),
-                            value: BigInt::from(32),
-                        }),
-                    };
+                        ty: Type::Uint(64),
+                        value: BigInt::from(32),
+                    }),
+                };
 
-                    Expression::Add {
+                let encoded = Expression::Add {
+                    loc: Loc::Codegen,
+                    ty: Type::Uint(64),
+                    overflowing: true,
+                    left: Box::new(encoded),
+                    right: Box::new(Expression::NumberLiteral {
                         loc: Loc::Codegen,
                         ty: Type::Uint(64),
-                        overflowing: true,
-                        left: Box::new(sesa),
-                        right: Box::new(Expression::NumberLiteral {
+                        value: BigInt::from(4),
+                    }),
+                };
+
+                let len = match item.clone() {
+                    Expression::AllocDynamicBytes { size, .. } => {
+                        let sesa = Expression::ShiftLeft {
+                            loc: Loc::Codegen,
+                            ty: Uint(64),
+                            left: Box::new(size.clone().cast(&Type::Uint(64), ns)),
+                            right: Box::new(Expression::NumberLiteral {
+                                loc: Loc::Codegen,
+                                ty: Type::Uint(64),
+                                value: BigInt::from(32),
+                            }),
+                        };
+
+                        Expression::Add {
                             loc: Loc::Codegen,
                             ty: Type::Uint(64),
-                            value: BigInt::from(4),
-                        }),
+                            overflowing: true,
+                            left: Box::new(sesa),
+                            right: Box::new(Expression::NumberLiteral {
+                                loc: Loc::Codegen,
+                                ty: Type::Uint(64),
+                                value: BigInt::from(4),
+                            }),
+                        }
                     }
-                }
-                Expression::BytesLiteral { loc, ty: _, value } => {
-                    let len = Expression::NumberLiteral {
-                        loc,
-                        ty: Type::Uint(64),
-                        value: BigInt::from(value.len() as u64),
-                    };
-
-                    let len = Expression::ShiftLeft {
-                        loc,
-                        ty: Type::Uint(64),
-                        left: Box::new(len),
-                        right: Box::new(Expression::NumberLiteral {
+                    Expression::BytesLiteral { loc, ty: _, value } => {
+                        let len = Expression::NumberLiteral {
                             loc,
                             ty: Type::Uint(64),
-                            value: BigInt::from(32),
-                        }),
-                    };
+                            value: BigInt::from(value.len() as u64),
+                        };
 
-                    Expression::Add {
-                        loc,
-                        ty: Type::Uint(64),
-                        left: Box::new(len),
-                        right: Box::new(Expression::NumberLiteral {
+                        let len = Expression::ShiftLeft {
                             loc,
                             ty: Type::Uint(64),
-                            value: BigInt::from(4),
-                        }),
-                        overflowing: false,
+                            left: Box::new(len),
+                            right: Box::new(Expression::NumberLiteral {
+                                loc,
+                                ty: Type::Uint(64),
+                                value: BigInt::from(32),
+                            }),
+                        };
+
+                        Expression::Add {
+                            loc,
+                            ty: Type::Uint(64),
+                            left: Box::new(len),
+                            right: Box::new(Expression::NumberLiteral {
+                                loc,
+                                ty: Type::Uint(64),
+                                value: BigInt::from(4),
+                            }),
+                            overflowing: false,
+                        }
                     }
-                }
-                _ => unreachable!(),
-            };
+                    _ => unreachable!(),
+                };
 
-            Instr::Call {
-                res: vec![obj],
-                return_tys: vec![Type::Uint(64)],
-                call: crate::codegen::cfg::InternalCallTy::HostFunction {
-                    name: HostFunctions::SymbolNewFromLinearMemory.name().to_string(),
-                },
-                args: vec![encoded, len],
+                Instr::Call {
+                    res: vec![obj],
+                    return_tys: vec![Type::Uint(64)],
+                    call: crate::codegen::cfg::InternalCallTy::HostFunction {
+                        name: HostFunctions::SymbolNewFromLinearMemory.name().to_string(),
+                    },
+                    args: vec![encoded, len],
+                }
             }
         }
         Type::Uint(32) | Type::Int(32) => {

+ 5 - 6
src/codegen/expression.rs

@@ -3784,20 +3784,19 @@ fn array_subscript(
         let array = expression(array, cfg, contract_no, func, ns, vartab, opt);
         let index = expression(index, cfg, contract_no, func, ns, vartab, opt);
 
-        return if ns.target == Target::Solana {
-            Expression::Subscript {
+        return match ns.target {
+            Target::Solana | Target::Soroban | Target::EVM => Expression::Subscript {
                 loc: *loc,
                 ty: elem_ty.clone(),
                 array_ty: array_ty.clone(),
                 expr: Box::new(array),
                 index: Box::new(index),
-            }
-        } else {
-            Expression::Keccak256 {
+            },
+            Target::Polkadot { .. } => Expression::Keccak256 {
                 loc: *loc,
                 ty: array_ty.clone(),
                 exprs: vec![array, index],
-            }
+            },
         };
     }
 

+ 1 - 0
src/codegen/mod.rs

@@ -1837,6 +1837,7 @@ pub enum Builtin {
     AuthAsCurrContract,
     ExtendTtl,
     ExtendInstanceTtl,
+    AccessMapping,
 }
 
 impl From<&ast::Builtin> for Builtin {

+ 42 - 6
src/codegen/revert.rs

@@ -187,7 +187,7 @@ pub(super) fn assert_failure(
     vartab: &mut Vartable,
 ) {
     // On Solana, returning the encoded arguments has no effect
-    if ns.target == Target::Solana {
+    if ns.target == Target::Solana || ns.target == Target::Soroban {
         cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
         return;
     }
@@ -258,7 +258,7 @@ pub(super) fn require(
         .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt));
 
     // On Solana and Polkadot, print the reason
-    if opt.log_runtime_errors && (ns.target == Target::Solana || ns.target.is_polkadot()) {
+    if opt.log_runtime_errors {
         if let Some(expr) = expr.clone() {
             let prefix = b"runtime_error: ";
             let error_string = format!(
@@ -287,7 +287,25 @@ pub(super) fn require(
                     ),
                 ],
             };
-            cfg.add(vartab, Instr::Print { expr: print_expr });
+
+            let expr_string = prefix
+                .to_vec()
+                .iter()
+                .chain(error_string.as_bytes())
+                .copied()
+                .collect::<Vec<u8>>();
+
+            let to_print = if ns.target == Target::Soroban {
+                Expression::BytesLiteral {
+                    loc: Codegen,
+                    ty: Type::String,
+                    value: expr_string.to_vec(),
+                }
+            } else {
+                print_expr
+            };
+
+            cfg.add(vartab, Instr::Print { expr: to_print });
         } else {
             log_runtime_error(
                 opt.log_runtime_errors,
@@ -324,16 +342,16 @@ pub(super) fn revert(
         .iter()
         .map(|s| expression(s, cfg, contract_no, func, ns, vartab, opt))
         .collect::<Vec<_>>();
-
     if opt.log_runtime_errors {
         match (error_no, exprs.first()) {
             // In the case of Error(string), we can print the reason
             (None, Some(expr)) => {
-                let prefix = b"runtime_error: ";
+                let prefix: &[u8; 15] = b"runtime_error: ";
                 let error_string = format!(
                     " revert encountered in {},\n",
                     ns.loc_to_string(PathDisplay::Filename, loc)
                 );
+
                 let print_expr = Expression::FormatString {
                     loc: Codegen,
                     args: vec![
@@ -356,7 +374,25 @@ pub(super) fn revert(
                         ),
                     ],
                 };
-                cfg.add(vartab, Instr::Print { expr: print_expr });
+
+                let expr_string = prefix
+                    .to_vec()
+                    .iter()
+                    .chain(error_string.as_bytes())
+                    .copied()
+                    .collect::<Vec<u8>>();
+
+                let to_print = Expression::BytesLiteral {
+                    loc: Codegen,
+                    ty: Type::String,
+                    value: expr_string.to_vec(),
+                };
+
+                if ns.target == Target::Soroban {
+                    cfg.add(vartab, Instr::Print { expr: to_print });
+                } else {
+                    cfg.add(vartab, Instr::Print { expr: print_expr });
+                }
             }
             // Else: Not all fields might be formattable, just print the error type
             _ => {

+ 7 - 2
src/emit/binary.rs

@@ -955,7 +955,11 @@ impl<'a> Binary<'a> {
                 }
                 Type::Enum(n) => self.llvm_type(&self.ns.enums[*n].ty),
                 Type::String | Type::DynamicBytes => {
-                    self.module.get_struct_type("struct.vector").unwrap().into()
+                    if self.ns.target == Target::Soroban {
+                        BasicTypeEnum::IntType(self.context.i64_type())
+                    } else {
+                        self.module.get_struct_type("struct.vector").unwrap().into()
+                    }
                 }
                 Type::Array(base_ty, dims) => {
                     dims.iter()
@@ -1088,7 +1092,8 @@ impl<'a> Binary<'a> {
                 // Return the constructed struct value
                 return struct_value.into();
             } else if matches!(ty, Type::String) {
-                let bs = init.as_ref().unwrap();
+                let default = " ".as_bytes().to_vec();
+                let bs = init.unwrap_or(&default);
 
                 let data = self.emit_global_string("const_string", bs, true);
 

+ 4 - 2
src/emit/expression.rs

@@ -12,7 +12,9 @@ use crate::sema::ast::{ArrayLength, RetrieveType, StructType, Type};
 use crate::Target;
 use inkwell::module::Linkage;
 use inkwell::types::{BasicType, StringRadix};
-use inkwell::values::{ArrayValue, BasicValueEnum, FunctionValue, IntValue, PointerValue};
+use inkwell::values::{
+    ArrayValue, BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue,
+};
 use inkwell::{AddressSpace, IntPredicate};
 use num_bigint::Sign;
 use num_traits::ToPrimitive;
@@ -1359,7 +1361,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
 
                 target
                     .storage_subscript(bin, function, ty, array, index)
-                    .into()
+                    .as_basic_value_enum()
             } else if elem_ty.is_builtin_struct() == Some(StructType::AccountInfo) {
                 let array = expression(target, bin, a, vartab, function).into_pointer_value();
                 let index = expression(target, bin, index, vartab, function).into_int_value();

+ 3 - 43
src/emit/soroban/mod.rs

@@ -1,10 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 pub(super) mod target;
-use crate::codegen::{
-    cfg::{ASTFunction, ControlFlowGraph},
-    HostFunctions, Options, STORAGE_INITIALIZER,
-};
+use crate::codegen::{cfg::ControlFlowGraph, HostFunctions, Options};
 
 use crate::emit::cfg::emit_cfg;
 use crate::{emit::Binary, sema::ast};
@@ -18,8 +15,6 @@ use soroban_sdk::xdr::{
     Limited, Limits, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, ScSpecEntry,
     ScSpecFunctionInputV0, ScSpecFunctionV0, ScSpecTypeDef, StringM, WriteXdr,
 };
-use std::ffi::CString;
-use std::sync;
 
 const SOROBAN_ENV_INTERFACE_VERSION: ScEnvMetaEntryInterfaceVersion =
     ScEnvMetaEntryInterfaceVersion {
@@ -141,7 +136,7 @@ impl SorobanTarget {
         Self::emit_functions_with_spec(contract, &mut bin, context, contract_no, &mut export_list);
         bin.internalize(export_list.as_slice());
 
-        Self::emit_initializer(&mut bin, ns);
+        //Self::emit_initializer(&mut binary, ns, contract.constructors(ns).first());
 
         Self::emit_env_meta_entries(context, &mut bin, opt);
 
@@ -258,7 +253,7 @@ impl SorobanTarget {
 
                             match ty {
                                 ast::Type::Uint(32) => ScSpecTypeDef::U32,
-                                &ast::Type::Int(32) => ScSpecTypeDef::I32,
+                                ast::Type::Int(32) => ScSpecTypeDef::I32,
                                 ast::Type::Uint(64) => ScSpecTypeDef::U64,
                                 &ast::Type::Int(64) => ScSpecTypeDef::I64,
                                 ast::Type::Int(128) => ScSpecTypeDef::I128,
@@ -374,39 +369,4 @@ impl SorobanTarget {
             );
         }
     }
-
-    fn emit_initializer(bin: &mut Binary, _ns: &ast::Namespace) {
-        let mut cfg = ControlFlowGraph::new("__constructor".to_string(), ASTFunction::None);
-
-        cfg.public = true;
-        let void_param = ast::Parameter::new_default(ast::Type::Void);
-        cfg.returns = sync::Arc::new(vec![void_param]);
-
-        Self::emit_function_spec_entry(bin.context, &cfg, "__constructor".to_string(), bin);
-
-        let function_name = CString::new(STORAGE_INITIALIZER).unwrap();
-        let mut storage_initializers = bin
-            .functions
-            .values()
-            .filter(|f: &&inkwell::values::FunctionValue| f.get_name() == function_name.as_c_str());
-        let storage_initializer = *storage_initializers
-            .next()
-            .expect("storage initializer is always present");
-        assert!(storage_initializers.next().is_none());
-
-        let void_type = bin.context.i64_type().fn_type(&[], false);
-        let constructor =
-            bin.module
-                .add_function("__constructor", void_type, Some(Linkage::External));
-        let entry = bin.context.append_basic_block(constructor, "entry");
-
-        bin.builder.position_at_end(entry);
-        bin.builder
-            .build_call(storage_initializer, &[], "storage_initializer")
-            .unwrap();
-
-        // return zero
-        let zero_val = bin.context.i64_type().const_int(2, false);
-        bin.builder.build_return(Some(&zero_val)).unwrap();
-    }
 }

+ 71 - 9
src/emit/soroban/target.rs

@@ -47,13 +47,19 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
     ) -> BasicValueEnum<'a> {
         let storage_type = storage_type_to_int(storage_type);
         emit_context!(bin);
+
+        let slot = if slot.is_const() {
+            slot.as_basic_value_enum()
+                .into_int_value()
+                .const_cast(bin.context.i64_type(), false)
+        } else {
+            *slot
+        };
+
         let ret = call!(
             HostFunctions::GetContractData.name(),
             &[
-                slot.as_basic_value_enum()
-                    .into_int_value()
-                    .const_cast(bin.context.i64_type(), false)
-                    .into(),
+                slot.into(),
                 bin.context.i64_type().const_int(storage_type, false).into(),
             ]
         )
@@ -84,16 +90,20 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
             .module
             .get_function(HostFunctions::PutContractData.name())
             .unwrap();
+        let slot = if slot.is_const() {
+            slot.as_basic_value_enum()
+                .into_int_value()
+                .const_cast(bin.context.i64_type(), false)
+        } else {
+            *slot
+        };
 
         let value = bin
             .builder
             .build_call(
                 function_value,
                 &[
-                    slot.as_basic_value_enum()
-                        .into_int_value()
-                        .const_cast(bin.context.i64_type(), false)
-                        .into(),
+                    slot.into(),
                     dest.into(),
                     bin.context.i64_type().const_int(storage_type, false).into(),
                 ],
@@ -188,7 +198,59 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
         slot: IntValue<'a>,
         index: BasicValueEnum<'a>,
     ) -> IntValue<'a> {
-        unimplemented!()
+        let vec_new = bin
+            .builder
+            .build_call(
+                bin.module
+                    .get_function(HostFunctions::VectorNew.name())
+                    .unwrap(),
+                &[],
+                "vec_new",
+            )
+            .unwrap()
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+
+        let slot = if slot.is_const() {
+            slot.as_basic_value_enum()
+                .into_int_value()
+                .const_cast(bin.context.i64_type(), false)
+        } else {
+            slot
+        };
+
+        // push the slot to the vector
+        bin.builder
+            .build_call(
+                bin.module
+                    .get_function(HostFunctions::VecPushBack.name())
+                    .unwrap(),
+                &[vec_new.as_basic_value_enum().into(), slot.into()],
+                "push",
+            )
+            .unwrap()
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+
+        // push the index to the vector
+        bin.builder
+            .build_call(
+                bin.module
+                    .get_function(HostFunctions::VecPushBack.name())
+                    .unwrap(),
+                &[vec_new.as_basic_value_enum().into(), index.into()],
+                "push",
+            )
+            .unwrap()
+            .try_as_basic_value()
+            .left()
+            .unwrap()
+            .into_int_value();
+        vec_new
     }
 
     fn storage_push(

+ 1 - 1
tests/soroban.rs

@@ -97,7 +97,7 @@ impl SorobanEnv {
         for arg in args {
             args_soroban.push_back(arg)
         }
-        println!("args_soroban: {:?}", args_soroban);
+        println!("args: {:?}", args_soroban);
         // To avoid running out of fuel
         self.env.cost_estimate().budget().reset_unlimited();
         self.env.invoke_contract(addr, &func, args_soroban)

+ 60 - 0
tests/soroban_testcases/mappings.rs

@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::build_solidity;
+use soroban_sdk::{testutils::Address as _, Address, IntoVal, Val};
+
+#[test]
+fn balance_and_allowance_test() {
+    let runtime = build_solidity(
+        r#"
+        contract mapper {
+            mapping(address => uint64) public balances;
+            mapping(address => mapping(address => uint64)) public allowances;
+
+            function setBalance(address addr, uint64 amount) public {
+                balances[addr] = amount;
+            }
+
+            function getBalance(address addr) public view returns (uint64) {
+                return balances[addr];
+            }
+
+            function setAllowance(address owner, address spender, uint64 amount) public {
+                allowances[owner][spender] = amount;
+            }
+
+            function getAllowance(address owner, address spender) public view returns (uint64) {
+                return allowances[owner][spender];
+            }
+        }
+        "#,
+        |_| {},
+    );
+
+    let addr = runtime.contracts.last().unwrap();
+
+    let user1 = Address::generate(&runtime.env);
+    let user2 = Address::generate(&runtime.env);
+
+    let bal: Val = 100_u64.into_val(&runtime.env);
+    let get_args = vec![user1.clone().into_val(&runtime.env)];
+
+    // Set and get balance
+    let set_args = vec![user1.clone().into_val(&runtime.env), bal];
+    runtime.invoke_contract(addr, "setBalance", set_args);
+    let res = runtime.invoke_contract(addr, "getBalance", get_args.clone());
+    assert!(bal.shallow_eq(&res));
+
+    // Set and get allowance
+    let allowance_val: Val = 77_u64.into_val(&runtime.env);
+    let set_allow_args = vec![
+        user1.clone().into_val(&runtime.env),
+        user2.clone().into_val(&runtime.env),
+        allowance_val,
+    ];
+    runtime.invoke_contract(addr, "setAllowance", set_allow_args);
+
+    let get_allow_args = vec![user1.into_val(&runtime.env), user2.into_val(&runtime.env)];
+    let res = runtime.invoke_contract(addr, "getAllowance", get_allow_args);
+    assert!(allowance_val.shallow_eq(&res));
+}

+ 1 - 0
tests/soroban_testcases/mod.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 mod auth;
 mod cross_contract_calls;
+mod mappings;
 mod math;
 mod print;
 mod storage;