Pārlūkot izejas kodu

Add Soroban target foundations. (#1602)

This PR is a follow up to this PR: #1138 and #1129 , both of which
discuss adding Soroban as a target for Solang.
The PR addresses three main points:
1- Soroban contracts have no dispatcher (a single point of entry).
Therefore, externally callable functions in the contract should preserve
their original name (if possible).
2- Soroban functions do not have their outputs as pointers in the
inputs. Instead, they return the value at the end of execution.
3-Linking Soroban WASM contracts is pretty much similar to Polkadot's
WASM, but with the following minor differences:
a- public functions are exported.
b- host functions (of course)

The next steps for this PR would be: (to be followed in another PRs)
1- Implement host function invocations. (The VM interface)
2- Implement XDR encoding and decoding.
3- Add Integration tests, and complete the mock VM implementation in
soroban_tests.

---------

Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
salaheldinsoliman 1 gadu atpakaļ
vecāks
revīzija
e6a21876d6

+ 5 - 3
.github/workflows/test.yml

@@ -73,9 +73,11 @@ jobs:
     - name: Run cargo clippy
       run: cargo clippy --workspace --tests --bins -- -D warnings
     - name: Run cargo clippy without wasm_opt feature
-      run: cargo clippy --workspace --no-default-features --features language_server,llvm --bins -- -D warnings
+      run: cargo clippy --workspace --no-default-features --features language_server,llvm,soroban --bins -- -D warnings
     - name: Run cargo clippy without llvm feature
       run: cargo clippy --workspace --no-default-features --lib -- -D warnings
+    - name: Run cargo clippy with only soroban feature
+      run: cargo clippy --workspace --no-default-features --features soroban,llvm --lib -- -D warnings
     - name: Run cargo doc
       run: cargo doc --workspace --bins
     - name: Run cargo fmt
@@ -87,7 +89,7 @@ jobs:
         cargo build
     - name: Run tests
       if: always()
-      run: cargo llvm-cov --all-features --workspace --no-report
+      run: cargo llvm-cov --all-features --workspace --no-report --jobs 2
     - name: Upload binary
       uses: actions/upload-artifact@v3.1.0
       with:
@@ -181,7 +183,7 @@ jobs:
     - name: Run tests
       run: cargo test --verbose --workspace
     - name: Run tests without wasm_opt
-      run: cargo test --verbose --workspace --no-default-features --features language_server,llvm
+      run: cargo test --verbose --workspace --no-default-features --features language_server,llvm,soroban
     - uses: actions/upload-artifact@v3.1.0
       with:
         name: solang-mac-arm

+ 3 - 1
CHANGELOG.md

@@ -5,6 +5,8 @@ will be documented here.
 ## Unreleased
 
 ### Added
+- **Soroban** Work on adding support for [Stellar's Soroban](https://soroban.stellar.org/docs) contracts platforms started, by adding a skeleton that supports the Soroban runtime. [Salaheldin Soliman](https://github.com/salaheldinsoliman)
+
 - The `string.concat()` and `bytes.concat()` builtin functions are supported. [seanyoung](https://github.com/seanyoung)
 
 ### Changed
@@ -532,4 +534,4 @@ substrate contracts node `v0.22.1`.
 
 ### Changed
 - Solang now uses llvm 10.0 rather than llvm 8.0
-- In line with Solidity 0.7.0, constructors no longer need a visibility argument
+- In line with Solidity 0.7.0, constructors no longer need a visibility argument

+ 3 - 1
Cargo.toml

@@ -71,6 +71,7 @@ forge-fmt = { version = "0.2.0", optional = true }
 # We don't use ethers-core directly, but need the correct version for the
 # build to work.
 ethers-core = { version = "2.0.10", optional = true }
+soroban-sdk = { version = "20.0.0-rc2", features = ["testutils"], optional = true }
 
 [dev-dependencies]
 num-derive = "0.4"
@@ -99,7 +100,8 @@ no-default-features = true
 lto = true
 
 [features]
-default = ["llvm", "wasm_opt", "language_server"]
+soroban = ["soroban-sdk"]
+default = ["llvm", "wasm_opt", "language_server", "soroban"]
 llvm = ["inkwell", "libc"]
 wasm_opt = ["llvm", "wasm-opt", "contract-build"]
 language_server = ["tower-lsp", "forge-fmt", "ethers-core", "tokio", "rust-lapper"]

+ 2 - 1
src/bin/cli/mod.rs

@@ -277,7 +277,7 @@ pub struct TargetArg {
 
 #[derive(Args, Deserialize, Debug, PartialEq)]
 pub struct CompileTargetArg {
-    #[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
+    #[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm", "soroban"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
     pub name: Option<String>,
 
     #[arg(name = "ADDRESS_LENGTH", help = "Address length on the Polkadot Parachain", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
@@ -463,6 +463,7 @@ pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
             value_length: target_arg.get_value_length().unwrap_or(16) as usize,
         },
         "evm" => solang::Target::EVM,
+        "soroban" => solang::Target::Soroban,
         _ => unreachable!(),
     };
 

+ 1 - 1
src/bin/solang.rs

@@ -405,7 +405,7 @@ fn contract_results(
 
     let context = inkwell::context::Context::create();
 
-    let binary = resolved_contract.binary(ns, &context, opt);
+    let binary = resolved_contract.binary(ns, &context, opt, contract_no);
 
     if save_intermediates(&binary, compiler_output) {
         return;

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

@@ -17,5 +17,6 @@ pub(super) fn function_dispatch(
         Target::Polkadot { .. } | Target::EVM => {
             polkadot::function_dispatch(contract_no, all_cfg, ns, opt)
         }
+        Target::Soroban => vec![],
     }
 }

+ 2 - 0
src/codegen/events/mod.rs

@@ -50,5 +50,7 @@ pub(super) fn new_event_emitter<'a>(
             ns,
             event_no,
         }),
+
+        Target::Soroban => todo!(),
     }
 }

+ 1 - 1
src/codegen/expression.rs

@@ -3651,7 +3651,7 @@ fn array_literal_to_memory_array(
 fn code(loc: &Loc, contract_no: usize, ns: &Namespace, opt: &Options) -> Expression {
     let contract = &ns.contracts[contract_no];
 
-    let code = contract.emit(ns, opt);
+    let code = contract.emit(ns, opt, contract_no);
 
     let size = Expression::NumberLiteral {
         loc: *loc,

+ 15 - 1
src/emit/binary.rs

@@ -39,6 +39,9 @@ use inkwell::OptimizationLevel;
 use once_cell::sync::OnceCell;
 use solang_parser::pt;
 
+#[cfg(feature = "soroban")]
+use super::soroban;
+
 static LLVM_INIT: OnceCell<()> = OnceCell::new();
 
 #[macro_export]
@@ -168,6 +171,7 @@ impl<'a> Binary<'a> {
         contract: &'a Contract,
         ns: &'a Namespace,
         opt: &'a Options,
+        _contract_no: usize,
     ) -> Self {
         let std_lib = load_stdlib(context, &ns.target);
         match ns.target {
@@ -175,7 +179,11 @@ impl<'a> Binary<'a> {
                 polkadot::PolkadotTarget::build(context, &std_lib, contract, ns, opt)
             }
             Target::Solana => solana::SolanaTarget::build(context, &std_lib, contract, ns, opt),
-            Target::EVM => unimplemented!(),
+            #[cfg(feature = "soroban")]
+            Target::Soroban => {
+                soroban::SorobanTarget::build(context, &std_lib, contract, ns, opt, _contract_no)
+            }
+            _ => unimplemented!("target not implemented"),
         }
     }
 
@@ -758,6 +766,12 @@ impl<'a> Binary<'a> {
             .map(|ty| self.llvm_var_ty(ty, ns).into())
             .collect::<Vec<BasicMetadataTypeEnum>>();
 
+        if ns.target == Target::Soroban {
+            match returns.iter().next() {
+                Some(ret) => return self.llvm_type(ret, ns).fn_type(&args, false),
+                None => return self.context.void_type().fn_type(&args, false),
+            }
+        }
         // add return values
         for ty in returns {
             args.push(if ty.is_reference_type(ns) && !ty.is_contract_storage() {

+ 11 - 2
src/emit/instructions.rs

@@ -36,11 +36,11 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
 ) {
     match ins {
         Instr::Nop => (),
-        Instr::Return { value } if value.is_empty() => {
+        Instr::Return { value } if value.is_empty() && ns.target != Target::Soroban => {
             bin.builder
                 .build_return(Some(&bin.return_values[&ReturnCode::Success]));
         }
-        Instr::Return { value } => {
+        Instr::Return { value } if ns.target != Target::Soroban => {
             let returns_offset = cfg.params.len();
             for (i, val) in value.iter().enumerate() {
                 let arg = function.get_nth_param((returns_offset + i) as u32).unwrap();
@@ -52,6 +52,15 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
             bin.builder
                 .build_return(Some(&bin.return_values[&ReturnCode::Success]));
         }
+        Instr::Return { value } => match value.iter().next() {
+            Some(val) => {
+                let retval = expression(target, bin, val, &w.vars, function, ns);
+                bin.builder.build_return(Some(&retval));
+            }
+            None => {
+                bin.builder.build_return(None);
+            }
+        },
         Instr::Set { res, expr, .. } => {
             if let Expression::Undefined { ty: expr_type } = expr {
                 // If the variable has been declared as undefined, but we can

+ 7 - 3
src/emit/mod.rs

@@ -23,6 +23,9 @@ mod loop_builder;
 mod math;
 pub mod polkadot;
 pub mod solana;
+
+#[cfg(feature = "soroban")]
+pub mod soroban;
 mod storage;
 mod strings;
 
@@ -366,12 +369,13 @@ impl ast::Contract {
         ns: &'a ast::Namespace,
         context: &'a inkwell::context::Context,
         opt: &'a Options,
+        contract_no: usize,
     ) -> binary::Binary {
-        binary::Binary::build(context, self, ns, opt)
+        binary::Binary::build(context, self, ns, opt, contract_no)
     }
 
     /// Generate the final program code for the contract
-    pub fn emit(&self, ns: &ast::Namespace, opt: &Options) -> Vec<u8> {
+    pub fn emit(&self, ns: &ast::Namespace, opt: &Options, contract_no: usize) -> Vec<u8> {
         if ns.target == Target::EVM {
             return vec![];
         }
@@ -379,7 +383,7 @@ impl ast::Contract {
         self.code
             .get_or_init(move || {
                 let context = inkwell::context::Context::create();
-                let binary = self.binary(ns, &context, opt);
+                let binary = self.binary(ns, &context, opt, contract_no);
                 binary.code(Generate::Linked).expect("llvm build")
             })
             .to_vec()

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

@@ -815,7 +815,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
 
         let created_contract = &ns.contracts[contract_no];
 
-        let code = created_contract.emit(ns, binary.options);
+        let code = created_contract.emit(ns, binary.options, contract_no);
 
         let (scratch_buf, scratch_len) = scratch_buf!();
 

+ 195 - 0
src/emit/soroban/mod.rs

@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub(super) mod target;
+use crate::codegen::cfg::ControlFlowGraph;
+use crate::emit::cfg::emit_cfg;
+use crate::{
+    codegen::{cfg::ASTFunction, Options},
+    emit::Binary,
+    sema::ast,
+};
+use inkwell::{
+    context::Context,
+    module::{Linkage, Module},
+};
+use soroban_sdk::xdr::{
+    DepthLimitedWrite, ScEnvMetaEntry, ScSpecEntry, ScSpecFunctionInputV0, ScSpecFunctionV0,
+    ScSpecTypeDef, StringM, WriteXdr,
+};
+
+const SOROBAN_ENV_INTERFACE_VERSION: u64 = 85899345977;
+
+pub struct SorobanTarget;
+
+impl SorobanTarget {
+    pub fn build<'a>(
+        context: &'a Context,
+        std_lib: &Module<'a>,
+        contract: &'a ast::Contract,
+        ns: &'a ast::Namespace,
+        opt: &'a Options,
+        contract_no: usize,
+    ) -> Binary<'a> {
+        let filename = ns.files[contract.loc.file_no()].file_name();
+        let mut binary = Binary::new(
+            context,
+            ns.target,
+            &contract.id.name,
+            &filename,
+            opt,
+            std_lib,
+            None,
+        );
+
+        Self::emit_functions_with_spec(contract, &mut binary, ns, context, contract_no);
+        Self::emit_env_meta_entries(context, &mut binary);
+
+        binary
+    }
+
+    // In Soroban, the public functions specifications is embeded in the contract binary.
+    // for each function, emit both the function spec entry and the function body.
+    fn emit_functions_with_spec<'a>(
+        contract: &'a ast::Contract,
+        binary: &mut Binary<'a>,
+        ns: &'a ast::Namespace,
+        context: &'a Context,
+        contract_no: usize,
+    ) {
+        let mut defines = Vec::new();
+
+        for (cfg_no, cfg) in contract.cfg.iter().enumerate() {
+            let ftype = binary.function_type(
+                &cfg.params.iter().map(|p| p.ty.clone()).collect::<Vec<_>>(),
+                &cfg.returns.iter().map(|p| p.ty.clone()).collect::<Vec<_>>(),
+                ns,
+            );
+
+            // For each function, determine the name and the linkage
+            // Soroban has no dispatcher, so all externally addressable functions are exported and should be named the same as the original function name in the source code.
+            // If there are duplicate function names, then the function name in the source is mangled to include the signature.
+            let default_constructor = ns.default_constructor(contract_no);
+            let name = {
+                if cfg.public {
+                    let f = match &cfg.function_no {
+                        ASTFunction::SolidityFunction(no) | ASTFunction::YulFunction(no) => {
+                            &ns.functions[*no]
+                        }
+                        _ => &default_constructor,
+                    };
+
+                    if f.mangled_name_contracts.contains(&contract_no) {
+                        &f.mangled_name
+                    } else {
+                        &f.id.name
+                    }
+                } else {
+                    &cfg.name
+                }
+            };
+
+            Self::emit_function_spec_entry(context, cfg, name.clone(), binary);
+
+            let linkage = if cfg.public {
+                Linkage::External
+            } else {
+                Linkage::Internal
+            };
+
+            let func_decl = if let Some(func) = binary.module.get_function(name) {
+                // must not have a body yet
+                assert_eq!(func.get_first_basic_block(), None);
+
+                func
+            } else {
+                binary.module.add_function(name, ftype, Some(linkage))
+            };
+
+            binary.functions.insert(cfg_no, func_decl);
+
+            defines.push((func_decl, cfg));
+        }
+
+        for (func_decl, cfg) in defines {
+            emit_cfg(&mut SorobanTarget, binary, contract, cfg, func_decl, ns);
+        }
+    }
+
+    fn emit_env_meta_entries<'a>(context: &'a Context, binary: &mut Binary<'a>) {
+        let mut meta = DepthLimitedWrite::new(Vec::new(), 10);
+        ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(SOROBAN_ENV_INTERFACE_VERSION)
+            .write_xdr(&mut meta)
+            .expect("writing env meta interface version to xdr");
+        Self::add_custom_section(context, &binary.module, "contractenvmetav0", meta.inner);
+    }
+
+    fn emit_function_spec_entry<'a>(
+        context: &'a Context,
+        cfg: &'a ControlFlowGraph,
+        name: String,
+        binary: &mut Binary<'a>,
+    ) {
+        if cfg.public && !cfg.is_placeholder() {
+            // TODO: Emit custom type spec entries.
+            let mut spec = DepthLimitedWrite::new(Vec::new(), 10);
+            ScSpecEntry::FunctionV0(ScSpecFunctionV0 {
+                name: name
+                    .try_into()
+                    .unwrap_or_else(|_| panic!("function name {:?} exceeds limit", cfg.name)),
+                inputs: cfg
+                    .params
+                    .iter()
+                    .enumerate()
+                    .map(|(i, p)| ScSpecFunctionInputV0 {
+                        name: p
+                            .id
+                            .as_ref()
+                            .map(|id| id.to_string())
+                            .unwrap_or_else(|| i.to_string())
+                            .try_into()
+                            .expect("function input name exceeds limit"),
+                        type_: ScSpecTypeDef::U32, // TODO: Map type.
+                        doc: StringM::default(),   // TODO: Add doc.
+                    })
+                    .collect::<Vec<_>>()
+                    .try_into()
+                    .expect("function input count exceeds limit"),
+                outputs: cfg
+                    .returns
+                    .iter()
+                    .map(|_| ScSpecTypeDef::U32) // TODO: Map type.
+                    .collect::<Vec<_>>()
+                    .try_into()
+                    .expect("function output count exceeds limit"),
+                doc: StringM::default(), // TODO: Add doc.
+            })
+            .write_xdr(&mut spec)
+            .unwrap_or_else(|_| panic!("writing spec to xdr for function {}", cfg.name));
+
+            Self::add_custom_section(context, &binary.module, "contractspecv0", spec.inner);
+        }
+    }
+
+    fn add_custom_section<'a>(
+        context: &'a Context,
+        module: &Module<'a>,
+        name: &'a str,
+        value: Vec<u8>,
+    ) {
+        let value_str = unsafe {
+            // TODO: Figure out the right way to generate the LLVM metadata for
+            // a slice of bytes.
+            String::from_utf8_unchecked(value)
+        };
+
+        module
+            .add_global_metadata(
+                "wasm.custom_sections",
+                &context.metadata_node(&[
+                    context.metadata_string(name).into(),
+                    context.metadata_string(&value_str).into(),
+                ]),
+            )
+            .expect("adding spec as metadata");
+    }
+}

+ 335 - 0
src/emit/soroban/target.rs

@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::codegen::cfg::HashTy;
+use crate::codegen::Expression;
+use crate::emit::binary::Binary;
+use crate::emit::soroban::SorobanTarget;
+use crate::emit::ContractArgs;
+use crate::emit::{TargetRuntime, Variable};
+use crate::sema::ast;
+use crate::sema::ast::CallTy;
+use crate::sema::ast::{Function, Namespace, Type};
+use inkwell::types::{BasicTypeEnum, IntType};
+use inkwell::values::{
+    ArrayValue, BasicMetadataValueEnum, BasicValueEnum, FunctionValue, IntValue, PointerValue,
+};
+use solang_parser::pt::Loc;
+use std::collections::HashMap;
+
+// TODO: Implement TargetRuntime for SorobanTarget.
+#[allow(unused_variables)]
+impl<'a> TargetRuntime<'a> for SorobanTarget {
+    fn get_storage_int(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: PointerValue<'a>,
+        ty: IntType<'a>,
+    ) -> IntValue<'a> {
+        unimplemented!()
+    }
+
+    fn storage_load(
+        &self,
+        binary: &Binary<'a>,
+        ty: &ast::Type,
+        slot: &mut IntValue<'a>,
+        function: FunctionValue<'a>,
+        ns: &ast::Namespace,
+    ) -> BasicValueEnum<'a> {
+        unimplemented!()
+    }
+
+    /// Recursively store a type to storage
+    fn storage_store(
+        &self,
+        binary: &Binary<'a>,
+        ty: &ast::Type,
+        existing: bool,
+        slot: &mut IntValue<'a>,
+        dest: BasicValueEnum<'a>,
+        function: FunctionValue<'a>,
+        ns: &ast::Namespace,
+    ) {
+        unimplemented!()
+    }
+
+    /// Recursively clear storage. The default implementation is for slot-based storage
+    fn storage_delete(
+        &self,
+        bin: &Binary<'a>,
+        ty: &Type,
+        slot: &mut IntValue<'a>,
+        function: FunctionValue<'a>,
+        ns: &Namespace,
+    ) {
+        unimplemented!()
+    }
+
+    // Bytes and string have special storage layout
+    fn set_storage_string(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue<'a>,
+        slot: PointerValue<'a>,
+        dest: BasicValueEnum<'a>,
+    ) {
+        unimplemented!()
+    }
+
+    fn get_storage_string(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: PointerValue<'a>,
+    ) -> PointerValue<'a> {
+        unimplemented!()
+    }
+
+    fn set_storage_extfunc(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: PointerValue,
+        dest: PointerValue,
+        dest_ty: BasicTypeEnum,
+    ) {
+        unimplemented!()
+    }
+
+    fn get_storage_extfunc(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: PointerValue<'a>,
+        ns: &Namespace,
+    ) -> PointerValue<'a> {
+        unimplemented!()
+    }
+
+    fn get_storage_bytes_subscript(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: IntValue<'a>,
+        index: IntValue<'a>,
+        loc: Loc,
+        ns: &Namespace,
+    ) -> IntValue<'a> {
+        unimplemented!()
+    }
+
+    fn set_storage_bytes_subscript(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue,
+        slot: IntValue<'a>,
+        index: IntValue<'a>,
+        value: IntValue<'a>,
+        ns: &Namespace,
+        loc: Loc,
+    ) {
+        unimplemented!()
+    }
+
+    fn storage_subscript(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue<'a>,
+        ty: &Type,
+        slot: IntValue<'a>,
+        index: BasicValueEnum<'a>,
+        ns: &Namespace,
+    ) -> IntValue<'a> {
+        unimplemented!()
+    }
+
+    fn storage_push(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue<'a>,
+        ty: &Type,
+        slot: IntValue<'a>,
+        val: Option<BasicValueEnum<'a>>,
+        ns: &Namespace,
+    ) -> BasicValueEnum<'a> {
+        unimplemented!()
+    }
+
+    fn storage_pop(
+        &self,
+        bin: &Binary<'a>,
+        function: FunctionValue<'a>,
+        ty: &Type,
+        slot: IntValue<'a>,
+        load: bool,
+        ns: &Namespace,
+        loc: Loc,
+    ) -> Option<BasicValueEnum<'a>> {
+        unimplemented!()
+    }
+
+    fn storage_array_length(
+        &self,
+        _bin: &Binary<'a>,
+        _function: FunctionValue,
+        _slot: IntValue<'a>,
+        _elem_ty: &Type,
+        _ns: &Namespace,
+    ) -> IntValue<'a> {
+        unimplemented!()
+    }
+
+    /// keccak256 hash
+    fn keccak256_hash(
+        &self,
+        bin: &Binary<'a>,
+        src: PointerValue,
+        length: IntValue,
+        dest: PointerValue,
+        ns: &Namespace,
+    ) {
+        unimplemented!()
+    }
+
+    /// Prints a string
+    fn print(&self, bin: &Binary, string: PointerValue, length: IntValue) {
+        unimplemented!()
+    }
+
+    /// Return success without any result
+    fn return_empty_abi(&self, bin: &Binary) {
+        unimplemented!()
+    }
+
+    /// Return failure code
+    fn return_code<'b>(&self, bin: &'b Binary, ret: IntValue<'b>) {
+        unimplemented!()
+    }
+
+    /// Return failure without any result
+    fn assert_failure(&self, bin: &Binary, data: PointerValue, length: IntValue) {
+        unimplemented!()
+    }
+
+    fn builtin_function(
+        &self,
+        binary: &Binary<'a>,
+        function: FunctionValue<'a>,
+        builtin_func: &Function,
+        args: &[BasicMetadataValueEnum<'a>],
+        first_arg_type: BasicTypeEnum,
+        ns: &Namespace,
+    ) -> Option<BasicValueEnum<'a>> {
+        unimplemented!()
+    }
+
+    /// Calls constructor
+    fn create_contract<'b>(
+        &mut self,
+        bin: &Binary<'b>,
+        function: FunctionValue<'b>,
+        success: Option<&mut BasicValueEnum<'b>>,
+        contract_no: usize,
+        address: PointerValue<'b>,
+        encoded_args: BasicValueEnum<'b>,
+        encoded_args_len: BasicValueEnum<'b>,
+        contract_args: ContractArgs<'b>,
+        ns: &Namespace,
+        loc: Loc,
+    ) {
+        unimplemented!()
+    }
+
+    /// call external function
+    fn external_call<'b>(
+        &self,
+        bin: &Binary<'b>,
+        function: FunctionValue<'b>,
+        success: Option<&mut BasicValueEnum<'b>>,
+        payload: PointerValue<'b>,
+        payload_len: IntValue<'b>,
+        address: Option<PointerValue<'b>>,
+        contract_args: ContractArgs<'b>,
+        ty: CallTy,
+        ns: &Namespace,
+        loc: Loc,
+    ) {
+        unimplemented!()
+    }
+
+    /// send value to address
+    fn value_transfer<'b>(
+        &self,
+        _bin: &Binary<'b>,
+        _function: FunctionValue,
+        _success: Option<&mut BasicValueEnum<'b>>,
+        _address: PointerValue<'b>,
+        _value: IntValue<'b>,
+        _ns: &Namespace,
+        loc: Loc,
+    ) {
+        unimplemented!()
+    }
+
+    /// builtin expressions
+    fn builtin<'b>(
+        &self,
+        bin: &Binary<'b>,
+        expr: &Expression,
+        vartab: &HashMap<usize, Variable<'b>>,
+        function: FunctionValue<'b>,
+        ns: &Namespace,
+    ) -> BasicValueEnum<'b> {
+        unimplemented!()
+    }
+
+    /// Return the return data from an external call (either revert error or return values)
+    fn return_data<'b>(&self, bin: &Binary<'b>, function: FunctionValue<'b>) -> PointerValue<'b> {
+        unimplemented!()
+    }
+
+    /// Return the value we received
+    fn value_transferred<'b>(&self, binary: &Binary<'b>, ns: &Namespace) -> IntValue<'b> {
+        unimplemented!()
+    }
+
+    /// Terminate execution, destroy bin and send remaining funds to addr
+    fn selfdestruct<'b>(&self, binary: &Binary<'b>, addr: ArrayValue<'b>, ns: &Namespace) {
+        unimplemented!()
+    }
+
+    /// Crypto Hash
+    fn hash<'b>(
+        &self,
+        bin: &Binary<'b>,
+        function: FunctionValue<'b>,
+        hash: HashTy,
+        string: PointerValue<'b>,
+        length: IntValue<'b>,
+        ns: &Namespace,
+    ) -> IntValue<'b> {
+        unimplemented!()
+    }
+
+    /// Emit event
+    fn emit_event<'b>(
+        &self,
+        bin: &Binary<'b>,
+        function: FunctionValue<'b>,
+        data: BasicValueEnum<'b>,
+        topics: &[BasicValueEnum<'b>],
+    ) {
+        unimplemented!()
+    }
+
+    /// Return ABI encoded data
+    fn return_abi_data<'b>(
+        &self,
+        binary: &Binary<'b>,
+        data: PointerValue<'b>,
+        data_len: BasicValueEnum<'b>,
+    ) {
+        unimplemented!()
+    }
+}

+ 4 - 1
src/lib.rs

@@ -31,6 +31,7 @@ pub enum Target {
     },
     /// Ethereum EVM, see <https://ethereum.org/en/developers/docs/evm/>
     EVM,
+    Soroban,
 }
 
 impl fmt::Display for Target {
@@ -39,6 +40,7 @@ impl fmt::Display for Target {
             Target::Solana => write!(f, "Solana"),
             Target::Polkadot { .. } => write!(f, "Polkadot"),
             Target::EVM => write!(f, "EVM"),
+            Target::Soroban => write!(f, "Soroban"),
         }
     }
 }
@@ -51,6 +53,7 @@ impl PartialEq for Target {
             Target::Solana => matches!(other, Target::Solana),
             Target::Polkadot { .. } => matches!(other, Target::Polkadot { .. }),
             Target::EVM => matches!(other, Target::EVM),
+            Target::Soroban => matches!(other, Target::Soroban),
         }
     }
 }
@@ -144,7 +147,7 @@ pub fn compile(
         let contract = &ns.contracts[contract_no];
 
         if contract.instantiable {
-            let code = contract.emit(&ns, opts);
+            let code = contract.emit(&ns, opts, contract_no);
 
             let (abistr, _) = abi::generate_abi(contract_no, &ns, &code, false, &authors, version);
 

+ 10 - 6
src/linker/mod.rs

@@ -1,8 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
 mod bpf;
-mod wasm;
-
+mod polkadot_wasm;
+mod soroban_wasm;
 use crate::Target;
 use once_cell::sync::Lazy;
 use std::ffi::CString;
@@ -16,10 +16,14 @@ pub fn link(input: &[u8], name: &str, target: Target) -> Vec<u8> {
     // We should fix this one day
     let _lock = LINKER_MUTEX.lock().unwrap();
 
-    if target == Target::Solana {
-        bpf::link(input, name)
-    } else {
-        wasm::link(input, name)
+    match target {
+        Target::Solana => bpf::link(input, name),
+        Target::Soroban => soroban_wasm::link(input, name),
+        Target::Polkadot {
+            address_length: _,
+            value_length: _,
+        } => polkadot_wasm::link(input, name),
+        _ => panic!("linker not implemented for target {:?}", target),
     }
 }
 

+ 0 - 2
src/linker/wasm.rs → src/linker/polkadot_wasm.rs

@@ -31,7 +31,6 @@ pub fn link(input: &[u8], name: &str) -> Vec<u8> {
         CString::new("--gc-sections").unwrap(),
         CString::new("--global-base=0").unwrap(),
     ];
-
     command_line.push(CString::new("--export").unwrap());
     command_line.push(CString::new("deploy").unwrap());
     command_line.push(CString::new("--export").unwrap());
@@ -40,7 +39,6 @@ pub fn link(input: &[u8], name: &str) -> Vec<u8> {
     command_line.push(CString::new("--import-memory").unwrap());
     command_line.push(CString::new("--initial-memory=1048576").unwrap());
     command_line.push(CString::new("--max-memory=1048576").unwrap());
-
     command_line.push(
         CString::new(
             object_filename

+ 54 - 0
src/linker/soroban_wasm.rs

@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use std::ffi::CString;
+use std::fs::File;
+use std::io::Read;
+use std::io::Write;
+use tempfile::tempdir;
+
+pub fn link(input: &[u8], name: &str) -> Vec<u8> {
+    let dir = tempdir().expect("failed to create temp directory for linking");
+
+    let object_filename = dir.path().join(format!("{name}.o"));
+    let res_filename = dir.path().join(format!("{name}.wasm"));
+
+    let mut objectfile =
+        File::create(object_filename.clone()).expect("failed to create object file");
+
+    objectfile
+        .write_all(input)
+        .expect("failed to write object file to temp file");
+
+    let mut command_line = vec![
+        CString::new("-O3").unwrap(),
+        CString::new("--no-entry").unwrap(),
+        CString::new("--allow-undefined").unwrap(),
+        CString::new("--gc-sections").unwrap(),
+        CString::new("--global-base=0").unwrap(),
+    ];
+    command_line.push(CString::new("--export-dynamic").unwrap());
+
+    command_line.push(
+        CString::new(
+            object_filename
+                .to_str()
+                .expect("temp path should be unicode"),
+        )
+        .unwrap(),
+    );
+    command_line.push(CString::new("-o").unwrap());
+    command_line
+        .push(CString::new(res_filename.to_str().expect("temp path should be unicode")).unwrap());
+
+    assert!(!super::wasm_linker(&command_line), "linker failed");
+
+    let mut output = Vec::new();
+    // read the whole file
+    let mut outputfile = File::open(res_filename).expect("output file should exist");
+
+    outputfile
+        .read_to_end(&mut output)
+        .expect("failed to read output file");
+
+    output
+}

+ 3 - 0
src/sema/builtin.rs

@@ -1707,6 +1707,9 @@ impl Namespace {
         ));
     }
 
+    pub fn add_soroban_builtins(&mut self) {
+        // TODO: add soroban builtins
+    }
     pub fn add_polkadot_builtins(&mut self) {
         let loc = pt::Loc::Builtin;
         let identifier = |name: &str| Identifier {

+ 2 - 0
src/sema/namespace.rs

@@ -41,6 +41,7 @@ impl Namespace {
                 value_length,
             } => (address_length, value_length),
             Target::Solana => (32, 8),
+            Target::Soroban => (32, 8),
         };
 
         let mut ns = Namespace {
@@ -70,6 +71,7 @@ impl Namespace {
         match target {
             Target::Solana => ns.add_solana_builtins(),
             Target::Polkadot { .. } => ns.add_polkadot_builtins(),
+            Target::Soroban => ns.add_soroban_builtins(),
             _ => {}
         }
 

+ 1 - 0
src/sema/yul/builtin.rs

@@ -20,6 +20,7 @@ impl YulBuiltinPrototype {
             Target::EVM => self.availability[0],
             Target::Polkadot { .. } => self.availability[1],
             Target::Solana => self.availability[2],
+            Target::Soroban => unimplemented!(),
         }
     }
 }

+ 4 - 1
tests/contract.rs

@@ -99,9 +99,12 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
             if contract.instantiable {
                 let code = match ns.target {
                     Target::Solana | Target::Polkadot { .. } => {
-                        contract.emit(&ns, &Default::default())
+                        contract.emit(&ns, &Default::default(), contract_no)
                     }
                     Target::EVM => b"beep".to_vec(),
+                    Target::Soroban => {
+                        todo!()
+                    }
                 };
 
                 let _ = generate_abi(contract_no, &ns, &code, false, &["unknown".into()], "0.1.0");

+ 83 - 0
tests/soroban.rs

@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#[cfg(feature = "soroban")]
+pub mod soroban_testcases;
+
+use solang::codegen::Options;
+use solang::file_resolver::FileResolver;
+use solang::{compile, Target};
+use soroban_sdk::{vec, Address, Env, Symbol, Val};
+use std::ffi::OsStr;
+
+// TODO: register accounts, related balances, events, etc.
+pub struct SorobanEnv {
+    env: Env,
+    contracts: Vec<Address>,
+}
+
+pub fn build_solidity(src: &str) -> SorobanEnv {
+    let tmp_file = OsStr::new("test.sol");
+    let mut cache = FileResolver::default();
+    cache.set_file_contents(tmp_file.to_str().unwrap(), src.to_string());
+    let opt = inkwell::OptimizationLevel::Default;
+    let target = Target::Soroban;
+    let (wasm, ns) = compile(
+        tmp_file,
+        &mut cache,
+        target,
+        &Options {
+            opt_level: opt.into(),
+            log_api_return_codes: false,
+            log_runtime_errors: false,
+            log_prints: true,
+            #[cfg(feature = "wasm_opt")]
+            wasm_opt: Some(contract_build::OptimizationPasses::Z),
+            ..Default::default()
+        },
+        std::vec!["unknown".to_string()],
+        "0.0.1",
+    );
+    ns.print_diagnostics_in_plain(&cache, false);
+    assert!(!wasm.is_empty());
+    let wasm_blob = wasm[0].0.clone();
+    SorobanEnv::new_with_contract(wasm_blob)
+}
+
+impl SorobanEnv {
+    pub fn new() -> Self {
+        Self {
+            env: Env::default(),
+            contracts: Vec::new(),
+        }
+    }
+
+    pub fn new_with_contract(contract_wasm: Vec<u8>) -> Self {
+        let mut env = Self::new();
+        env.register_contract(contract_wasm);
+        env
+    }
+
+    pub fn register_contract(&mut self, contract_wasm: Vec<u8>) -> Address {
+        let addr = self
+            .env
+            .register_contract_wasm(None, contract_wasm.as_slice());
+        self.contracts.push(addr.clone());
+        addr
+    }
+
+    pub fn invoke_contract(&self, addr: &Address, function_name: &str, args: Vec<Val>) -> Val {
+        let func = Symbol::new(&self.env, function_name);
+        let mut args_soroban = vec![&self.env];
+        for arg in args {
+            args_soroban.push_back(arg)
+        }
+        println!("args_soroban: {:?}", args_soroban);
+        self.env.invoke_contract(addr, &func, args_soroban)
+    }
+}
+
+impl Default for SorobanEnv {
+    fn default() -> Self {
+        Self::new()
+    }
+}

+ 78 - 0
tests/soroban_testcases/math.rs

@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::build_solidity;
+use soroban_sdk::Val;
+
+#[test]
+fn math() {
+    let env = build_solidity(
+        r#"contract math {
+        function max(uint64 a, uint64 b) public returns (uint64) {
+            if (a > b) {
+                return a;
+            } else {
+                return b;
+            }
+        }
+    }"#,
+    );
+
+    let addr = env.contracts.last().unwrap();
+    let res = env.invoke_contract(
+        addr,
+        "max",
+        vec![*Val::from_u32(4).as_val(), *Val::from_u32(5).as_val()],
+    );
+    assert!(Val::from_u32(5).as_val().shallow_eq(&res))
+}
+
+#[test]
+fn math_same_name() {
+    let src = build_solidity(
+        r#"contract math {
+        function max(uint64 a, uint64 b) public returns (uint64) {
+            if (a > b) {
+                return a;
+            } else {
+                return b;
+            }
+        }
+    
+        function max(uint64 a, uint64 b, uint64 c) public returns (uint64) {
+            if (a > b) {
+                if (a > c) {
+                    return a;
+                } else {
+                    return c;
+                }
+            } else {
+                if (b > c) {
+                    return b;
+                } else {
+                    return c;
+                }
+            }
+        }
+    }
+    "#,
+    );
+
+    let addr = src.contracts.last().unwrap();
+    let res = src.invoke_contract(
+        addr,
+        "max_uint64_uint64",
+        vec![*Val::from_u32(4).as_val(), *Val::from_u32(5).as_val()],
+    );
+    assert!(Val::from_u32(5).as_val().shallow_eq(&res));
+
+    let res = src.invoke_contract(
+        addr,
+        "max_uint64_uint64_uint64",
+        vec![
+            *Val::from_u32(4).as_val(),
+            *Val::from_u32(5).as_val(),
+            *Val::from_u32(6).as_val(),
+        ],
+    );
+    assert!(Val::from_u32(6).as_val().shallow_eq(&res));
+}

+ 2 - 0
tests/soroban_testcases/mod.rs

@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: Apache-2.0
+mod math;