From 2536c0803676bd1a3c09425f5a9cc0c83bfb1a4b Mon Sep 17 00:00:00 2001 From: salaheldinsoliman <49910731+salaheldinsoliman@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:58:32 +0200 Subject: [PATCH] Support different Soroban storage types (#1664) --- fmt/src/formatter.rs | 7 +- fmt/src/solang_ext/ast_eq.rs | 12 + integration/polkadot/try_catch.sol | 4 +- integration/soroban/.gitignore | 1 - integration/soroban/package.json | 4 +- integration/soroban/runtime_error.spec.js | 48 ++++ integration/soroban/storage_types.sol | 21 ++ integration/soroban/storage_types.spec.js | 84 +++++++ solang-parser/src/helpers/fmt.rs | 7 +- solang-parser/src/helpers/loc.rs | 9 + solang-parser/src/lexer.rs | 11 + solang-parser/src/pt.rs | 18 ++ solang-parser/src/solidity.lalrpop | 10 + solang-parser/src/tests.rs | 4 +- src/codegen/cfg.rs | 6 +- src/codegen/constant_folding.rs | 16 +- src/codegen/dead_storage.rs | 4 +- src/codegen/dispatch/solana.rs | 1 + src/codegen/dispatch/soroban.rs | 11 +- src/codegen/encoding/mod.rs | 2 +- src/codegen/expression.rs | 55 +++- src/codegen/mod.rs | 1 + src/codegen/solana_deploy.rs | 2 + src/codegen/statements/mod.rs | 1 + src/codegen/storage.rs | 11 +- .../subexpression_elimination/instruction.rs | 16 +- src/codegen/yul/tests/expression.rs | 2 + src/emit/binary.rs | 8 + src/emit/instructions.rs | 18 +- src/emit/mod.rs | 4 +- src/emit/polkadot/target.rs | 4 +- src/emit/solana/target.rs | 14 +- src/emit/soroban/target.rs | 32 ++- src/sema/ast.rs | 1 + src/sema/variables.rs | 36 +++ src/sema/yul/tests/expression.rs | 6 + .../soroban_testcases/storage/counter.1.json | 163 ++++++++++++ .../storage/different_storage_types.1.json | 236 ++++++++++++++++++ .../common_subexpression_elimination.sol | 14 +- tests/soroban.rs | 10 +- tests/soroban_testcases/storage.rs | 84 +++++++ 41 files changed, 949 insertions(+), 49 deletions(-) create mode 100644 integration/soroban/runtime_error.spec.js create mode 100644 integration/soroban/storage_types.sol create mode 100644 integration/soroban/storage_types.spec.js create mode 100644 test_snapshots/soroban_testcases/storage/counter.1.json create mode 100644 test_snapshots/soroban_testcases/storage/different_storage_types.1.json diff --git a/fmt/src/formatter.rs b/fmt/src/formatter.rs index 674127642..72cd9b6ac 100644 --- a/fmt/src/formatter.rs +++ b/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(); diff --git a/fmt/src/solang_ext/ast_eq.rs b/fmt/src/solang_ext/ast_eq.rs index d7e3a134c..78fa71550 100644 --- a/fmt/src/solang_ext/ast_eq.rs +++ b/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) _ }} diff --git a/integration/polkadot/try_catch.sol b/integration/polkadot/try_catch.sol index b748f055f..18fe80f7e 100644 --- a/integration/polkadot/try_catch.sol +++ b/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"); diff --git a/integration/soroban/.gitignore b/integration/soroban/.gitignore index ee0ea4517..270feccc9 100644 --- a/integration/soroban/.gitignore +++ b/integration/soroban/.gitignore @@ -1,4 +1,3 @@ -*.js *.so *.key *.json diff --git a/integration/soroban/package.json b/integration/soroban/package.json index dc8303de0..d6d16f60a 100644 --- a/integration/soroban/package.json +++ b/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", diff --git a/integration/soroban/runtime_error.spec.js b/integration/soroban/runtime_error.spec.js new file mode 100644 index 000000000..61bc0e398 --- /dev/null +++ b/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'); + }); + +}); + + diff --git a/integration/soroban/storage_types.sol b/integration/soroban/storage_types.sol new file mode 100644 index 000000000..1035e83f0 --- /dev/null +++ b/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--; + } +} \ No newline at end of file diff --git a/integration/soroban/storage_types.spec.js b/integration/soroban/storage_types.spec.js new file mode 100644 index 000000000..e60cdf5a4 --- /dev/null +++ b/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"); + }); +}); diff --git a/solang-parser/src/helpers/fmt.rs b/solang-parser/src/helpers/fmt.rs index afc500fbb..552b1ce08 100644 --- a/solang-parser/src/helpers/fmt.rs +++ b/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"), + }, } } } diff --git a/solang-parser/src/helpers/loc.rs b/solang-parser/src/helpers/loc.rs index 701023426..357b97e6a 100644 --- a/solang-parser/src/helpers/loc.rs +++ b/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 { + match self { + Self::Persistent(l) | Self::Temporary(l) | Self::Instance(l) => *l, + } + } +} + impl OptionalCodeLocation for pt::SourceUnit { #[inline] fn loc_opt(&self) -> Option { @@ -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, diff --git a/solang-parser/src/lexer.rs b/solang-parser/src/lexer.rs index 1da4c7954..73afb7f59 100644 --- a/solang-parser/src/lexer.rs +++ b/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> { diff --git a/solang-parser/src/pt.rs b/solang-parser/src/pt.rs index 3e8ee9910..4aa14bb2c 100644 --- a/solang-parser/src/pt.rs +++ b/solang-parser/src/pt.rs @@ -955,6 +955,24 @@ pub enum VariableAttribute { /// `ovveride(<1>,*)` Override(Loc, Vec), + + /// 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), + + /// `persistent` + Persistent(Option), + + /// `Instance` + Instance(Option), } /// A variable definition. diff --git a/solang-parser/src/solidity.lalrpop b/solang-parser/src/solidity.lalrpop index c3141dbf4..22221ecf0 100644 --- a/solang-parser/src/solidity.lalrpop +++ b/solang-parser/src/solidity.lalrpop @@ -359,8 +359,15 @@ Visibility: Visibility = { "private" => Visibility::Private(Some(Loc::File(file_no, l, r))), } +StorageType: StorageType = { + "persistent" => StorageType::Persistent(Some(Loc::File(file_no, l, r))), + "temporary" => StorageType::Temporary(Some(Loc::File(file_no, l, r))), + "instance" => StorageType::Instance(Some(Loc::File(file_no, l, r))), +} + VariableAttribute: VariableAttribute = { Visibility => VariableAttribute::Visibility(<>), + StorageType => VariableAttribute::StorageType(<>), "constant" => VariableAttribute::Constant(Loc::File(file_no, l, r)), "immutable" => VariableAttribute::Immutable(Loc::File(file_no, l, r)), "override" => 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, } } diff --git a/solang-parser/src/tests.rs b/solang-parser/src/tests.rs index 91d0b96e5..dea05de0f 100644 --- a/solang-parser/src/tests.rs +++ b/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![] }, diff --git a/src/codegen/cfg.rs b/src/codegen/cfg.rs index 1fcdbc20a..b6d69b55c 100644 --- a/src/codegen/cfg.rs +++ b/src/codegen/cfg.rs @@ -70,6 +70,7 @@ pub enum Instr { res: usize, ty: Type, storage: Expression, + storage_type: Option, }, /// 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, }, /// 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), diff --git a/src/codegen/constant_folding.rs b/src/codegen/constant_folding.rs index 800362b79..8c33a6ea5 100644 --- a/src/codegen/constant_folding.rs +++ b/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(), }; } } diff --git a/src/codegen/dead_storage.rs b/src/codegen/dead_storage.rs index e17256951..b26942240 100644 --- a/src/codegen/dead_storage.rs +++ b/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; diff --git a/src/codegen/dispatch/solana.rs b/src/codegen/dispatch/solana.rs index 5439488c0..fd3b4f4f1 100644 --- a/src/codegen/dispatch/solana.rs +++ b/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, }, ); diff --git a/src/codegen/dispatch/soroban.rs b/src/codegen/dispatch/soroban.rs index 83c2fd4cd..58520b9b3 100644 --- a/src/codegen/dispatch/soroban.rs +++ b/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(), }; diff --git a/src/codegen/encoding/mod.rs b/src/codegen/encoding/mod.rs index dab27bb6f..649263f67 100644 --- a/src/codegen/encoding/mod.rs +++ b/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 diff --git a/src/codegen/expression.rs b/src/codegen/expression.rs index 6ed45ced0..5913fcfe0 100644 --- a/src/codegen/expression.rs +++ b/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, ) -> 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 { + 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, + } +} diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index 0484cfc01..cf756557e 100644 --- a/src/codegen/mod.rs +++ b/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(), }, ); } diff --git a/src/codegen/solana_deploy.rs b/src/codegen/solana_deploy.rs index 5c821a804..2772bf523 100644 --- a/src/codegen/solana_deploy.rs +++ b/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, }, ); } diff --git a/src/codegen/statements/mod.rs b/src/codegen/statements/mod.rs index e8d3782e3..cd3a67ada 100644 --- a/src/codegen/statements/mod.rs +++ b/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, }, ); diff --git a/src/codegen/storage.rs b/src/codegen/storage.rs index 454bab613..0fd58e3f2 100644 --- a/src/codegen/storage.rs +++ b/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, }, ); diff --git a/src/codegen/subexpression_elimination/instruction.rs b/src/codegen/subexpression_elimination/instruction.rs index 665e784a2..52dfb87cb 100644 --- a/src/codegen/subexpression_elimination/instruction.rs +++ b/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 { diff --git a/src/codegen/yul/tests/expression.rs b/src/codegen/yul/tests/expression.rs index a54c248b8..2aa1d5280 100644 --- a/src/codegen/yul/tests/expression.rs +++ b/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); diff --git a/src/emit/binary.rs b/src/emit/binary.rs index a81035329..1e7dd9cdf 100644 --- a/src/emit/binary.rs +++ b/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!(), } } diff --git a/src/emit/instructions.rs b/src/emit/instructions.rs index c36b266b3..43a47a56e 100644 --- a/src/emit/instructions.rs +++ b/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, diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 09a5ea80b..1dbc42aee 100644 --- a/src/emit/mod.rs +++ b/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, ) -> 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, ); /// Recursively clear storage. The default implementation is for slot-based storage diff --git a/src/emit/polkadot/target.rs b/src/emit/polkadot/target.rs index 70faeaa02..6b36a86e2 100644 --- a/src/emit/polkadot/target.rs +++ b/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, ) -> 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, ) { let slot_ptr = binary .builder diff --git a/src/emit/solana/target.rs b/src/emit/solana/target.rs index 75310d4b9..d36ca92c6 100644 --- a/src/emit/solana/target.rs +++ b/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, ) -> 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, ) { 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 { diff --git a/src/emit/soroban/target.rs b/src/emit/soroban/target.rs index 76dd8a398..b0dcb4585 100644 --- a/src/emit/soroban/target.rs +++ b/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, ) -> 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, ) { 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) -> u64 { + if let Some(storage_type) = storage_type { + match storage_type { + StorageType::Temporary(_) => 0, + StorageType::Persistent(_) => 1, + StorageType::Instance(_) => 2, + } + } else { + 1 + } +} diff --git a/src/sema/ast.rs b/src/sema/ast.rs index 1cfdf3a56..3be779d1c 100644 --- a/src/sema/ast.rs +++ b/src/sema/ast.rs @@ -590,6 +590,7 @@ pub struct Variable { pub initializer: Option, pub assigned: bool, pub read: bool, + pub storage_type: Option, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/sema/variables.rs b/src/sema/variables.rs index bc23a3172..05da734d3 100644 --- a/src/sema/variables.rs +++ b/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 = None; let mut has_immutable: Option = None; let mut is_override: Option<(pt::Loc, Vec)> = None; + let mut storage_type: Option = 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 { diff --git a/src/sema/yul/tests/expression.rs b/src/sema/yul/tests/expression.rs index 9c93ab13e..a6d4f1aa5 100644 --- a/src/sema/yul/tests/expression.rs +++ b/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(); diff --git a/test_snapshots/soroban_testcases/storage/counter.1.json b/test_snapshots/soroban_testcases/storage/counter.1.json new file mode 100644 index 000000000..6f7a5073e --- /dev/null +++ b/test_snapshots/soroban_testcases/storage/counter.1.json @@ -0,0 +1,163 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "bool": false + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "bool": false + }, + "durability": "persistent", + "val": { + "u128": { + "hi": 0, + "lo": 0 + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "491b95249a0a04c1f39cdef85507db307713a9db2e9c422a33dac1c44e6ac17c" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "491b95249a0a04c1f39cdef85507db307713a9db2e9c422a33dac1c44e6ac17c" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": { + "v1": { + "ext": "v0", + "cost_inputs": { + "ext": "v0", + "n_instructions": 91, + "n_functions": 5, + "n_globals": 1, + "n_table_entries": 0, + "n_types": 5, + "n_data_segments": 1, + "n_elem_segments": 0, + "n_imports": 3, + "n_exports": 6, + "n_data_segment_bytes": 115 + } + } + }, + "hash": "491b95249a0a04c1f39cdef85507db307713a9db2e9c422a33dac1c44e6ac17c", + "code": "0061736d01000000011d0560027e7e017e60047e7e7e7e017e60037e7e7e017e6000006000017e021303016c013100000178015f0001016c015f0002030605030404040405030100020609017f01418080c0000b076406066d656d6f7279020027636f756e7465723a3a636f756e7465723a3a636f6e7374727563746f723a3a3836313733316435000305636f756e74000409696e6372656d656e7400050964656372656d656e7400060d5f5f636f6e7374727563746f7200070ae1010502000b1200420042011080808080004208864206840b5a01017e02404200420110808080800042017c22004200520d00418088808000ad42208642048422004284808080a006200042041081808080001a000b4200200042011082808080001a420042011080808080004208864206840b5c01027e0240420042011080808080002200427f7c22012000580d0041c088808000ad42208642048422004284808080b006200042041081808080001a000b4200200142011082808080001a420042011080808080004208864206840b11004200420a42011082808080001a42020b0b7a01004180080b7372756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a353a31372d32372c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31303a31372d32372c0a001e11636f6e7472616374656e766d657461763000000000000000160000000000bf010e636f6e747261637473706563763000000000000000000000000b636f6e7374727563746f72000000000000000000000000000000000000000005636f756e74000000000000000000000100000006000000000000000000000009696e6372656d656e7400000000000000000000010000000600000000000000000000000964656372656d656e7400000000000000000000010000000600000000000000000000000d5f5f636f6e7374727563746f72000000000000000000000100000002008c01046e616d6501650800036c2e310103782e5f02036c2e5f0327636f756e7465723a3a636f756e7465723a3a636f6e7374727563746f723a3a38363137333164350405636f756e740509696e6372656d656e74060964656372656d656e74070d5f5f636f6e7374727563746f72071201000f5f5f737461636b5f706f696e746572090a0100072e726f64617461008e010970726f64756365727302086c616e6775616765010143000c70726f6365737365642d62790105636c616e676131362e302e35202868747470733a2f2f6769746875622e636f6d2f736f6c616e612d6c6162732f6c6c766d2d70726f6a6563742e676974203033386434373262636430623832666637363862353135636337376466623165336133393663613829002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d657874" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/test_snapshots/soroban_testcases/storage/different_storage_types.1.json b/test_snapshots/soroban_testcases/storage/different_storage_types.1.json new file mode 100644 index 000000000..6262cbf63 --- /dev/null +++ b/test_snapshots/soroban_testcases/storage/different_storage_types.1.json @@ -0,0 +1,236 @@ +{ + "generators": { + "address": 1, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "bool": false + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "bool": false + }, + "durability": "temporary", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 15 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "void", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "void", + "durability": "persistent", + "val": "void" + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "error": { + "contract": 0 + } + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": { + "error": { + "contract": 0 + } + }, + "durability": "persistent", + "val": "void" + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBKMUZNFQIAL775XBB2W2GP5CNHBM5YGH6C3XB7AY6SUVO2IBU3VYK2V", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "2cbf08be172731c4b4fa9318600f13b7fc00c2082ab6601bf4c736406fc5c528" + }, + "storage": [ + { + "key": { + "bool": true + }, + "val": { + "bool": true + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "2cbf08be172731c4b4fa9318600f13b7fc00c2082ab6601bf4c736406fc5c528" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": { + "v1": { + "ext": "v0", + "cost_inputs": { + "ext": "v0", + "n_instructions": 293, + "n_functions": 8, + "n_globals": 1, + "n_table_entries": 0, + "n_types": 5, + "n_data_segments": 1, + "n_elem_segments": 0, + "n_imports": 3, + "n_exports": 9, + "n_data_segment_bytes": 498 + } + } + }, + "hash": "2cbf08be172731c4b4fa9318600f13b7fc00c2082ab6601bf4c736406fc5c528", + "code": "0061736d01000000011d0560027e7e017e60037e7e7e017e60047e7e7e7e017e6000006000017e021303016c01310000016c015f00010178015f0002030908030404040404040405030100020609017f01418080c0000b076909066d656d6f7279020021736573613a3a736573613a3a636f6e7374727563746f723a3a383631373331643500030473657361000405736573613100050573657361320006057365736133000703696e6300080364656300090d5f5f636f6e7374727563746f72000a0acb050802000b1200420042001080808080004208864206840b1200420142021080808080004208864206840b1200420242011080808080004208864206840b1200420342011080808080004208864206840b970201017e02400240024002404200420010808080800042017c2200500d004200200042001081808080001a4201420210808080800042017c2200500d014201200042021081808080001a4202420110808080800042017c2200500d024202200042011081808080001a4203420110808080800042017c2200500d034203200042011081808080001a42020f0b418088808000ad422086420484220042848080809006200042041082808080001a000b41c088808000ad42208642048422004284808080a006200042041082808080001a000b418089808000ad42208642048422004284808080a006200042041082808080001a000b41c089808000ad42208642048422004284808080a006200042041082808080001a000ba70201027e0240024002400240420042001080808080002200427f7c22012000560d004200200142001081808080001a420142021080808080002200427f7c22012000560d014201200142021081808080001a420242011080808080002200427f7c22012000560d024202200142011081808080001a420342011080808080002200427f7c22012000560d034203200142011081808080001a42020f0b41808a808000ad42208642048422004284808080a006200042041082808080001a000b41c08a808000ad42208642048422004284808080a006200042041082808080001a000b41808b808000ad42208642048422004284808080a006200042041082808080001a000b41c08b808000ad42208642048422004284808080a006200042041082808080001a000b38004200420142001081808080001a4201420142021081808080001a4202420242011081808080001a4203420242011081808080001a42020b0bfa0301004180080bf20372756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a393a392d31352c0a00000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31303a392d31362c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31313a392d31362c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31323a392d31362c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31363a392d31352c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31373a392d31362c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31383a392d31362c0a000000000000000000000000000072756e74696d655f6572726f723a206d617468206f766572666c6f7720696e20746573742e736f6c3a31393a392d31362c0a001e11636f6e7472616374656e766d6574617630000000000000001600000000008b020e636f6e747261637473706563763000000000000000000000000b636f6e7374727563746f7200000000000000000000000000000000000000000473657361000000000000000100000006000000000000000000000005736573613100000000000000000000010000000600000000000000000000000573657361320000000000000000000001000000060000000000000000000000057365736133000000000000000000000100000006000000000000000000000003696e63000000000000000001000000020000000000000000000000036465630000000000000000010000000200000000000000000000000d5f5f636f6e7374727563746f72000000000000000000000100000002008e01046e616d6501670b00036c2e3101036c2e5f0203782e5f0321736573613a3a736573613a3a636f6e7374727563746f723a3a38363137333164350404736573610505736573613106057365736132070573657361330803696e6309036465630a0d5f5f636f6e7374727563746f72071201000f5f5f737461636b5f706f696e746572090a0100072e726f64617461008e010970726f64756365727302086c616e6775616765010143000c70726f6365737365642d62790105636c616e676131362e302e35202868747470733a2f2f6769746875622e636f6d2f736f6c616e612d6c6162732f6c6c766d2d70726f6a6563742e676974203033386434373262636430623832666637363862353135636337376466623165336133393663613829002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d657874" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/tests/codegen_testcases/solidity/common_subexpression_elimination.sol b/tests/codegen_testcases/solidity/common_subexpression_elimination.sol index c55dd479a..2c78af5b9 100644 --- a/tests/codegen_testcases/solidity/common_subexpression_elimination.sol +++ b/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; } diff --git a/tests/soroban.rs b/tests/soroban.rs index 3492adcd5..63c84388b 100644 --- a/tests/soroban.rs +++ b/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
, + 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) -> Self { let mut env = Self::new(); env.register_contract(contract_wasm); diff --git a/tests/soroban_testcases/storage.rs b/tests/soroban_testcases/storage.rs index b41a9432b..120fcf6d2 100644 --- a/tests/soroban_testcases/storage.rs +++ b/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`")); +}