Browse Source

Support different Soroban storage types (#1664)

salaheldinsoliman 11 months ago
parent
commit
2536c08036
41 changed files with 925 additions and 49 deletions
  1. 6 1
      fmt/src/formatter.rs
  2. 12 0
      fmt/src/solang_ext/ast_eq.rs
  3. 2 2
      integration/polkadot/try_catch.sol
  4. 0 1
      integration/soroban/.gitignore
  5. 2 2
      integration/soroban/package.json
  6. 48 0
      integration/soroban/runtime_error.spec.js
  7. 21 0
      integration/soroban/storage_types.sol
  8. 84 0
      integration/soroban/storage_types.spec.js
  9. 6 1
      solang-parser/src/helpers/fmt.rs
  10. 9 0
      solang-parser/src/helpers/loc.rs
  11. 11 0
      solang-parser/src/lexer.rs
  12. 18 0
      solang-parser/src/pt.rs
  13. 10 0
      solang-parser/src/solidity.lalrpop
  14. 2 2
      solang-parser/src/tests.rs
  15. 4 2
      src/codegen/cfg.rs
  16. 14 2
      src/codegen/constant_folding.rs
  17. 3 1
      src/codegen/dead_storage.rs
  18. 1 0
      src/codegen/dispatch/solana.rs
  19. 8 3
      src/codegen/dispatch/soroban.rs
  20. 1 1
      src/codegen/encoding/mod.rs
  21. 50 5
      src/codegen/expression.rs
  22. 1 0
      src/codegen/mod.rs
  23. 2 0
      src/codegen/solana_deploy.rs
  24. 1 0
      src/codegen/statements/mod.rs
  25. 8 3
      src/codegen/storage.rs
  26. 14 2
      src/codegen/subexpression_elimination/instruction.rs
  27. 2 0
      src/codegen/yul/tests/expression.rs
  28. 8 0
      src/emit/binary.rs
  29. 14 4
      src/emit/instructions.rs
  30. 3 1
      src/emit/mod.rs
  31. 3 1
      src/emit/polkadot/target.rs
  32. 10 4
      src/emit/solana/target.rs
  33. 29 3
      src/emit/soroban/target.rs
  34. 1 0
      src/sema/ast.rs
  35. 36 0
      src/sema/variables.rs
  36. 6 0
      src/sema/yul/tests/expression.rs
  37. 151 0
      test_snapshots/soroban_testcases/storage/counter.1.json
  38. 224 0
      test_snapshots/soroban_testcases/storage/different_storage_types.1.json
  39. 7 7
      tests/codegen_testcases/solidity/common_subexpression_elimination.sol
  40. 9 1
      tests/soroban.rs
  41. 84 0
      tests/soroban_testcases/storage.rs

+ 6 - 1
fmt/src/formatter.rs

@@ -20,7 +20,7 @@ use crate::{
 };
 use alloy_primitives::Address;
 use itertools::{Either, Itertools};
-use solang_parser::pt::{PragmaDirective, VersionComparator};
+use solang_parser::pt::{PragmaDirective, StorageType, VersionComparator};
 use std::{fmt::Write, str::FromStr};
 use thiserror::Error;
 
@@ -3333,6 +3333,11 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> {
                 self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?;
                 None
             }
+            VariableAttribute::StorageType(s) => match s {
+                StorageType::Instance(_) => Some("instance".to_string()),
+                StorageType::Temporary(_) => Some("temporary".to_string()),
+                StorageType::Persistent(_) => Some("persistent".to_string()),
+            },
         };
         if let Some(token) = token {
             let loc = attribute.loc();

+ 12 - 0
fmt/src/solang_ext/ast_eq.rs

@@ -75,6 +75,17 @@ impl AstEq for VariableDefinition {
     }
 }
 
+impl AstEq for StorageType {
+    fn ast_eq(&self, other: &Self) -> bool {
+        matches!(
+            (self, other),
+            (StorageType::Instance(_), StorageType::Instance(_))
+                | (StorageType::Persistent(_), StorageType::Persistent(_))
+                | (StorageType::Temporary(_), StorageType::Temporary(_))
+        )
+    }
+}
+
 impl AstEq for FunctionDefinition {
     fn ast_eq(&self, other: &Self) -> bool {
         // attributes
@@ -726,5 +737,6 @@ derive_ast_eq! { enum VariableAttribute {
     Constant(loc),
     Immutable(loc),
     Override(loc, idents),
+    StorageType(s)
     _
 }}

+ 2 - 2
integration/polkadot/try_catch.sol

@@ -2,9 +2,9 @@ contract TryCatchCaller {
     constructor() payable {}
 
     function test(uint128 div) public payable returns (uint128) {
-        TryCatchCallee instance = new TryCatchCallee();
+        TryCatchCallee contract_instance = new TryCatchCallee();
 
-        try instance.test(div) returns (uint128) {
+        try contract_instance.test(div) returns (uint128) {
             return 4;
         } catch Error(string reason) {
             assert(reason == "foo");

+ 0 - 1
integration/soroban/.gitignore

@@ -1,4 +1,3 @@
-*.js
 *.so
 *.key
 *.json

+ 2 - 2
integration/soroban/package.json

@@ -7,9 +7,9 @@
       "mocha": "^10.4.0"
     },
     "scripts": {
-      "build": "solang compile *.sol --target soroban",
+      "build": "solang compile *.sol --target soroban && solang compile storage_types.sol --target soroban --release",
       "setup": "node setup.js",
-      "test": "mocha *.spec.js --timeout 20000"
+      "test": "mocha *.spec.js --timeout 100000"
     },
     "devDependencies": {
       "@eslint/js": "^9.4.0",

+ 48 - 0
integration/soroban/runtime_error.spec.js

@@ -0,0 +1,48 @@
+import * as StellarSdk from '@stellar/stellar-sdk';
+import { readFileSync } from 'fs';
+import { expect } from 'chai';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { call_contract_function } from './test_helpers.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const dirname = path.dirname(__filename);
+
+describe('Runtime Error', () => {
+  let keypair;
+  const server = new StellarSdk.SorobanRpc.Server(
+    "https://soroban-testnet.stellar.org:443",
+  );
+
+  let contractAddr;
+  let contract;
+  before(async () => {
+
+    console.log('Setting up runtime_error.sol contract tests...');
+
+    // read secret from file
+    const secret = readFileSync('alice.txt', 'utf8').trim();
+    keypair = StellarSdk.Keypair.fromSecret(secret);
+
+    let contractIdFile = path.join(dirname, '.soroban', 'contract-ids', 'Error.txt');
+    // read contract address from file
+    contractAddr = readFileSync(contractIdFile, 'utf8').trim().toString();
+
+    // load contract
+    contract = new StellarSdk.Contract(contractAddr);
+
+    // call decrement once. The second call however will result in a runtime error
+    await call_contract_function("decrement", server, keypair, contract);
+  });
+
+  it('get correct initial counter', async () => {
+
+    // decrement the counter again, resulting in a runtime error
+    let res = await call_contract_function("decrement", server, keypair, contract);
+
+    expect(res).to.contain('runtime_error: math overflow in runtime_error.sol:6:9-19');
+  });
+
+});
+
+

+ 21 - 0
integration/soroban/storage_types.sol

@@ -0,0 +1,21 @@
+contract storage_types {
+            
+    uint64 public temporary sesa = 1;
+    uint64 public instance sesa1 = 1;
+    uint64 public persistent sesa2 = 2;
+    uint64 public sesa3 = 2;
+
+    function inc() public {
+        sesa++;
+        sesa1++;
+        sesa2++;
+        sesa3++;
+    }
+
+    function dec() public {
+        sesa--;
+        sesa1--;
+        sesa2--;
+        sesa3--;
+    }
+}

+ 84 - 0
integration/soroban/storage_types.spec.js

@@ -0,0 +1,84 @@
+import * as StellarSdk from '@stellar/stellar-sdk';
+import { readFileSync } from 'fs';
+import { expect } from 'chai';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { call_contract_function } from './test_helpers.js';  // Helper to interact with the contract
+
+const __filename = fileURLToPath(import.meta.url);
+const dirname = path.dirname(__filename);
+
+describe('StorageTypes', () => {
+  let keypair;
+  const server = new StellarSdk.SorobanRpc.Server(
+    "https://soroban-testnet.stellar.org:443",
+  );
+
+  let contractAddr;
+  let contract;
+  before(async () => {
+    console.log('Setting up storage_types contract tests...');
+
+    // Read secret from file
+    const secret = readFileSync('alice.txt', 'utf8').trim();
+    keypair = StellarSdk.Keypair.fromSecret(secret);
+
+    let contractIdFile = path.join(dirname, '.soroban', 'contract-ids', 'storage_types.txt');
+    // Read contract address from file
+    contractAddr = readFileSync(contractIdFile, 'utf8').trim().toString();
+
+    // Load contract
+    contract = new StellarSdk.Contract(contractAddr);
+  });
+
+  it('check initial values', async () => {
+    // Check initial values of all storage variables
+    let sesa = await call_contract_function("sesa", server, keypair, contract);
+    expect(sesa.toString()).eq("1");
+
+    let sesa1 = await call_contract_function("sesa1", server, keypair, contract);
+    expect(sesa1.toString()).eq("1");
+
+    let sesa2 = await call_contract_function("sesa2", server, keypair, contract);
+    expect(sesa2.toString()).eq("2");
+
+    let sesa3 = await call_contract_function("sesa3", server, keypair, contract);
+    expect(sesa3.toString()).eq("2");
+  });
+
+  it('increment values', async () => {
+    // Increment all values by calling the inc function
+    await call_contract_function("inc", server, keypair, contract);
+
+    // Check the incremented values
+    let sesa = await call_contract_function("sesa", server, keypair, contract);
+    expect(sesa.toString()).eq("2");
+
+    let sesa1 = await call_contract_function("sesa1", server, keypair, contract);
+    expect(sesa1.toString()).eq("2");
+
+    let sesa2 = await call_contract_function("sesa2", server, keypair, contract);
+    expect(sesa2.toString()).eq("3");
+
+    let sesa3 = await call_contract_function("sesa3", server, keypair, contract);
+    expect(sesa3.toString()).eq("3");
+  });
+
+  it('decrement values', async () => {
+    // Decrement all values by calling the dec function
+    await call_contract_function("dec", server, keypair, contract);
+
+    // Check the decremented values
+    let sesa = await call_contract_function("sesa", server, keypair, contract);
+    expect(sesa.toString()).eq("1");
+
+    let sesa1 = await call_contract_function("sesa1", server, keypair, contract);
+    expect(sesa1.toString()).eq("1");
+
+    let sesa2 = await call_contract_function("sesa2", server, keypair, contract);
+    expect(sesa2.toString()).eq("2");
+
+    let sesa3 = await call_contract_function("sesa3", server, keypair, contract);
+    expect(sesa3.toString()).eq("2");
+  });
+});

+ 6 - 1
solang-parser/src/helpers/fmt.rs

@@ -4,7 +4,7 @@
 //!
 //! [ref]: https://docs.soliditylang.org/en/latest/style-guide.html
 
-use crate::pt;
+use crate::pt::{self, StorageType};
 use std::{
     borrow::Cow,
     fmt::{Display, Formatter, Result, Write},
@@ -1169,6 +1169,11 @@ impl Display for pt::VariableAttribute {
                 }
                 Ok(())
             }
+            Self::StorageType(storage) => match storage {
+                StorageType::Instance(_) => f.write_str("instance"),
+                StorageType::Temporary(_) => f.write_str("temporary"),
+                StorageType::Persistent(_) => f.write_str("persistent"),
+            },
         }
     }
 }

+ 9 - 0
solang-parser/src/helpers/loc.rs

@@ -28,6 +28,14 @@ impl OptionalCodeLocation for pt::Visibility {
     }
 }
 
+impl OptionalCodeLocation for pt::StorageType {
+    fn loc_opt(&self) -> Option<Loc> {
+        match self {
+            Self::Persistent(l) | Self::Temporary(l) | Self::Instance(l) => *l,
+        }
+    }
+}
+
 impl OptionalCodeLocation for pt::SourceUnit {
     #[inline]
     fn loc_opt(&self) -> Option<Loc> {
@@ -431,6 +439,7 @@ impl_for_enums! {
 
     pt::VariableAttribute: match self {
         Self::Visibility(ref l, ..) => l.loc_opt().unwrap_or_default(),
+        Self::StorageType(ref l, ..) => l.loc_opt().unwrap_or_default(),
         Self::Constant(l, ..)
         | Self::Immutable(l, ..)
         | Self::Override(l, ..) => l,

+ 11 - 0
solang-parser/src/lexer.rs

@@ -179,6 +179,11 @@ pub enum Token<'input> {
     Default,
     YulArrow,
 
+    // Storage types for Soroban
+    Persistent,
+    Temporary,
+    Instance,
+
     Annotation(&'input str),
 }
 
@@ -316,6 +321,9 @@ impl fmt::Display for Token<'_> {
             Token::Default => write!(f, "default"),
             Token::YulArrow => write!(f, "->"),
             Token::Annotation(name) => write!(f, "@{name}"),
+            Token::Persistent => write!(f, "persistent"),
+            Token::Temporary => write!(f, "temporary"),
+            Token::Instance => write!(f, "instance"),
         }
     }
 }
@@ -553,6 +561,9 @@ static KEYWORDS: phf::Map<&'static str, Token> = phf_map! {
     "unchecked" => Token::Unchecked,
     "assembly" => Token::Assembly,
     "let" => Token::Let,
+    "persistent" => Token::Persistent,
+    "temporary" => Token::Temporary,
+    "instance" => Token::Instance,
 };
 
 impl<'input> Lexer<'input> {

+ 18 - 0
solang-parser/src/pt.rs

@@ -955,6 +955,24 @@ pub enum VariableAttribute {
 
     /// `ovveride(<1>,*)`
     Override(Loc, Vec<IdentifierPath>),
+
+    /// Storage type.
+    StorageType(StorageType),
+}
+
+/// Soroban storage types.
+#[derive(Debug, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "pt-serde", derive(Serialize, Deserialize))]
+#[repr(u8)] // for cmp; order of variants is important
+pub enum StorageType {
+    /// `Temporary`
+    Temporary(Option<Loc>),
+
+    /// `persistent`
+    Persistent(Option<Loc>),
+
+    /// `Instance`
+    Instance(Option<Loc>),
 }
 
 /// A variable definition.

+ 10 - 0
solang-parser/src/solidity.lalrpop

@@ -359,8 +359,15 @@ Visibility: Visibility = {
     <l:@L> "private" <r:@R> => Visibility::Private(Some(Loc::File(file_no, l, r))),
 }
 
+StorageType: StorageType = {
+    <l:@L> "persistent" <r:@R> => StorageType::Persistent(Some(Loc::File(file_no, l, r))),
+    <l:@R> "temporary" <r:@R> => StorageType::Temporary(Some(Loc::File(file_no, l, r))),
+    <l:@R> "instance" <r:@R> => StorageType::Instance(Some(Loc::File(file_no, l, r))),
+}
+
 VariableAttribute: VariableAttribute = {
     Visibility => VariableAttribute::Visibility(<>),
+    StorageType => VariableAttribute::StorageType(<>),
     <l:@L> "constant" <r:@R> => VariableAttribute::Constant(Loc::File(file_no, l, r)),
     <l:@L> "immutable" <r:@R> => VariableAttribute::Immutable(Loc::File(file_no, l, r)),
     <l:@L> "override" <r:@R> => VariableAttribute::Override(Loc::File(file_no, l, r), Vec::new()),
@@ -1312,5 +1319,8 @@ extern {
         "switch" => Token::Switch,
         "case" => Token::Case,
         "default" => Token::Default,
+        "persistent" => Token::Persistent,
+        "temporary" => Token::Temporary,
+        "instance" => Token::Instance,
     }
 }

+ 2 - 2
solang-parser/src/tests.rs

@@ -47,9 +47,9 @@ contract 9c {
                 Diagnostic { loc: File(0, 48, 49), level: Error, ty: ParserError, message: "unrecognised token ';', expected \"*\", \"<\", \"<=\", \"=\", \">\", \">=\", \"^\", \"~\", identifier, number, string".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 62, 65), level: Error, ty: ParserError, message: r#"unrecognised token 'for', expected "(", ";", "=""#.to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 78, 79), level: Error, ty: ParserError, message: r#"unrecognised token '9', expected "case", "default", "leave", "revert", "switch", identifier"#.to_string(), notes: vec![] },
-                Diagnostic { loc: File(0, 95, 96), level: Error, ty: ParserError, message: "unrecognised token '0', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
+                Diagnostic { loc: File(0, 95, 96), level: Error, ty: ParserError, message: "unrecognised token '0', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"instance\", \"internal\", \"leave\", \"override\", \"persistent\", \"private\", \"public\", \"revert\", \"switch\", \"temporary\", \"{\", identifier".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 116, 123), level: Error, ty: ParserError, message: "unrecognised token 'uint256', expected \"++\", \"--\", \".\", \"[\", \"case\", \"default\", \"leave\", \"switch\", identifier".to_string(), notes: vec![] },
-                Diagnostic { loc: File(0, 403, 404), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"private\", \"public\", \"revert\", \"switch\", \"{\", identifier".to_string(), notes: vec![] },
+                Diagnostic { loc: File(0, 403, 404), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"(\", \"++\", \"--\", \".\", \"[\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"instance\", \"internal\", \"leave\", \"override\", \"persistent\", \"private\", \"public\", \"revert\", \"switch\", \"temporary\", \"{\", identifier".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 441, 442), level: Error, ty: ParserError, message: r#"unrecognised token '4', expected "(", "case", "default", "leave", "revert", "switch", identifier"#.to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 460, 461), level: Error, ty: ParserError, message: "unrecognised token '!', expected \";\", \"case\", \"constant\", \"default\", \"external\", \"immutable\", \"internal\", \"leave\", \"override\", \"payable\", \"private\", \"public\", \"pure\", \"return\", \"returns\", \"revert\", \"switch\", \"view\", \"virtual\", \"{\", identifier".to_string(), notes: vec![] },
                 Diagnostic { loc: File(0, 482, 483), level: Error, ty: ParserError, message: "unrecognised token '3', expected \"!=\", \"%\", \"%=\", \"&\", \"&&\", \"&=\", \"(\", \"*\", \"**\", \"*=\", \"+\", \"++\", \"+=\", \"-\", \"--\", \"-=\", \".\", \"/\", \"/=\", \";\", \"<\", \"<<\", \"<<=\", \"<=\", \"=\", \"==\", \">\", \">=\", \">>\", \">>=\", \"?\", \"[\", \"^\", \"^=\", \"calldata\", \"case\", \"default\", \"leave\", \"memory\", \"revert\", \"storage\", \"switch\", \"{\", \"|\", \"|=\", \"||\", identifier".to_string(), notes: vec![] },

+ 4 - 2
src/codegen/cfg.rs

@@ -70,6 +70,7 @@ pub enum Instr {
         res: usize,
         ty: Type,
         storage: Expression,
+        storage_type: Option<pt::StorageType>,
     },
     /// Clear storage at slot for ty (might span multiple slots)
     ClearStorage { ty: Type, storage: Expression },
@@ -78,6 +79,7 @@ pub enum Instr {
         ty: Type,
         value: Expression,
         storage: Expression,
+        storage_type: Option<pt::StorageType>,
     },
     /// In storage slot, set the value at the offset
     SetStorageBytes {
@@ -1027,7 +1029,7 @@ impl ControlFlowGraph {
                 true_block,
                 false_block,
             ),
-            Instr::LoadStorage { ty, res, storage } => format!(
+            Instr::LoadStorage { ty, res, storage, .. } => format!(
                 "%{} = load storage slot({}) ty:{}",
                 self.vars[res].id.name,
                 self.expr_to_string(contract, ns, storage),
@@ -1038,7 +1040,7 @@ impl ControlFlowGraph {
                 self.expr_to_string(contract, ns, storage),
                 ty.to_string(ns),
             ),
-            Instr::SetStorage { ty, value, storage } => format!(
+            Instr::SetStorage { ty, value, storage, .. } => format!(
                 "store storage slot({}) ty:{} = {}",
                 self.expr_to_string(contract, ns, storage),
                 ty.to_string(ns),

+ 14 - 2
src/codegen/constant_folding.rs

@@ -129,7 +129,12 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Name
                         };
                     }
                 }
-                Instr::SetStorage { ty, storage, value } => {
+                Instr::SetStorage {
+                    ty,
+                    storage,
+                    value,
+                    storage_type,
+                } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
                     let (value, _) = expression(value, Some(&vars), cfg, ns);
 
@@ -138,10 +143,16 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Name
                             ty: ty.clone(),
                             storage,
                             value,
+                            storage_type: storage_type.clone(),
                         };
                     }
                 }
-                Instr::LoadStorage { ty, storage, res } => {
+                Instr::LoadStorage {
+                    ty,
+                    storage,
+                    res,
+                    storage_type,
+                } => {
                     let (storage, _) = expression(storage, Some(&vars), cfg, ns);
 
                     if !dry_run {
@@ -149,6 +160,7 @@ pub fn constant_folding(cfg: &mut ControlFlowGraph, dry_run: bool, ns: &mut Name
                             ty: ty.clone(),
                             storage,
                             res: *res,
+                            storage_type: storage_type.clone(),
                         };
                     }
                 }

+ 3 - 1
src/codegen/dead_storage.rs

@@ -488,7 +488,9 @@ pub fn dead_storage(cfg: &mut ControlFlowGraph, _ns: &mut Namespace) {
             let vars = &block_vars[&block_no][instr_no];
 
             match &cfg.blocks[block_no].instr[instr_no] {
-                Instr::LoadStorage { res, ty, storage } => {
+                Instr::LoadStorage {
+                    res, ty, storage, ..
+                } => {
                     // is there a definition which has the same storage expression
                     let mut found = None;
 

+ 1 - 0
src/codegen/dispatch/solana.rs

@@ -491,6 +491,7 @@ fn check_magic(magic_value: u32, cfg: &mut ControlFlowGraph, vartab: &mut Vartab
                 ty: Type::Uint(32),
                 value: 0.into(),
             },
+            storage_type: None,
         },
     );
 

+ 8 - 3
src/codegen/dispatch/soroban.rs

@@ -46,8 +46,13 @@ pub fn function_dispatch(
 
         wrapper_cfg.params = function.params.clone();
 
-        let param = ast::Parameter::new_default(Type::Uint(64));
-        wrapper_cfg.returns = vec![param].into();
+        let return_type = if cfg.returns.len() == 1 {
+            cfg.returns[0].clone()
+        } else {
+            ast::Parameter::new_default(Type::Void)
+        };
+
+        wrapper_cfg.returns = vec![return_type].into();
         wrapper_cfg.public = true;
 
         let mut vartab = Vartable::from_symbol_table(&function.symtable, ns.next_id);
@@ -127,7 +132,7 @@ pub fn function_dispatch(
             let added = Expression::Add {
                 loc: pt::Loc::Codegen,
                 ty: Type::Uint(64),
-                overflowing: false,
+                overflowing: true,
                 left: shifted.into(),
                 right: tag.into(),
             };

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

@@ -1422,7 +1422,7 @@ pub(crate) trait AbiEncoding {
                 self.get_expr_size(arg_no, &loaded, ns, vartab, cfg)
             }
             Type::StorageRef(_, r) => {
-                let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab);
+                let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab, None);
                 let size = self.get_expr_size(arg_no, &var, ns, vartab, cfg);
                 self.storage_cache_insert(arg_no, var.clone());
                 size

+ 50 - 5
src/codegen/expression.rs

@@ -58,9 +58,10 @@ pub fn expression(
             ns.contracts[contract_no].get_storage_slot(*loc, *var_contract_no, *var_no, ns, None)
         }
         ast::Expression::StorageLoad { loc, ty, expr } => {
+            let storage_type = storage_type(expr, ns);
             let storage = expression(expr, cfg, contract_no, func, ns, vartab, opt);
 
-            load_storage(loc, ty, storage, cfg, vartab)
+            load_storage(loc, ty, storage, cfg, vartab, storage_type)
         }
         ast::Expression::Add {
             loc,
@@ -543,7 +544,7 @@ pub fn expression(
                                 elem_ty: elem_ty.clone(),
                             }
                         } else {
-                            load_storage(loc, &ns.storage_type(), array, cfg, vartab)
+                            load_storage(loc, &ns.storage_type(), array, cfg, vartab, None)
                         }
                     }
                     ArrayLength::Fixed(length) => {
@@ -1232,13 +1233,23 @@ fn post_incdec(
 ) -> Expression {
     let res = vartab.temp_anonymous(ty);
     let v = expression(var, cfg, contract_no, func, ns, vartab, opt);
+
+    let storage_type = storage_type(var, ns);
+
     let v = match var.ty() {
         Type::Ref(ty) => Expression::Load {
             loc: var.loc(),
             ty: ty.as_ref().clone(),
             expr: Box::new(v),
         },
-        Type::StorageRef(_, ty) => load_storage(&var.loc(), ty.as_ref(), v, cfg, vartab),
+        Type::StorageRef(_, ty) => load_storage(
+            &var.loc(),
+            ty.as_ref(),
+            v,
+            cfg,
+            vartab,
+            storage_type.clone(),
+        ),
         _ => v,
     };
     cfg.add(
@@ -1314,6 +1325,7 @@ fn post_incdec(
                             },
                             ty: ty.clone(),
                             storage: dest,
+                            storage_type,
                         },
                     );
                 }
@@ -1356,13 +1368,21 @@ fn pre_incdec(
 ) -> Expression {
     let res = vartab.temp_anonymous(ty);
     let v = expression(var, cfg, contract_no, func, ns, vartab, opt);
+    let storage_type = storage_type(var, ns);
     let v = match var.ty() {
         Type::Ref(ty) => Expression::Load {
             loc: var.loc(),
             ty: ty.as_ref().clone(),
             expr: Box::new(v),
         },
-        Type::StorageRef(_, ty) => load_storage(&var.loc(), ty.as_ref(), v, cfg, vartab),
+        Type::StorageRef(_, ty) => load_storage(
+            &var.loc(),
+            ty.as_ref(),
+            v,
+            cfg,
+            vartab,
+            storage_type.clone(),
+        ),
         _ => v,
     };
     let one = Box::new(Expression::NumberLiteral {
@@ -1395,6 +1415,7 @@ fn pre_incdec(
             expr,
         },
     );
+
     match var {
         ast::Expression::Variable { loc, var_no, .. } => {
             cfg.add(
@@ -1425,6 +1446,7 @@ fn pre_incdec(
                             },
                             ty: ty.clone(),
                             storage: dest,
+                            storage_type: storage_type.clone(),
                         },
                     );
                 }
@@ -2676,6 +2698,8 @@ pub fn assign_single(
                 },
             );
 
+            let storage_type = storage_type(left, ns);
+
             match left_ty {
                 Type::StorageRef(..) if set_storage_bytes => {
                     if let Expression::Subscript {
@@ -2710,6 +2734,7 @@ pub fn assign_single(
                             },
                             ty: ty.deref_any().clone(),
                             storage: dest,
+                            storage_type,
                         },
                     );
                 }
@@ -3268,8 +3293,9 @@ fn array_subscript(
                             elem_ty: array_ty.storage_array_elem().deref_into(),
                         }
                     } else {
+                        // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban
                         let array_length =
-                            load_storage(loc, &Type::Uint(256), array.clone(), cfg, vartab);
+                            load_storage(loc, &Type::Uint(256), array.clone(), cfg, vartab, None);
 
                         array = Expression::Keccak256 {
                             loc: *loc,
@@ -3616,14 +3642,17 @@ pub fn load_storage(
     storage: Expression,
     cfg: &mut ControlFlowGraph,
     vartab: &mut Vartable,
+    storage_type: Option<pt::StorageType>,
 ) -> Expression {
     let res = vartab.temp_anonymous(ty);
+
     cfg.add(
         vartab,
         Instr::LoadStorage {
             res,
             ty: ty.clone(),
             storage,
+            storage_type,
         },
     );
 
@@ -3805,3 +3834,19 @@ fn add_prefix_and_delimiter_to_print(mut expr: Expression) -> Expression {
         }
     }
 }
+
+fn storage_type(expr: &ast::Expression, ns: &Namespace) -> Option<pt::StorageType> {
+    match expr {
+        ast::Expression::StorageVariable {
+            loc: _,
+            ty: _,
+            var_no,
+            contract_no,
+        } => {
+            let var = ns.contracts[*contract_no].variables.get(*var_no).unwrap();
+
+            var.storage_type.clone()
+        }
+        _ => None,
+    }
+}

+ 1 - 0
src/codegen/mod.rs

@@ -284,6 +284,7 @@ fn storage_initializer(contract_no: usize, ns: &mut Namespace, opt: &Options) ->
                     value,
                     ty: var.ty.clone(),
                     storage,
+                    storage_type: var.storage_type.clone(),
                 },
             );
         }

+ 2 - 0
src/codegen/solana_deploy.rs

@@ -639,6 +639,7 @@ pub(super) fn solana_deploy(
                 ty: Type::Uint(64),
                 value: BigInt::zero(),
             },
+            storage_type: None,
         },
     );
 
@@ -663,6 +664,7 @@ pub(super) fn solana_deploy(
                 ty: Type::Uint(64),
                 value: BigInt::from(12),
             },
+            storage_type: None,
         },
     );
 }

+ 1 - 0
src/codegen/statements/mod.rs

@@ -1002,6 +1002,7 @@ fn try_load_and_cast(
                     res: anonymous_no,
                     ty: (*ty).clone(),
                     storage: expr.cast(to_ty, ns),
+                    storage_type: None,
                 },
             );
 

+ 8 - 3
src/codegen/storage.rs

@@ -98,7 +98,8 @@ pub fn storage_slots_array_push(
 
     let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
 
-    let expr = load_storage(loc, &slot_ty, var_expr.clone(), cfg, vartab);
+    // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban
+    let expr = load_storage(loc, &slot_ty, var_expr.clone(), cfg, vartab, None);
 
     cfg.add(
         vartab,
@@ -149,6 +150,7 @@ pub fn storage_slots_array_push(
                     ty: slot_ty.clone(),
                     var_no: entry_pos,
                 },
+                storage_type: None,
             },
         );
     }
@@ -176,6 +178,7 @@ pub fn storage_slots_array_push(
             ty: slot_ty,
             value: new_length,
             storage: var_expr,
+            storage_type: None,
         },
     );
 
@@ -209,8 +212,8 @@ pub fn storage_slots_array_pop(
 
     let ty = args[0].ty();
     let var_expr = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
-
-    let expr = load_storage(loc, &length_ty, var_expr.clone(), cfg, vartab);
+    // TODO(Soroban): Storage type here is None, since arrays are not yet supported in Soroban
+    let expr = load_storage(loc, &length_ty, var_expr.clone(), cfg, vartab, None);
 
     cfg.add(
         vartab,
@@ -324,6 +327,7 @@ pub fn storage_slots_array_pop(
             },
             cfg,
             vartab,
+            None,
         );
 
         cfg.add(
@@ -368,6 +372,7 @@ pub fn storage_slots_array_pop(
                 var_no: new_length,
             },
             storage: var_expr,
+            storage_type: None,
         },
     );
 

+ 14 - 2
src/codegen/subexpression_elimination/instruction.rs

@@ -267,10 +267,16 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> {
                 expr: self.regenerate_expression(expr, ave, cst).1,
             },
 
-            Instr::LoadStorage { res, ty, storage } => Instr::LoadStorage {
+            Instr::LoadStorage {
+                res,
+                ty,
+                storage,
+                storage_type,
+            } => Instr::LoadStorage {
                 res: *res,
                 ty: ty.clone(),
                 storage: self.regenerate_expression(storage, ave, cst).1,
+                storage_type: storage_type.clone(),
             },
 
             Instr::ClearStorage { ty, storage } => Instr::ClearStorage {
@@ -278,10 +284,16 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> {
                 storage: self.regenerate_expression(storage, ave, cst).1,
             },
 
-            Instr::SetStorage { ty, value, storage } => Instr::SetStorage {
+            Instr::SetStorage {
+                ty,
+                value,
+                storage,
+                storage_type,
+            } => Instr::SetStorage {
                 ty: ty.clone(),
                 value: self.regenerate_expression(value, ave, cst).1,
                 storage: self.regenerate_expression(storage, ave, cst).1,
+                storage_type: storage_type.clone(),
             },
 
             Instr::SetStorageBytes {

+ 2 - 0
src/codegen/yul/tests/expression.rs

@@ -133,6 +133,7 @@ fn contract_constant_variable() {
         }),
         assigned: false,
         read: false,
+        storage_type: None,
     };
 
     let contract = Contract {
@@ -198,6 +199,7 @@ fn global_constant_variable() {
         }),
         assigned: false,
         read: false,
+        storage_type: None,
     };
     ns.constants.push(var);
     let expr = ast::YulExpression::ConstantVariable(loc, Type::Uint(64), None, 0);

+ 8 - 0
src/emit/binary.rs

@@ -1015,6 +1015,14 @@ impl<'a> Binary<'a> {
                 Type::FunctionSelector => {
                     self.llvm_type(&Type::Bytes(ns.target.selector_length()), ns)
                 }
+                // Soroban functions always return a 64 bit value.
+                Type::Void => {
+                    if ns.target == Target::Soroban {
+                        BasicTypeEnum::IntType(self.context.i64_type())
+                    } else {
+                        unreachable!()
+                    }
+                }
                 _ => unreachable!(),
             }
         }

+ 14 - 4
src/emit/instructions.rs

@@ -112,23 +112,33 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
                 .build_conditional_branch(cond.into_int_value(), bb_true, bb_false)
                 .unwrap();
         }
-        Instr::LoadStorage { res, ty, storage } => {
+        Instr::LoadStorage {
+            res,
+            ty,
+            storage,
+            storage_type,
+        } => {
             let mut slot = expression(target, bin, storage, &w.vars, function, ns).into_int_value();
 
             w.vars.get_mut(res).unwrap().value =
-                target.storage_load(bin, ty, &mut slot, function, ns);
+                target.storage_load(bin, ty, &mut slot, function, ns, storage_type);
         }
         Instr::ClearStorage { ty, storage } => {
             let mut slot = expression(target, bin, storage, &w.vars, function, ns).into_int_value();
 
             target.storage_delete(bin, ty, &mut slot, function, ns);
         }
-        Instr::SetStorage { ty, value, storage } => {
+        Instr::SetStorage {
+            ty,
+            value,
+            storage,
+            storage_type,
+        } => {
             let value = expression(target, bin, value, &w.vars, function, ns);
 
             let mut slot = expression(target, bin, storage, &w.vars, function, ns).into_int_value();
 
-            target.storage_store(bin, ty, true, &mut slot, value, function, ns);
+            target.storage_store(bin, ty, true, &mut slot, value, function, ns, storage_type);
         }
         Instr::SetStorageBytes {
             storage,

+ 3 - 1
src/emit/mod.rs

@@ -12,7 +12,7 @@ use inkwell::types::{BasicTypeEnum, IntType};
 use inkwell::values::{
     ArrayValue, BasicMetadataValueEnum, BasicValueEnum, FunctionValue, IntValue, PointerValue,
 };
-use solang_parser::pt::Loc;
+use solang_parser::pt::{Loc, StorageType};
 
 pub mod binary;
 mod cfg;
@@ -85,6 +85,7 @@ pub trait TargetRuntime<'a> {
         slot: &mut IntValue<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        storage_type: &Option<StorageType>,
     ) -> BasicValueEnum<'a>;
 
     /// Recursively store a type to storage
@@ -97,6 +98,7 @@ pub trait TargetRuntime<'a> {
         dest: BasicValueEnum<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        storage_type: &Option<StorageType>,
     );
 
     /// Recursively clear storage. The default implementation is for slot-based storage

+ 3 - 1
src/emit/polkadot/target.rs

@@ -17,7 +17,7 @@ use inkwell::values::{
     ArrayValue, BasicMetadataValueEnum, BasicValueEnum, FunctionValue, IntValue, PointerValue,
 };
 use inkwell::{AddressSpace, IntPredicate};
-use solang_parser::pt::Loc;
+use solang_parser::pt::{Loc, StorageType};
 use std::collections::HashMap;
 
 impl<'a> TargetRuntime<'a> for PolkadotTarget {
@@ -1574,6 +1574,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
         slot: &mut IntValue<'a>,
         function: FunctionValue,
         ns: &Namespace,
+        _storage_type: &Option<StorageType>,
     ) -> BasicValueEnum<'a> {
         // The storage slot is an i256 accessed through a pointer, so we need
         // to store it
@@ -1594,6 +1595,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
         dest: BasicValueEnum<'a>,
         function: FunctionValue<'a>,
         ns: &Namespace,
+        _: &Option<StorageType>,
     ) {
         let slot_ptr = binary
             .builder

+ 10 - 4
src/emit/solana/target.rs

@@ -14,7 +14,7 @@ use inkwell::values::{
 };
 use inkwell::{AddressSpace, IntPredicate};
 use num_traits::ToPrimitive;
-use solang_parser::pt::Loc;
+use solang_parser::pt::{Loc, StorageType};
 use std::collections::HashMap;
 
 impl<'a> TargetRuntime<'a> for SolanaTarget {
@@ -440,7 +440,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .unwrap();
 
         if let Some(val) = val {
-            self.storage_store(binary, ty, false, &mut new_offset, val, function, ns);
+            self.storage_store(binary, ty, false, &mut new_offset, val, function, ns, &None);
         }
 
         if ty.is_reference_type(ns) {
@@ -545,7 +545,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
             .unwrap();
 
         let val = if load {
-            Some(self.storage_load(binary, ty, &mut old_elem_offset, function, ns))
+            Some(self.storage_load(binary, ty, &mut old_elem_offset, function, ns, &None))
         } else {
             None
         };
@@ -639,6 +639,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         slot: &mut IntValue<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        _storage_type: &Option<StorageType>,
     ) -> BasicValueEnum<'a> {
         let data = self.contract_storage_data(binary);
 
@@ -733,7 +734,8 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                         )
                         .unwrap();
 
-                    let val = self.storage_load(binary, &field.ty, &mut offset, function, ns);
+                    let val =
+                        self.storage_load(binary, &field.ty, &mut offset, function, ns, &None);
 
                     let elem = unsafe {
                         binary
@@ -849,6 +851,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                     &mut offset_val,
                     function,
                     ns,
+                    &None,
                 );
 
                 let val = if elem_ty.deref_memory().is_fixed_reference_type(ns) {
@@ -896,6 +899,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
         val: BasicValueEnum<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        _: &Option<StorageType>,
     ) {
         let data = self.contract_storage_data(binary);
         let account = self.contract_storage_account(binary);
@@ -1221,6 +1225,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                 },
                 function,
                 ns,
+                &None,
             );
 
             offset_val = binary
@@ -1296,6 +1301,7 @@ impl<'a> TargetRuntime<'a> for SolanaTarget {
                     },
                     function,
                     ns,
+                    &None,
                 );
             }
         } else {

+ 29 - 3
src/emit/soroban/target.rs

@@ -19,7 +19,7 @@ use inkwell::values::{
     PointerValue,
 };
 
-use solang_parser::pt::Loc;
+use solang_parser::pt::{Loc, StorageType};
 
 use std::collections::HashMap;
 
@@ -43,7 +43,9 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
         slot: &mut IntValue<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        storage_type: &Option<StorageType>,
     ) -> BasicValueEnum<'a> {
+        let storage_type = storage_type_to_int(storage_type);
         emit_context!(binary);
         let ret = call!(
             GET_CONTRACT_DATA,
@@ -52,7 +54,11 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
                     .into_int_value()
                     .const_cast(binary.context.i64_type(), false)
                     .into(),
-                i64_const!(2).into()
+                binary
+                    .context
+                    .i64_type()
+                    .const_int(storage_type, false)
+                    .into(),
             ]
         )
         .try_as_basic_value()
@@ -73,8 +79,12 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
         dest: BasicValueEnum<'a>,
         function: FunctionValue<'a>,
         ns: &ast::Namespace,
+        storage_type: &Option<StorageType>,
     ) {
         emit_context!(binary);
+
+        let storage_type = storage_type_to_int(storage_type);
+
         let function_value = binary.module.get_function(PUT_CONTRACT_DATA).unwrap();
 
         let value = binary
@@ -87,7 +97,11 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
                         .const_cast(binary.context.i64_type(), false)
                         .into(),
                     dest.into(),
-                    binary.context.i64_type().const_int(2, false).into(),
+                    binary
+                        .context
+                        .i64_type()
+                        .const_int(storage_type, false)
+                        .into(),
                 ],
                 PUT_CONTRACT_DATA,
             )
@@ -434,3 +448,15 @@ impl<'a> TargetRuntime<'a> for SorobanTarget {
         unimplemented!()
     }
 }
+
+fn storage_type_to_int(storage_type: &Option<StorageType>) -> u64 {
+    if let Some(storage_type) = storage_type {
+        match storage_type {
+            StorageType::Temporary(_) => 0,
+            StorageType::Persistent(_) => 1,
+            StorageType::Instance(_) => 2,
+        }
+    } else {
+        1
+    }
+}

+ 1 - 0
src/sema/ast.rs

@@ -590,6 +590,7 @@ pub struct Variable {
     pub initializer: Option<Expression>,
     pub assigned: bool,
     pub read: bool,
+    pub storage_type: Option<pt::StorageType>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]

+ 36 - 0
src/sema/variables.rs

@@ -15,6 +15,7 @@ use super::{
 };
 use crate::sema::expression::resolve_expression::expression;
 use crate::sema::namespace::ResolveTypeContext;
+use crate::Target;
 use solang_parser::{
     doccomment::DocComment,
     pt::{self, CodeLocation, OptionalCodeLocation},
@@ -136,6 +137,7 @@ pub fn variable_decl<'a>(
     let mut visibility: Option<pt::Visibility> = None;
     let mut has_immutable: Option<pt::Loc> = None;
     let mut is_override: Option<(pt::Loc, Vec<usize>)> = None;
+    let mut storage_type: Option<pt::StorageType> = None;
 
     for attr in attrs {
         match &attr {
@@ -233,9 +235,42 @@ pub fn variable_decl<'a>(
 
                 visibility = Some(v.clone());
             }
+            pt::VariableAttribute::StorageType(s) => {
+                if storage_type.is_some() {
+                    ns.diagnostics.push(Diagnostic::error(
+                        attr.loc(),
+                        format!(
+                            "mutliple storage type specifiers for '{}'",
+                            def.name.as_ref().unwrap().name
+                        ),
+                    ));
+                } else {
+                    storage_type = Some(s.clone());
+                }
+            }
         }
     }
 
+    if ns.target == Target::Soroban {
+        if storage_type.is_none() {
+            ns.diagnostics.push(Diagnostic::warning(
+                def.loc,
+                format!(
+                    "storage type not specified for `{}`, defaulting to `persistent`",
+                    def.name.as_ref().unwrap().name
+                ),
+            ));
+        }
+    } else if storage_type.is_some() {
+        ns.diagnostics.push(Diagnostic::warning(
+            def.loc,
+            format!(
+                "variable `{}`: storage types are only valid for Soroban targets",
+                def.name.as_ref().unwrap().name
+            ),
+        ));
+    }
+
     if let Some(loc) = &has_immutable {
         if constant {
             ns.diagnostics.push(Diagnostic::error(
@@ -392,6 +427,7 @@ pub fn variable_decl<'a>(
         assigned: def.initializer.is_some(),
         initializer,
         read: matches!(visibility, pt::Visibility::Public(_)),
+        storage_type,
     };
 
     let var_no = if let Some(contract_no) = contract_no {

+ 6 - 0
src/sema/yul/tests/expression.rs

@@ -370,6 +370,7 @@ fn resolve_variable_contract() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
     contract.variables.push(Variable {
         tags: vec![],
@@ -382,6 +383,7 @@ fn resolve_variable_contract() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
 
     contract.variables.push(Variable {
@@ -395,6 +397,7 @@ fn resolve_variable_contract() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
 
     ns.contracts.push(contract);
@@ -410,6 +413,7 @@ fn resolve_variable_contract() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
 
     ns.variable_symbols.insert(
@@ -921,6 +925,7 @@ fn test_member_access() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
 
     ns.contracts.push(contract);
@@ -1043,6 +1048,7 @@ fn test_check_types() {
         initializer: None,
         assigned: false,
         read: false,
+        storage_type: None,
     });
     ns.contracts.push(contract);
     let mut symtable = Symtable::default();

File diff suppressed because it is too large
+ 151 - 0
test_snapshots/soroban_testcases/storage/counter.1.json


File diff suppressed because it is too large
+ 224 - 0
test_snapshots/soroban_testcases/storage/different_storage_types.1.json


+ 7 - 7
tests/codegen_testcases/solidity/common_subexpression_elimination.sol

@@ -222,14 +222,14 @@ contract c1 {
 
 // BEGIN-CHECK: c1::function::test9
     function test9(int a, int b) public view returns (int ret) {
-        stTest instance = stTest(2, 3);
+        stTest struct_instance = stTest(2, 3);
         // CHECK:  ty:int256 %1.cse_temp = ((arg #0) + (arg #1))
-        int x = a + b + instance.a;
-        // CHECK: ty:int256 %x = (%1.cse_temp + (load (struct %instance field 0)))
-        // CHECK: branchcond (signed less (%x + int256((load (struct %instance field 1)))) < int256 0)
-        if(x  + int(instance.b) < 0) {
-            // CHECK: ty:uint256 %p = uint256((%1.cse_temp + (load (struct %instance field 0))))
-            uint p = uint(a+b+instance.a);
+        int x = a + b + struct_instance.a;
+        // CHECK: ty:int256 %x = (%1.cse_temp + (load (struct %struct_instance field 0)))
+        // CHECK: branchcond (signed less (%x + int256((load (struct %struct_instance field 1)))) < int256 0)
+        if(x  + int(struct_instance.b) < 0) {
+            // CHECK: ty:uint256 %p = uint256((%1.cse_temp + (load (struct %struct_instance field 0))))
+            uint p = uint(a+b+struct_instance.a);
             bool e = p > 50;
         }
 

+ 9 - 1
tests/soroban.rs

@@ -5,6 +5,7 @@ pub mod soroban_testcases;
 
 use solang::codegen::Options;
 use solang::file_resolver::FileResolver;
+use solang::sema::diagnostics::Diagnostics;
 use solang::{compile, Target};
 use soroban_sdk::testutils::Logs;
 use soroban_sdk::{vec, Address, Env, Symbol, Val};
@@ -14,6 +15,7 @@ use std::ffi::OsStr;
 pub struct SorobanEnv {
     env: Env,
     contracts: Vec<Address>,
+    compiler_diagnostics: Diagnostics,
 }
 
 pub fn build_solidity(src: &str) -> SorobanEnv {
@@ -41,7 +43,7 @@ pub fn build_solidity(src: &str) -> SorobanEnv {
     ns.print_diagnostics_in_plain(&cache, false);
     assert!(!wasm.is_empty());
     let wasm_blob = wasm[0].0.clone();
-    SorobanEnv::new_with_contract(wasm_blob)
+    SorobanEnv::new_with_contract(wasm_blob).insert_diagnostics(ns.diagnostics)
 }
 
 impl SorobanEnv {
@@ -49,9 +51,15 @@ impl SorobanEnv {
         Self {
             env: Env::default(),
             contracts: Vec::new(),
+            compiler_diagnostics: Diagnostics::default(),
         }
     }
 
+    pub fn insert_diagnostics(mut self, diagnostics: Diagnostics) -> Self {
+        self.compiler_diagnostics = diagnostics;
+        self
+    }
+
     pub fn new_with_contract(contract_wasm: Vec<u8>) -> Self {
         let mut env = Self::new();
         env.register_contract(contract_wasm);

+ 84 - 0
tests/soroban_testcases/storage.rs

@@ -37,3 +37,87 @@ fn counter() {
     let expected: Val = 10_u64.into_val(&src.env);
     assert!(expected.shallow_eq(&res));
 }
+
+#[test]
+fn different_storage_types() {
+    let src = build_solidity(
+        r#"contract sesa {
+            
+    uint64 public temporary sesa = 1;
+    uint64 public instance sesa1 = 1;
+    uint64 public persistent sesa2 = 2;
+    uint64 public sesa3 = 2;
+
+    function inc() public {
+        sesa++;
+        sesa1++;
+        sesa2++;
+        sesa3++;
+    }
+
+    function dec() public {
+        sesa--;
+        sesa1--;
+        sesa2--;
+        sesa3--;
+    }
+}"#,
+    );
+
+    let addr = src.contracts.last().unwrap();
+
+    let res = src.invoke_contract(addr, "sesa", vec![]);
+    let expected: Val = 1_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa1", vec![]);
+    let expected: Val = 1_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa2", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa3", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    src.invoke_contract(addr, "inc", vec![]);
+    let res = src.invoke_contract(addr, "sesa", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa1", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa2", vec![]);
+    let expected: Val = 3_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa3", vec![]);
+    let expected: Val = 3_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    src.invoke_contract(addr, "dec", vec![]);
+    let res = src.invoke_contract(addr, "sesa", vec![]);
+    let expected: Val = 1_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa1", vec![]);
+    let expected: Val = 1_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa2", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let res = src.invoke_contract(addr, "sesa3", vec![]);
+    let expected: Val = 2_u64.into_val(&src.env);
+    assert!(expected.shallow_eq(&res));
+
+    let diags = src.compiler_diagnostics;
+
+    assert!(diags
+        .contains_message("storage type not specified for `sesa3`, defaulting to `persistent`"));
+}

Some files were not shown because too many files changed in this diff