Forráskód Böngészése

Polkadot: Bubble up reverts (#1454)

This is a continuation of #1415. To align Solang with `solc` further,
any uncaught reverts must be bubbled up back to the caller. To do so,
handling of the return code for `call`, `instantiate` and `transfer`
runtime API calls is now done in codegen and no longer treated as a
bool.

This PR also fixes try-catch in codegen resulting in fixing a bunch of
deactivated tests.

Co-authored-by: Lucas Steuernagel <38472950+LucasSte@users.noreply.github.com>
Cyrill Leutwiler 2 éve
szülő
commit
8180872ee9

+ 1 - 0
.gitignore

@@ -4,6 +4,7 @@ Cargo.lock
 **/*.rs.bk
 *.ll
 tests/.tmp*
+tests/create_me/
 
 .helix/
 .vscode/

+ 1 - 1
integration/polkadot/runtime_errors.spec.ts

@@ -23,7 +23,7 @@ describe('Deploy runtime_errors.sol and test the debug buffer', () => {
         expect(res).toContain(`runtime_error: storage array index out of bounds in runtime_errors.sol:46:19-23`)
 
         let res1 = await debug_buffer(conn, contract, `transfer_abort`, [])
-        expect(res1).toContain(`runtime_error: value transfer failure in runtime_errors.sol:53:29-31`)
+        expect(res1).toContain(`runtime_error: value transfer failure in runtime_errors.sol:53:9-32`)
 
         let res2 = await debug_buffer(conn, contract, `pop_empty_storage`, [])
         expect(res2).toContain(`runtime_error: pop from empty storage array in runtime_errors.sol:58:13-16`)

+ 8 - 5
src/abi/polkadot.rs

@@ -20,9 +20,12 @@ use scale_info::{
 use semver::Version;
 use solang_parser::pt;
 
-use crate::sema::{
-    ast::{self, ArrayLength, EventDecl, Function},
-    tags::render,
+use crate::{
+    codegen::revert::{ERROR_SELECTOR, PANIC_SELECTOR},
+    sema::{
+        ast::{self, ArrayLength, EventDecl, Function},
+        tags::render,
+    },
 };
 
 macro_rules! path {
@@ -502,8 +505,8 @@ pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
         .done();
 
     let error_definitions = &[
-        ("Error", 0x08c379a0, vec![ast::Type::String]),
-        ("Panic", 0x4e487b71, vec![ast::Type::Uint(256)]),
+        ("Error", ERROR_SELECTOR, vec![ast::Type::String]),
+        ("Panic", PANIC_SELECTOR, vec![ast::Type::Uint(256)]),
     ];
 
     let spec = ContractSpec::new()

+ 6 - 4
src/bin/languageserver/mod.rs

@@ -492,14 +492,16 @@ impl<'a> Builder<'a> {
             }
             ast::Statement::TryCatch(_, _, try_stmt) => {
                 self.expression(&try_stmt.expr, symtab);
-                for stmt in &try_stmt.catch_stmt {
-                    self.statement(stmt, symtab);
+                if let Some(clause) = try_stmt.catch_all.as_ref() {
+                    for stmt in &clause.stmt {
+                        self.statement(stmt, symtab);
+                    }
                 }
                 for stmt in &try_stmt.ok_stmt {
                     self.statement(stmt, symtab);
                 }
-                for (_, _, block) in &try_stmt.errors {
-                    for stmts in block {
+                for clause in &try_stmt.errors {
+                    for stmts in &clause.stmt {
                         self.statement(stmts, symtab);
                     }
                 }

+ 0 - 1
src/codegen/constructor.rs

@@ -81,7 +81,6 @@ pub(super) fn call_constructor(
     args.append(&mut constructor_args);
 
     let (encoded_args, _) = abi_encode(loc, args, ns, vartab, cfg, false);
-
     cfg.add(
         vartab,
         Instr::Constructor {

+ 2 - 1
src/codegen/dispatch/polkadot.rs

@@ -407,9 +407,10 @@ impl<'a> Dispatch<'a> {
 
         self.cfg.set_basic_block(true_block);
         let function_name = self.all_cfg[func_no].name.split("::").last().unwrap();
+        let function_type = self.all_cfg[func_no].ty;
         log_runtime_error(
             self.opt.log_runtime_errors,
-            &format!("runtime_error: non payable function {function_name} received value"),
+            &format!("runtime_error: non payable {function_type} {function_name} received value"),
             Codegen,
             &mut self.cfg,
             &mut self.vartab,

+ 109 - 38
src/codegen/expression.rs

@@ -7,11 +7,11 @@ use super::revert::{
 use super::storage::{
     array_offset, array_pop, array_push, storage_slots_array_pop, storage_slots_array_push,
 };
-use super::Options;
 use super::{
     cfg::{ControlFlowGraph, Instr, InternalCallTy},
     vartable::Vartable,
 };
+use super::{polkadot, Options};
 use crate::codegen::array_boundary::handle_array_assign;
 use crate::codegen::constructor::call_constructor;
 use crate::codegen::unused_variable::should_remove_assignment;
@@ -450,7 +450,10 @@ pub fn expression(
             call_args,
         } => {
             let address_res = vartab.temp_anonymous(&Type::Contract(*constructor_contract));
-
+            let success = ns
+                .target
+                .is_polkadot()
+                .then(|| vartab.temp_name("success", &Type::Uint(32)));
             call_constructor(
                 loc,
                 *constructor_contract,
@@ -459,13 +462,21 @@ pub fn expression(
                 args,
                 call_args,
                 address_res,
-                None,
+                success,
                 func,
                 ns,
                 vartab,
                 cfg,
                 opt,
             );
+            if ns.target.is_polkadot() {
+                polkadot::RetCodeCheckBuilder::default()
+                    .loc(*loc)
+                    .msg("contract creation failed")
+                    .success_var(success.unwrap())
+                    .insert(cfg, vartab)
+                    .handle_cases(cfg, ns, opt, vartab);
+            }
             Expression::Variable {
                 loc: *loc,
                 ty: Type::Contract(*constructor_contract),
@@ -1568,19 +1579,11 @@ fn payable_send(
             loc: *loc,
             name: "success".to_owned(),
         },
-        &Type::Bool,
+        &Type::Uint(32),
     );
-    if ns.target != Target::EVM {
-        cfg.add(
-            vartab,
-            Instr::ValueTransfer {
-                success: Some(success),
-                address,
-                value,
-            },
-        );
-    } else {
-        // Ethereum can only transfer via external call
+
+    // Ethereum can only transfer via external call
+    if ns.target == Target::EVM {
         cfg.add(
             vartab,
             Instr::ExternalCall {
@@ -1609,11 +1612,30 @@ fn payable_send(
                 flags: None,
             },
         );
+        return Expression::Variable {
+            loc: *loc,
+            ty: Type::Bool,
+            var_no: success,
+        };
     }
-    Expression::Variable {
-        loc: *loc,
-        ty: Type::Bool,
-        var_no: success,
+
+    cfg.add(
+        vartab,
+        Instr::ValueTransfer {
+            success: Some(success),
+            address,
+            value,
+        },
+    );
+
+    if ns.target.is_polkadot() {
+        polkadot::check_transfer_ret(loc, success, cfg, ns, opt, vartab, false).unwrap()
+    } else {
+        Expression::Variable {
+            loc: *loc,
+            ty: Type::Bool,
+            var_no: success,
+        }
     }
 }
 
@@ -1629,16 +1651,7 @@ fn payable_transfer(
 ) -> Expression {
     let address = expression(&args[0], cfg, contract_no, func, ns, vartab, opt);
     let value = expression(&args[1], cfg, contract_no, func, ns, vartab, opt);
-    if ns.target != Target::EVM {
-        cfg.add(
-            vartab,
-            Instr::ValueTransfer {
-                success: None,
-                address,
-                value,
-            },
-        );
-    } else {
+    if ns.target == Target::EVM {
         // Ethereum can only transfer via external call
         cfg.add(
             vartab,
@@ -1668,7 +1681,24 @@ fn payable_transfer(
                 flags: None,
             },
         );
+        return Expression::Poison;
     }
+
+    let success = ns
+        .target
+        .is_polkadot()
+        .then(|| vartab.temp_name("success", &Type::Uint(32)));
+    let ins = Instr::ValueTransfer {
+        success,
+        address,
+        value,
+    };
+    cfg.add(vartab, ins);
+
+    if ns.target.is_polkadot() {
+        polkadot::check_transfer_ret(loc, success.unwrap(), cfg, ns, opt, vartab, true);
+    }
+
     Expression::Poison
 }
 
@@ -2752,7 +2782,7 @@ pub fn emit_function_call(
                 .as_ref()
                 .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt));
 
-            let success = vartab.temp_name("success", &Type::Bool);
+            let success = vartab.temp_name("success", &Type::Uint(32));
 
             let flags = call_args
                 .flags
@@ -2775,14 +2805,30 @@ pub fn emit_function_call(
                 },
             );
 
-            vec![
+            let success = if ns.target.is_polkadot() {
+                let ret_code = Expression::Variable {
+                    loc: *loc,
+                    ty: Type::Uint(32),
+                    var_no: success,
+                };
+                let ret_ok = Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: Type::Uint(32),
+                    value: 0.into(),
+                };
+                Expression::Equal {
+                    loc: *loc,
+                    left: ret_code.into(),
+                    right: ret_ok.into(),
+                }
+            } else {
                 Expression::Variable {
                     loc: *loc,
-                    ty: Type::Bool,
+                    ty: Type::Uint(32),
                     var_no: success,
-                },
-                Expression::ReturnData { loc: *loc },
-            ]
+                }
+            };
+            vec![success, Expression::ReturnData { loc: *loc }]
         }
         ast::Expression::ExternalFunctionCall {
             loc,
@@ -2853,10 +2899,14 @@ pub fn emit_function_call(
                     .as_ref()
                     .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt));
 
+                let success = ns
+                    .target
+                    .is_polkadot()
+                    .then(|| vartab.temp_name("success", &Type::Uint(32)));
                 cfg.add(
                     vartab,
                     Instr::ExternalCall {
-                        success: None,
+                        success,
                         accounts,
                         address: Some(address),
                         payload,
@@ -2869,6 +2919,15 @@ pub fn emit_function_call(
                     },
                 );
 
+                if ns.target.is_polkadot() {
+                    polkadot::RetCodeCheckBuilder::default()
+                        .loc(*loc)
+                        .msg("external call failed")
+                        .success_var(success.unwrap())
+                        .insert(cfg, vartab)
+                        .handle_cases(cfg, ns, opt, vartab);
+                }
+
                 // If the first element of returns is Void, we can discard the returns
                 if !dest_func.returns.is_empty() && returns[0] != Type::Void {
                     let tys = dest_func
@@ -2926,11 +2985,14 @@ pub fn emit_function_call(
                     .flags
                     .as_ref()
                     .map(|expr| expression(expr, cfg, caller_contract_no, func, ns, vartab, opt));
-
+                let success = ns
+                    .target
+                    .is_polkadot()
+                    .then(|| vartab.temp_name("success", &Type::Uint(32)));
                 cfg.add(
                     vartab,
                     Instr::ExternalCall {
-                        success: None,
+                        success,
                         accounts: None,
                         seeds: None,
                         address: Some(address),
@@ -2943,6 +3005,15 @@ pub fn emit_function_call(
                     },
                 );
 
+                if ns.target.is_polkadot() {
+                    polkadot::RetCodeCheckBuilder::default()
+                        .loc(*loc)
+                        .msg("external call failed")
+                        .success_var(success.unwrap())
+                        .insert(cfg, vartab)
+                        .handle_cases(cfg, ns, opt, vartab);
+                }
+
                 if !func_returns.is_empty() && returns[0] != Type::Void {
                     abi_decode(
                         loc,

+ 1 - 0
src/codegen/mod.rs

@@ -10,6 +10,7 @@ pub(crate) mod encoding;
 mod events;
 mod expression;
 mod external_functions;
+pub(super) mod polkadot;
 mod reaching_definitions;
 pub mod revert;
 mod solana_accounts;

+ 173 - 0
src/codegen/polkadot.rs

@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: Apache-2.0
+
+//! Contains `codegen` helpers for the Polkadot target.
+
+use solang_parser::pt::Loc;
+
+use crate::{
+    codegen::{
+        cfg::{ControlFlowGraph, Instr},
+        revert::log_runtime_error,
+        vartable::Vartable,
+        Expression, Options,
+    },
+    sema::ast::{Namespace, Type},
+};
+
+/// Helper to handle error cases from external function and constructor calls.
+pub(crate) struct RetCodeCheck {
+    pub success: usize,
+    pub revert: usize,
+    pub error_no_data: usize,
+    msg: &'static str,
+    loc: Loc,
+}
+
+#[derive(Default)]
+pub(crate) struct RetCodeCheckBuilder<Success = ()> {
+    loc: Loc,
+    msg: &'static str,
+    success_var: Success,
+}
+
+impl RetCodeCheckBuilder {
+    pub(crate) fn loc(mut self, loc: Loc) -> Self {
+        self.loc = loc;
+        self
+    }
+
+    pub(crate) fn msg(mut self, msg: &'static str) -> Self {
+        self.msg = msg;
+        self
+    }
+
+    pub(crate) fn success_var(self, success_var: usize) -> RetCodeCheckBuilder<usize> {
+        RetCodeCheckBuilder {
+            loc: self.loc,
+            msg: self.msg,
+            success_var,
+        }
+    }
+}
+
+impl RetCodeCheckBuilder<usize> {
+    pub(crate) fn insert(self, cfg: &mut ControlFlowGraph, vartab: &mut Vartable) -> RetCodeCheck {
+        let cond = Expression::Variable {
+            loc: self.loc,
+            ty: Type::Uint(32),
+            var_no: self.success_var,
+        };
+        let ret = RetCodeCheck {
+            success: cfg.new_basic_block("ret_success".into()),
+            revert: cfg.new_basic_block("ret_bubble".into()),
+            error_no_data: cfg.new_basic_block("ret_no_data".into()),
+            msg: self.msg,
+            loc: self.loc,
+        };
+        let cases = vec![
+            (
+                Expression::NumberLiteral {
+                    loc: self.loc,
+                    ty: Type::Uint(32),
+                    value: 0.into(),
+                },
+                ret.success,
+            ),
+            (
+                Expression::NumberLiteral {
+                    loc: self.loc,
+                    ty: Type::Uint(32),
+                    value: 2.into(),
+                },
+                ret.revert,
+            ),
+        ];
+        let ins = Instr::Switch {
+            cond,
+            cases,
+            default: ret.error_no_data,
+        };
+        cfg.add(vartab, ins);
+
+        ret
+    }
+}
+
+impl RetCodeCheck {
+    /// Handles all cases from the [RetBlock] accordingly.
+    /// * On success, nothing is done and the execution continues at the success block.
+    /// If the callee reverted and output was supplied, it will be bubble up.
+    /// Otherwise, a revert without data will be inserted.
+    pub(crate) fn handle_cases(
+        &self,
+        cfg: &mut ControlFlowGraph,
+        ns: &Namespace,
+        opt: &Options,
+        vartab: &mut Vartable,
+    ) {
+        cfg.set_basic_block(self.error_no_data);
+        log_runtime_error(opt.log_runtime_errors, self.msg, self.loc, cfg, vartab, ns);
+        cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
+
+        cfg.set_basic_block(self.revert);
+        log_runtime_error(opt.log_runtime_errors, self.msg, self.loc, cfg, vartab, ns);
+        let encoded_args = Expression::ReturnData { loc: self.loc }.into();
+        cfg.add(vartab, Instr::AssertFailure { encoded_args });
+
+        cfg.set_basic_block(self.success);
+    }
+}
+
+/// Check the return code of `transfer`.
+///
+/// If `bubble_up` is set to true, this will revert the contract execution on failure.
+/// Otherwise, the expression comparing the return code against `0` is returned.
+pub(super) fn check_transfer_ret(
+    loc: &Loc,
+    success: usize,
+    cfg: &mut ControlFlowGraph,
+    ns: &Namespace,
+    opt: &Options,
+    vartab: &mut Vartable,
+    bubble_up: bool,
+) -> Option<Expression> {
+    let ret_code = Expression::Variable {
+        loc: *loc,
+        ty: Type::Uint(32),
+        var_no: success,
+    };
+    let ret_ok = Expression::NumberLiteral {
+        loc: *loc,
+        ty: Type::Uint(32),
+        value: 0.into(),
+    };
+    let cond = Expression::Equal {
+        loc: *loc,
+        left: ret_code.into(),
+        right: ret_ok.into(),
+    };
+
+    if !bubble_up {
+        return Some(cond);
+    }
+
+    let success_block = cfg.new_basic_block("transfer_success".into());
+    let fail_block = cfg.new_basic_block("transfer_fail".into());
+    cfg.add(
+        vartab,
+        Instr::BranchCond {
+            cond,
+            true_block: success_block,
+            false_block: fail_block,
+        },
+    );
+
+    cfg.set_basic_block(fail_block);
+    let msg = "value transfer failure";
+    log_runtime_error(opt.log_runtime_errors, msg, *loc, cfg, vartab, ns);
+    cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
+
+    cfg.set_basic_block(success_block);
+
+    None
+}

+ 10 - 5
src/codegen/revert.rs

@@ -21,6 +21,11 @@ use crate::Target;
 use parse_display::Display;
 use solang_parser::pt::{CodeLocation, Loc, Loc::Codegen};
 
+/// Signature of `Keccak256('Error(string)')[:4]`
+pub(crate) const ERROR_SELECTOR: u32 = 0x08c379a0u32;
+/// Signature of `Keccak256('Panic(uint256)')[:4]`
+pub(crate) const PANIC_SELECTOR: u32 = 0x4e487b71u32;
+
 /// Corresponds to the error types from the Solidity language.
 ///
 /// Marked as non-exhaustive because Solidity may add more variants in the future.
@@ -55,8 +60,8 @@ impl SolidityError {
     pub fn selector(&self) -> u32 {
         match self {
             Self::Empty => unreachable!("empty return data has no selector"),
-            Self::String(_) => 0x08c379a0u32,
-            Self::Panic(_) => 0x4e487b71u32,
+            Self::String(_) => ERROR_SELECTOR,
+            Self::Panic(_) => PANIC_SELECTOR,
         }
     }
 
@@ -378,7 +383,7 @@ fn string_to_expr(string: String) -> Expression {
 #[cfg(test)]
 mod tests {
     use crate::codegen::{
-        revert::{PanicCode, SolidityError},
+        revert::{PanicCode, SolidityError, ERROR_SELECTOR, PANIC_SELECTOR},
         Expression,
     };
 
@@ -399,11 +404,11 @@ mod tests {
     #[test]
     fn function_selector_expression() {
         assert_eq!(
-            0x08c379a0u32, // Keccak256('Error(string)')[:4]
+            ERROR_SELECTOR,
             SolidityError::String(Expression::Poison).selector(),
         );
         assert_eq!(
-            0x4e487b71u32, // Keccak256('Panic(uint256)')[:4]
+            PANIC_SELECTOR,
             SolidityError::Panic(PanicCode::Generic).selector(),
         );
     }

+ 18 - 355
src/codegen/statements.rs → src/codegen/statements/mod.rs

@@ -1,30 +1,30 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use num_bigint::BigInt;
-
-use super::encoding::{abi_decode, abi_encode};
-use super::expression::{assign_single, default_gas, emit_function_call, expression};
-use super::Options;
 use super::{
     cfg::{ControlFlowGraph, Instr},
+    events::new_event_emitter,
+    expression::{assign_single, emit_function_call, expression},
+    revert::revert,
+    unused_variable::{
+        should_remove_assignment, should_remove_variable, SideEffectsCheckParameters,
+    },
     vartable::Vartable,
+    yul::inline_assembly_cfg,
+    Builtin, Expression, Options,
 };
-use crate::codegen::constructor::call_constructor;
-use crate::codegen::events::new_event_emitter;
-use crate::codegen::revert::revert;
-use crate::codegen::unused_variable::{
-    should_remove_assignment, should_remove_variable, SideEffectsCheckParameters,
-};
-use crate::codegen::yul::inline_assembly_cfg;
-use crate::codegen::Expression;
-use crate::sema::ast::{
-    self, ArrayLength, CallTy, DestructureField, Function, Namespace, RetrieveType, Statement,
-    TryCatch, Type, Type::Uint,
+use crate::sema::{
+    ast::{
+        self, ArrayLength, DestructureField, Function, Namespace, RetrieveType, Statement, Type,
+        Type::Uint,
+    },
+    Recurse,
 };
-use crate::sema::Recurse;
+use num_bigint::BigInt;
 use num_traits::Zero;
 use solang_parser::pt::{self, CodeLocation, Loc::Codegen};
 
+mod try_catch;
+
 /// Resolve a statement, which might be a block of statements or an entire body of a function
 pub(crate) fn statement(
     stmt: &Statement,
@@ -554,7 +554,7 @@ pub(crate) fn statement(
         Statement::Destructure(_, fields, expr) => {
             destructure(fields, expr, cfg, contract_no, func, ns, vartab, opt)
         }
-        Statement::TryCatch(_, _, try_stmt) => try_catch(
+        Statement::TryCatch(_, _, try_stmt) => self::try_catch::try_catch(
             try_stmt,
             func,
             cfg,
@@ -1005,343 +1005,6 @@ fn try_load_and_cast(
     }
 }
 
-/// Resolve try catch statement
-fn try_catch(
-    try_stmt: &TryCatch,
-    func: &Function,
-    cfg: &mut ControlFlowGraph,
-    callee_contract_no: usize,
-    ns: &Namespace,
-    vartab: &mut Vartable,
-    loops: &mut LoopScopes,
-    placeholder: Option<&Instr>,
-    return_override: Option<&Instr>,
-    opt: &Options,
-) {
-    let success = vartab.temp(
-        &pt::Identifier {
-            loc: try_stmt.expr.loc(),
-            name: "success".to_owned(),
-        },
-        &Type::Bool,
-    );
-
-    let success_block = cfg.new_basic_block("success".to_string());
-    let catch_block = cfg.new_basic_block("catch".to_string());
-    let finally_block = cfg.new_basic_block("finally".to_string());
-
-    match &try_stmt.expr {
-        ast::Expression::ExternalFunctionCall {
-            loc,
-            function,
-            args,
-            call_args,
-            ..
-        } => {
-            if let Type::ExternalFunction {
-                returns: func_returns,
-                ..
-            } = function.ty()
-            {
-                let value = if let Some(value) = &call_args.value {
-                    expression(value, cfg, callee_contract_no, Some(func), ns, vartab, opt)
-                } else {
-                    Expression::NumberLiteral {
-                        loc: Codegen,
-                        ty: Type::Value,
-                        value: BigInt::zero(),
-                    }
-                };
-                let gas = if let Some(gas) = &call_args.gas {
-                    expression(gas, cfg, callee_contract_no, Some(func), ns, vartab, opt)
-                } else {
-                    default_gas(ns)
-                };
-                let function = expression(
-                    function,
-                    cfg,
-                    callee_contract_no,
-                    Some(func),
-                    ns,
-                    vartab,
-                    opt,
-                );
-
-                let mut args = args
-                    .iter()
-                    .map(|a| expression(a, cfg, callee_contract_no, Some(func), ns, vartab, opt))
-                    .collect::<Vec<Expression>>();
-
-                let selector = function.external_function_selector();
-
-                let address = function.external_function_address();
-
-                args.insert(0, selector);
-                let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false);
-
-                let flags = call_args.flags.as_ref().map(|expr| {
-                    expression(expr, cfg, callee_contract_no, Some(func), ns, vartab, opt)
-                });
-
-                cfg.add(
-                    vartab,
-                    Instr::ExternalCall {
-                        success: Some(success),
-                        address: Some(address),
-                        accounts: None,
-                        seeds: None,
-                        payload,
-                        value,
-                        gas,
-                        callty: CallTy::Regular,
-                        contract_function_no: None,
-                        flags,
-                    },
-                );
-
-                cfg.add(
-                    vartab,
-                    Instr::BranchCond {
-                        cond: Expression::Variable {
-                            loc: try_stmt.expr.loc(),
-                            ty: Type::Bool,
-                            var_no: success,
-                        },
-                        true_block: success_block,
-                        false_block: catch_block,
-                    },
-                );
-
-                cfg.set_basic_block(success_block);
-
-                if !try_stmt.returns.is_empty() {
-                    let mut res = Vec::new();
-
-                    for ret in &try_stmt.returns {
-                        res.push(match ret {
-                            (Some(pos), _) => *pos,
-                            (None, param) => vartab.temp_anonymous(&param.ty),
-                        });
-                    }
-
-                    let buf = &Expression::ReturnData { loc: Codegen };
-                    let decoded = abi_decode(&Codegen, buf, &func_returns, ns, vartab, cfg, None);
-                    for instruction in res.iter().zip(decoded).map(|(var, expr)| Instr::Set {
-                        loc: Codegen,
-                        res: *var,
-                        expr,
-                    }) {
-                        cfg.add(vartab, instruction)
-                    }
-                }
-            } else {
-                // dynamic dispatch
-                unimplemented!();
-            }
-        }
-        ast::Expression::Constructor {
-            loc,
-            contract_no,
-            constructor_no,
-            args,
-            call_args,
-            ..
-        } => {
-            let address_res = match try_stmt.returns.get(0) {
-                Some((Some(pos), _)) => *pos,
-                _ => vartab.temp_anonymous(&Type::Contract(*contract_no)),
-            };
-
-            call_constructor(
-                loc,
-                *contract_no,
-                callee_contract_no,
-                constructor_no,
-                args,
-                call_args,
-                address_res,
-                Some(success),
-                Some(func),
-                ns,
-                vartab,
-                cfg,
-                opt,
-            );
-
-            cfg.add(
-                vartab,
-                Instr::BranchCond {
-                    cond: Expression::Variable {
-                        loc: try_stmt.expr.loc(),
-                        ty: Type::Bool,
-                        var_no: success,
-                    },
-                    true_block: success_block,
-                    false_block: catch_block,
-                },
-            );
-
-            cfg.set_basic_block(success_block);
-        }
-        _ => unreachable!(),
-    }
-
-    vartab.new_dirty_tracker();
-
-    let mut finally_reachable = true;
-
-    for stmt in &try_stmt.ok_stmt {
-        statement(
-            stmt,
-            func,
-            cfg,
-            callee_contract_no,
-            ns,
-            vartab,
-            loops,
-            placeholder,
-            return_override,
-            opt,
-        );
-
-        finally_reachable = stmt.reachable();
-    }
-
-    if finally_reachable {
-        cfg.add(
-            vartab,
-            Instr::Branch {
-                block: finally_block,
-            },
-        );
-    }
-
-    cfg.set_basic_block(catch_block);
-
-    for (error_param_pos, error_param, error_stmt) in &try_stmt.errors {
-        let no_reason_block = cfg.new_basic_block("no_reason".to_string());
-
-        let error_var = match error_param_pos {
-            Some(pos) => *pos,
-            _ => vartab.temp_anonymous(&Type::String),
-        };
-
-        // Expect the returned data to match the 4 bytes function selector for "Error(string)"
-        let buf = &Expression::ReturnData { loc: Codegen };
-        let tys = &[Type::Bytes(4), error_param.ty.clone()];
-        let decoded = abi_decode(&Codegen, buf, tys, ns, vartab, cfg, None);
-        let err_id = Expression::NumberLiteral {
-            loc: Codegen,
-            ty: Type::Bytes(4),
-            value: 0x08c3_79a0.into(),
-        }
-        .into();
-        let cond = Expression::Equal {
-            loc: Codegen,
-            left: decoded[0].clone().into(),
-            right: err_id,
-        };
-        let match_err_id = cfg.new_basic_block("match_err_id".into());
-        let no_match_err_id = cfg.new_basic_block("no_match_err_id".into());
-        let instruction = Instr::BranchCond {
-            cond,
-            true_block: match_err_id,
-            false_block: no_match_err_id,
-        };
-
-        cfg.add(vartab, instruction);
-        cfg.set_basic_block(no_match_err_id);
-
-        cfg.add(vartab, Instr::AssertFailure { encoded_args: None });
-        cfg.set_basic_block(match_err_id);
-        let instruction = Instr::Set {
-            loc: Codegen,
-            res: error_var,
-            expr: decoded[1].clone(),
-        };
-        cfg.add(vartab, instruction);
-
-        let mut reachable = true;
-
-        for stmt in error_stmt {
-            statement(
-                stmt,
-                func,
-                cfg,
-                callee_contract_no,
-                ns,
-                vartab,
-                loops,
-                placeholder,
-                return_override,
-                opt,
-            );
-
-            reachable = stmt.reachable();
-        }
-        if reachable {
-            cfg.add(
-                vartab,
-                Instr::Branch {
-                    block: finally_block,
-                },
-            );
-        }
-
-        cfg.set_basic_block(no_reason_block);
-    }
-
-    if let Some(res) = try_stmt.catch_param_pos {
-        let instruction = Instr::Set {
-            loc: Codegen,
-            res,
-            expr: Expression::ReturnData { loc: Codegen },
-        };
-        cfg.add(vartab, instruction);
-    }
-
-    let mut reachable = true;
-
-    for stmt in &try_stmt.catch_stmt {
-        statement(
-            stmt,
-            func,
-            cfg,
-            callee_contract_no,
-            ns,
-            vartab,
-            loops,
-            placeholder,
-            return_override,
-            opt,
-        );
-
-        reachable = stmt.reachable();
-    }
-
-    if reachable {
-        cfg.add(
-            vartab,
-            Instr::Branch {
-                block: finally_block,
-            },
-        );
-    }
-
-    let mut set = vartab.pop_dirty_tracker();
-    if let Some(pos) = &try_stmt.catch_param_pos {
-        set.remove(pos);
-    }
-    for (pos, _, _) in &try_stmt.errors {
-        if let Some(pos) = pos {
-            set.remove(pos);
-        }
-    }
-    cfg.set_phis(finally_block, set);
-
-    cfg.set_basic_block(finally_block);
-}
-
 pub struct LoopScope {
     break_bb: usize,
     continue_bb: usize,

+ 592 - 0
src/codegen/statements/try_catch.rs

@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use super::{statement, Builtin, LoopScopes, Options};
+use crate::codegen::{
+    cfg::{ControlFlowGraph, Instr},
+    constructor::call_constructor,
+    encoding::{abi_decode, abi_encode},
+    expression::{default_gas, expression},
+    polkadot,
+    revert::ERROR_SELECTOR,
+    vartable::Vartable,
+    Expression,
+};
+use crate::sema::ast::{
+    self, CallTy, Function, Namespace, RetrieveType, TryCatch, Type, Type::Uint,
+};
+use num_bigint::BigInt;
+use num_traits::Zero;
+use solang_parser::pt::{self, CodeLocation, Loc::Codegen};
+
+/// Resolve try catch statement
+pub(super) fn try_catch(
+    try_stmt: &TryCatch,
+    func: &Function,
+    cfg: &mut ControlFlowGraph,
+    callee_contract_no: usize,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    loops: &mut LoopScopes,
+    placeholder: Option<&Instr>,
+    return_override: Option<&Instr>,
+    opt: &Options,
+) {
+    if !ns.target.is_polkadot() {
+        unimplemented!()
+    }
+
+    let ok_block = cfg.new_basic_block("ok".to_string());
+    let catch_block = cfg.new_basic_block("catch".to_string());
+    let finally_block = cfg.new_basic_block("finally".to_string());
+
+    let error_ret_data_var = insert_try_expression(
+        try_stmt,
+        cfg,
+        ns,
+        vartab,
+        func,
+        callee_contract_no,
+        opt,
+        ok_block,
+        catch_block,
+    );
+
+    vartab.new_dirty_tracker();
+
+    insert_success_code_block(
+        try_stmt,
+        func,
+        cfg,
+        callee_contract_no,
+        ns,
+        vartab,
+        loops,
+        placeholder,
+        return_override,
+        opt,
+        ok_block,
+        finally_block,
+    );
+
+    insert_catch_clauses(
+        try_stmt,
+        func,
+        cfg,
+        callee_contract_no,
+        ns,
+        vartab,
+        loops,
+        placeholder,
+        return_override,
+        opt,
+        catch_block,
+        finally_block,
+        error_ret_data_var,
+    );
+
+    //  Remove the variables only in scope inside the catch clauses block from the phi set for the finally block
+    let mut set = vartab.pop_dirty_tracker();
+    if let Some(pos) = try_stmt
+        .catch_all
+        .as_ref()
+        .and_then(|clause| clause.param_pos)
+    {
+        set.remove(&pos);
+    }
+    for clause in &try_stmt.errors {
+        if let Some(pos) = clause.param_pos {
+            set.remove(&pos);
+        }
+    }
+    cfg.set_phis(finally_block, set);
+
+    cfg.set_basic_block(finally_block);
+}
+
+/// Insert try statement execution and error data collection into the CFG.
+/// Returns the variable number of the return error data.
+fn insert_try_expression(
+    try_stmt: &TryCatch,
+    cfg: &mut ControlFlowGraph,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    func: &Function,
+    callee_contract_no: usize,
+    opt: &Options,
+    ok_block: usize,
+    catch_block: usize,
+) -> usize {
+    let (cases, return_types) = exec_try(try_stmt, func, cfg, callee_contract_no, ns, vartab, opt);
+
+    let error_ret_data_var = vartab.temp_name("error_ret_data", &Type::DynamicBytes);
+
+    vartab.new_dirty_tracker();
+
+    cfg.set_basic_block(cases.error_no_data);
+    cfg.add(
+        vartab,
+        Instr::Set {
+            loc: Codegen,
+            res: error_ret_data_var,
+            expr: Expression::AllocDynamicBytes {
+                loc: Codegen,
+                ty: Type::DynamicBytes,
+                size: Expression::NumberLiteral {
+                    loc: Codegen,
+                    ty: Uint(32),
+                    value: 0.into(),
+                }
+                .into(),
+                initializer: Some(vec![]),
+            },
+        },
+    );
+    cfg.add(vartab, Instr::Branch { block: catch_block });
+
+    cfg.set_basic_block(cases.revert);
+    cfg.add(
+        vartab,
+        Instr::Set {
+            loc: Codegen,
+            res: error_ret_data_var,
+            expr: Expression::ReturnData { loc: Codegen },
+        },
+    );
+    cfg.add(vartab, Instr::Branch { block: catch_block });
+
+    vartab.set_dirty(error_ret_data_var);
+    cfg.set_phis(catch_block, vartab.pop_dirty_tracker());
+
+    cfg.set_basic_block(cases.success);
+
+    if !try_stmt.returns.is_empty() {
+        let mut res = Vec::new();
+
+        for ret in &try_stmt.returns {
+            res.push(match ret {
+                (Some(pos), _) => *pos,
+                (None, param) => vartab.temp_anonymous(&param.ty),
+            });
+        }
+
+        let buf = &Expression::ReturnData { loc: Codegen };
+        let decoded = abi_decode(&Codegen, buf, &return_types, ns, vartab, cfg, None);
+        for instruction in res.iter().zip(decoded).map(|(var, expr)| Instr::Set {
+            loc: Codegen,
+            res: *var,
+            expr,
+        }) {
+            cfg.add(vartab, instruction)
+        }
+    }
+    cfg.add(vartab, Instr::Branch { block: ok_block });
+
+    error_ret_data_var
+}
+
+/// Insert the execution of the `try` expression into the CFG.
+fn exec_try(
+    try_stmt: &TryCatch,
+    func: &Function,
+    cfg: &mut ControlFlowGraph,
+    callee_contract_no: usize,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    opt: &Options,
+) -> (polkadot::RetCodeCheck, Vec<Type>) {
+    let success = vartab.temp(
+        &pt::Identifier {
+            loc: try_stmt.expr.loc(),
+            name: "success".to_owned(),
+        },
+        &Type::Bool,
+    );
+    match &try_stmt.expr {
+        ast::Expression::ExternalFunctionCall {
+            loc,
+            function,
+            args,
+            call_args,
+            ..
+        } => {
+            if let Type::ExternalFunction {
+                returns: func_returns,
+                ..
+            } = function.ty()
+            {
+                let value = if let Some(value) = &call_args.value {
+                    expression(value, cfg, callee_contract_no, Some(func), ns, vartab, opt)
+                } else {
+                    Expression::NumberLiteral {
+                        loc: Codegen,
+                        ty: Type::Value,
+                        value: BigInt::zero(),
+                    }
+                };
+                let gas = if let Some(gas) = &call_args.gas {
+                    expression(gas, cfg, callee_contract_no, Some(func), ns, vartab, opt)
+                } else {
+                    default_gas(ns)
+                };
+                let function = expression(
+                    function,
+                    cfg,
+                    callee_contract_no,
+                    Some(func),
+                    ns,
+                    vartab,
+                    opt,
+                );
+
+                let mut args = args
+                    .iter()
+                    .map(|a| expression(a, cfg, callee_contract_no, Some(func), ns, vartab, opt))
+                    .collect::<Vec<Expression>>();
+
+                let selector = function.external_function_selector();
+
+                let address = function.external_function_address();
+
+                args.insert(0, selector);
+                let (payload, _) = abi_encode(loc, args, ns, vartab, cfg, false);
+
+                let flags = call_args.flags.as_ref().map(|expr| {
+                    expression(expr, cfg, callee_contract_no, Some(func), ns, vartab, opt)
+                });
+
+                cfg.add(
+                    vartab,
+                    Instr::ExternalCall {
+                        success: Some(success),
+                        address: Some(address),
+                        accounts: None,
+                        seeds: None,
+                        payload,
+                        value,
+                        gas,
+                        callty: CallTy::Regular,
+                        contract_function_no: None,
+                        flags,
+                    },
+                );
+
+                let cases = polkadot::RetCodeCheckBuilder::default()
+                    .loc(*loc)
+                    .success_var(success)
+                    .insert(cfg, vartab);
+                (cases, func_returns)
+            } else {
+                // dynamic dispatch
+                unimplemented!();
+            }
+        }
+        ast::Expression::Constructor {
+            loc,
+            contract_no,
+            constructor_no,
+            args,
+            call_args,
+            ..
+        } => {
+            let address_res = match try_stmt.returns.get(0) {
+                Some((Some(pos), _)) => *pos,
+                _ => vartab.temp_anonymous(&Type::Contract(*contract_no)),
+            };
+
+            call_constructor(
+                loc,
+                *contract_no,
+                callee_contract_no,
+                constructor_no,
+                args,
+                call_args,
+                address_res,
+                Some(success),
+                Some(func),
+                ns,
+                vartab,
+                cfg,
+                opt,
+            );
+
+            let cases = polkadot::RetCodeCheckBuilder::default()
+                .loc(*loc)
+                .success_var(success)
+                .insert(cfg, vartab);
+            (cases, vec![])
+        }
+        _ => unreachable!(),
+    }
+}
+
+/// Insert the success code into the CFG.
+fn insert_success_code_block(
+    try_stmt: &TryCatch,
+    func: &Function,
+    cfg: &mut ControlFlowGraph,
+    callee_contract_no: usize,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    loops: &mut LoopScopes,
+    placeholder: Option<&Instr>,
+    return_override: Option<&Instr>,
+    opt: &Options,
+    ok_block: usize,
+    finally_block: usize,
+) {
+    cfg.set_basic_block(ok_block);
+
+    let mut finally_reachable = true;
+
+    for stmt in &try_stmt.ok_stmt {
+        statement(
+            stmt,
+            func,
+            cfg,
+            callee_contract_no,
+            ns,
+            vartab,
+            loops,
+            placeholder,
+            return_override,
+            opt,
+        );
+
+        finally_reachable = stmt.reachable();
+    }
+
+    if finally_reachable {
+        cfg.add(
+            vartab,
+            Instr::Branch {
+                block: finally_block,
+            },
+        );
+    }
+}
+
+/// Insert all catch cases into the CFG.
+fn insert_catch_clauses(
+    try_stmt: &TryCatch,
+    func: &Function,
+    cfg: &mut ControlFlowGraph,
+    callee_contract_no: usize,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    loops: &mut LoopScopes,
+    placeholder: Option<&Instr>,
+    return_override: Option<&Instr>,
+    opt: &Options,
+    catch_block: usize,
+    finally_block: usize,
+    error_ret_data_var: usize,
+) {
+    cfg.set_basic_block(catch_block);
+
+    let buffer = Expression::Variable {
+        loc: Codegen,
+        ty: Type::DynamicBytes,
+        var_no: error_ret_data_var,
+    };
+
+    // Check for error blocks and dispatch
+    // If no errors, go straight to the catch all block
+    if try_stmt.errors.is_empty() {
+        insert_catchall_clause_code_block(
+            try_stmt,
+            func,
+            cfg,
+            callee_contract_no,
+            ns,
+            vartab,
+            loops,
+            placeholder,
+            return_override,
+            opt,
+            finally_block,
+            buffer,
+        );
+        return;
+    }
+
+    let clause = &try_stmt.errors.get(0).unwrap();
+
+    let error_var = clause
+        .param_pos
+        .unwrap_or_else(|| vartab.temp_anonymous(&Type::String));
+
+    // Dispatch according to the error selector.
+    // At the moment, we only support Error(string).
+    // Expect the returned data to contain at least the selector + 1 byte of data.
+    // If the error data len is <4 and a fallback available then proceed else bubble
+    let ret_data_len = Expression::Builtin {
+        loc: Codegen,
+        tys: vec![Type::Uint(32)],
+        kind: Builtin::ArrayLength,
+        args: vec![buffer.clone()],
+    };
+    let enough_data_block = cfg.new_basic_block("enough_data".into());
+    let no_match_err_id = cfg.new_basic_block("no_match_err_id".into());
+    let selector_data_len = Expression::NumberLiteral {
+        loc: Codegen,
+        ty: Type::Uint(32),
+        value: 4.into(),
+    };
+    let cond_enough_data = Expression::More {
+        loc: Codegen,
+        signed: false,
+        left: ret_data_len.into(),
+        right: selector_data_len.into(),
+    };
+    cfg.add(
+        vartab,
+        Instr::BranchCond {
+            cond: cond_enough_data,
+            true_block: enough_data_block,
+            false_block: no_match_err_id,
+        },
+    );
+
+    let offset = Expression::NumberLiteral {
+        loc: Codegen,
+        ty: Uint(32),
+        value: 0.into(),
+    };
+    let err_selector = Expression::Builtin {
+        loc: Codegen,
+        tys: vec![Type::Bytes(4)],
+        kind: Builtin::ReadFromBuffer,
+        args: vec![buffer.clone(), offset],
+    };
+
+    cfg.set_basic_block(enough_data_block);
+    // Expect the returned data to match the 4 bytes function selector for "Error(string)"
+    let err_id = Expression::NumberLiteral {
+        loc: Codegen,
+        ty: Type::Bytes(4),
+        value: ERROR_SELECTOR.into(),
+    };
+    let cond = Expression::Equal {
+        loc: Codegen,
+        left: err_selector.into(),
+        right: err_id.into(),
+    };
+    let match_err_id = cfg.new_basic_block("match_err_id".into());
+    let instruction = Instr::BranchCond {
+        cond,
+        true_block: match_err_id,
+        false_block: no_match_err_id,
+    };
+    cfg.add(vartab, instruction);
+
+    // If the selector doesn't match any of the errors and no fallback then bubble else catch
+    cfg.set_basic_block(no_match_err_id);
+    if try_stmt.catch_all.is_none() {
+        let encoded_args = Some(buffer.clone());
+        cfg.add(vartab, Instr::AssertFailure { encoded_args });
+    } else {
+        insert_catchall_clause_code_block(
+            try_stmt,
+            func,
+            cfg,
+            callee_contract_no,
+            ns,
+            vartab,
+            loops,
+            placeholder,
+            return_override,
+            opt,
+            finally_block,
+            buffer.clone(),
+        );
+    }
+
+    cfg.set_basic_block(match_err_id);
+    let tys = &[Type::Bytes(4), clause.param.as_ref().unwrap().ty.clone()];
+    let decoded = abi_decode(&Codegen, &buffer, tys, ns, vartab, cfg, None);
+    let instruction = Instr::Set {
+        loc: Codegen,
+        res: error_var,
+        expr: decoded[1].clone(),
+    };
+    cfg.add(vartab, instruction);
+
+    let mut reachable = true;
+    for stmt in &clause.stmt {
+        statement(
+            stmt,
+            func,
+            cfg,
+            callee_contract_no,
+            ns,
+            vartab,
+            loops,
+            placeholder,
+            return_override,
+            opt,
+        );
+
+        reachable = stmt.reachable();
+    }
+    if reachable {
+        cfg.add(
+            vartab,
+            Instr::Branch {
+                block: finally_block,
+            },
+        );
+    }
+}
+
+/// Insert the fallback catch code into the CFG.
+fn insert_catchall_clause_code_block(
+    try_stmt: &TryCatch,
+    func: &Function,
+    cfg: &mut ControlFlowGraph,
+    callee_contract_no: usize,
+    ns: &Namespace,
+    vartab: &mut Vartable,
+    loops: &mut LoopScopes,
+    placeholder: Option<&Instr>,
+    return_override: Option<&Instr>,
+    opt: &Options,
+    finally_block: usize,
+    error_data_buf: Expression,
+) {
+    if let Some(res) = try_stmt.catch_all.as_ref().unwrap().param_pos {
+        let instruction = Instr::Set {
+            loc: Codegen,
+            res,
+            expr: error_data_buf,
+        };
+        cfg.add(vartab, instruction);
+    }
+
+    let mut reachable = true;
+
+    for stmt in &try_stmt.catch_all.as_ref().unwrap().stmt {
+        statement(
+            stmt,
+            func,
+            cfg,
+            callee_contract_no,
+            ns,
+            vartab,
+            loops,
+            placeholder,
+            return_override,
+            opt,
+        );
+
+        reachable = stmt.reachable();
+    }
+
+    if reachable {
+        cfg.add(
+            vartab,
+            Instr::Branch {
+                block: finally_block,
+            },
+        );
+    }
+}

+ 8 - 80
src/emit/polkadot/target.rs

@@ -822,7 +822,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
         encoded_args_len: BasicValueEnum<'b>,
         contract_args: ContractArgs<'b>,
         ns: &ast::Namespace,
-        loc: Loc,
+        _loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -904,30 +904,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
 
         log_return_code(binary, "seal_instantiate", ret);
 
-        let is_success =
-            binary
-                .builder
-                .build_int_compare(IntPredicate::EQ, ret, i32_zero!(), "success");
-
-        if let Some(success) = success {
-            // we're in a try statement. This means:
-            // return success or not in success variable; do not abort execution
-            *success = is_success.into();
-        } else {
-            let success_block = binary.context.append_basic_block(function, "success");
-            let bail_block = binary.context.append_basic_block(function, "bail");
-
-            binary
-                .builder
-                .build_conditional_branch(is_success, success_block, bail_block);
-
-            binary.builder.position_at_end(bail_block);
-
-            binary.log_runtime_error(self, "contract creation failed".to_string(), Some(loc), ns);
-            self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
-
-            binary.builder.position_at_end(success_block);
-        }
+        *success.unwrap() = ret.into();
     }
 
     /// Call external binary
@@ -952,7 +929,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
             .build_store(scratch_len, i32_const!(SCRATCH_SIZE as u64));
 
         // do the actual call
-        let ret = match call_type {
+        *success.unwrap() = match call_type {
             ast::CallTy::Regular => {
                 let value_ptr = binary
                     .builder
@@ -978,7 +955,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
                 .unwrap()
                 .into_int_value();
                 log_return_code(binary, "seal_call", ret);
-                ret
+                ret.as_basic_value_enum()
             }
             ast::CallTy::Delegate => {
                 // delegate_call asks for a code hash instead of an address
@@ -1055,47 +1032,22 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
                 let ret = binary.builder.build_phi(ty, "storage_res");
                 ret.add_incoming(&[(&code_hash_ret, not_found_block), (&ty.const_zero(), entry)]);
                 ret.add_incoming(&[(&delegate_call_ret, call_block), (&ty.const_zero(), entry)]);
-                ret.as_basic_value().into_int_value()
+                ret.as_basic_value()
             }
             ast::CallTy::Static => unreachable!("sema does not allow this"),
         };
-
-        let is_success =
-            binary
-                .builder
-                .build_int_compare(IntPredicate::EQ, ret, i32_zero!(), "success");
-
-        if let Some(success) = success {
-            // we're in a try statement. This means:
-            // do not abort execution; return success or not in success variable
-            *success = is_success.into();
-        } else {
-            let success_block = binary.context.append_basic_block(function, "success");
-            let bail_block = binary.context.append_basic_block(function, "bail");
-
-            binary
-                .builder
-                .build_conditional_branch(is_success, success_block, bail_block);
-
-            binary.builder.position_at_end(bail_block);
-
-            binary.log_runtime_error(self, "external call failed".to_string(), Some(loc), ns);
-            self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
-
-            binary.builder.position_at_end(success_block);
-        }
     }
 
     /// Send value to address
     fn value_transfer<'b>(
         &self,
         binary: &Binary<'b>,
-        function: FunctionValue,
+        _function: FunctionValue,
         success: Option<&mut BasicValueEnum<'b>>,
         address: PointerValue<'b>,
         value: IntValue<'b>,
         ns: &ast::Namespace,
-        loc: Loc,
+        _loc: Loc,
     ) {
         emit_context!(binary);
 
@@ -1121,31 +1073,7 @@ impl<'a> TargetRuntime<'a> for PolkadotTarget {
         .into_int_value();
 
         log_return_code(binary, "seal_transfer", ret);
-
-        let is_success =
-            binary
-                .builder
-                .build_int_compare(IntPredicate::EQ, ret, i32_zero!(), "success");
-
-        if let Some(success) = success {
-            // we're in a try statement. This means:
-            // do not abort execution; return success or not in success variable
-            *success = is_success.into();
-        } else {
-            let success_block = binary.context.append_basic_block(function, "success");
-            let bail_block = binary.context.append_basic_block(function, "bail");
-
-            binary
-                .builder
-                .build_conditional_branch(is_success, success_block, bail_block);
-
-            binary.builder.position_at_end(bail_block);
-
-            binary.log_runtime_error(self, "value transfer failure".to_string(), Some(loc), ns);
-            self.assert_failure(binary, byte_ptr!().const_null(), i32_zero!());
-
-            binary.builder.position_at_end(success_block);
-        }
+        *success.unwrap() = ret.into();
     }
 
     fn return_data<'b>(&self, binary: &Binary<'b>, _function: FunctionValue) -> PointerValue<'b> {

+ 15 - 8
src/sema/ast.rs

@@ -1694,10 +1694,15 @@ pub struct TryCatch {
     pub expr: Expression,
     pub returns: Vec<(Option<usize>, Parameter)>,
     pub ok_stmt: Vec<Statement>,
-    pub errors: Vec<(Option<usize>, Parameter, Vec<Statement>)>,
-    pub catch_param: Option<Parameter>,
-    pub catch_param_pos: Option<usize>,
-    pub catch_stmt: Vec<Statement>,
+    pub errors: Vec<CatchClause>,
+    pub catch_all: Option<CatchClause>,
+}
+
+#[derive(Clone, Debug)]
+pub struct CatchClause {
+    pub param: Option<Parameter>,
+    pub param_pos: Option<usize>,
+    pub stmt: Vec<Statement>,
 }
 
 #[derive(Clone, Debug)]
@@ -1761,14 +1766,16 @@ impl Recurse for Statement {
                         stmt.recurse(cx, f);
                     }
 
-                    for error_stmt in &try_catch.errors {
-                        for stmt in &error_stmt.2 {
+                    for clause in &try_catch.errors {
+                        for stmt in &clause.stmt {
                             stmt.recurse(cx, f);
                         }
                     }
 
-                    for stmt in &try_catch.catch_stmt {
-                        stmt.recurse(cx, f);
+                    if let Some(clause) = try_catch.catch_all.as_ref() {
+                        for stmt in &clause.stmt {
+                            stmt.recurse(cx, f);
+                        }
                     }
                 }
                 _ => (),

+ 16 - 24
src/sema/dotgraphviz.rs

@@ -1813,45 +1813,37 @@ impl Dot {
 
                     self.add_statement(&try_catch.ok_stmt, func, ns, parent, String::from("ok"));
 
-                    for (_, param, stmt) in &try_catch.errors {
+                    for clause in &try_catch.errors {
                         self.add_node(
                             Node::new(
                                 "error_param",
                                 vec![format!(
                                     "{} {}",
-                                    param.ty.to_string(ns),
-                                    param.name_as_str()
+                                    clause.param.as_ref().unwrap().ty.to_string(ns),
+                                    clause.param.as_ref().unwrap().name_as_str()
                                 )],
                             ),
                             Some(parent),
                             Some(String::from("error parameter")),
                         );
 
-                        self.add_statement(stmt, func, ns, parent, String::from("error"));
+                        self.add_statement(&clause.stmt, func, ns, parent, String::from("error"));
                     }
 
-                    if let Some(param) = &try_catch.catch_param {
-                        self.add_node(
-                            Node::new(
-                                "catch_param",
-                                vec![format!(
-                                    "{} {}",
-                                    param.ty.to_string(ns),
-                                    param.name_as_str()
-                                )],
-                            ),
-                            Some(parent),
-                            Some(String::from("catch parameter")),
-                        );
+                    if let Some(clause) = try_catch
+                        .catch_all
+                        .as_ref()
+                        .filter(|clause| clause.param.is_some())
+                    {
+                        let param = clause.param.as_ref().unwrap();
+                        let label = format!("{} {}", param.ty.to_string(ns), param.name_as_str());
+                        let node = Node::new("catch_param", vec![label]);
+                        self.add_node(node, Some(parent), Some(String::from("catch parameter")));
                     }
 
-                    self.add_statement(
-                        &try_catch.catch_stmt,
-                        func,
-                        ns,
-                        parent,
-                        String::from("catch"),
-                    );
+                    if let Some(clause) = try_catch.catch_all.as_ref() {
+                        self.add_statement(&clause.stmt, func, ns, parent, String::from("catch"));
+                    }
                 }
                 Statement::Underscore(loc) => {
                     let labels = vec![

+ 5 - 3
src/sema/mutability.rs

@@ -278,10 +278,12 @@ fn recurse_statements(stmts: &[Statement], ns: &Namespace, state: &mut StateChec
             Statement::TryCatch(_, _, try_catch) => {
                 try_catch.expr.recurse(state, read_expression);
                 recurse_statements(&try_catch.ok_stmt, ns, state);
-                for (_, _, s) in &try_catch.errors {
-                    recurse_statements(s, ns, state);
+                for clause in &try_catch.errors {
+                    recurse_statements(&clause.stmt, ns, state);
+                }
+                if let Some(clause) = try_catch.catch_all.as_ref() {
+                    recurse_statements(&clause.stmt, ns, state);
                 }
-                recurse_statements(&try_catch.catch_stmt, ns, state);
             }
             Statement::Emit { loc, .. } => state.write(loc),
             Statement::Revert { args, .. } => {

+ 21 - 7
src/sema/statements.rs

@@ -2414,9 +2414,8 @@ fn try_catch(
 
     let mut clauses_unique = HashSet::new();
     let mut errors_resolved = Vec::new();
-    let mut catch_param = None;
-    let mut catch_param_pos = None;
-    let mut catch_stmt_resolved = Vec::new();
+
+    let mut catch_all = None;
 
     clause_stmts.iter().try_for_each(|clause_stmt| {
         let (loc, name) = match clause_stmt {
@@ -2439,6 +2438,10 @@ fn try_catch(
             CatchClause::Simple(_, param, stmt) => {
                 symtable.new_scope();
 
+                let mut catch_param = None;
+                let mut catch_param_pos = None;
+                let mut catch_stmt_resolved = vec![];
+
                 if let Some(param) = param {
                     let (catch_ty, ty_loc) =
                         resolve_var_decl_ty(&param.ty, &param.storage, context, ns, diagnostics)?;
@@ -2500,6 +2503,12 @@ fn try_catch(
 
                 symtable.leave_scope();
 
+                catch_all = Some(super::ast::CatchClause {
+                    param: catch_param,
+                    param_pos: catch_param_pos,
+                    stmt: catch_stmt_resolved,
+                });
+
                 Ok(())
             }
             CatchClause::Named(_, id, param, stmt) => {
@@ -2610,11 +2619,16 @@ fn try_catch(
         TryCatch {
             expr: fcall,
             returns: params,
-            errors: errors_resolved,
+            errors: errors_resolved
+                .iter()
+                .map(|(pos, param, stmt)| super::ast::CatchClause {
+                    param: param.clone().into(),
+                    param_pos: *pos,
+                    stmt: stmt.clone(),
+                })
+                .collect(),
             ok_stmt: ok_resolved,
-            catch_param,
-            catch_param_pos,
-            catch_stmt: catch_stmt_resolved,
+            catch_all,
         },
     );
 

+ 1 - 3
src/sema/tests/mod.rs

@@ -116,9 +116,7 @@ fn test_statement_reachable() {
                     returns: vec![],
                     ok_stmt: vec![],
                     errors: vec![],
-                    catch_param: None,
-                    catch_param_pos: None,
-                    catch_stmt: vec![],
+                    catch_all: None,
                 },
             ),
             true,

+ 46 - 44
tests/codegen_testcases/solidity/scale.sol

@@ -21,9 +21,7 @@ contract ExternalFunctions {
         // CHECK: ty:bytes %abi_encoded.temp.5 = (alloc bytes len uint32 8)
         // CHECK: writebuffer buffer:%abi_encoded.temp.5 offset:uint32 0 value:(load (struct (arg #0) field 0))
         // CHECK: writebuffer buffer:%abi_encoded.temp.5 offset:uint32 4 value:int32 102
-        // CHECK: _ = external call::regular address:(load (struct (arg #0) field 1)) payload:%abi_encoded.temp.5 value:uint128 0 gas:uint64 0 accounts: seeds:
-        // CHECK: ty:uint32 %temp.6 = (builtin ArrayLength ((external call return data)))
-        // CHECK: branchcond (unsigned uint32 8 <= %temp.6), block3, block4
+        // CHECK: %success.temp.6 = external call::regular address:(load (struct (arg #0) field 1)) payload:%abi_encoded.temp.5 value:uint128 0 gas:uint64 0 accounts: seeds: contract|function:_ flags:
 
         // CHECK: block1: # noassert
         // CHECK: return
@@ -31,25 +29,29 @@ contract ExternalFunctions {
         // CHECK: block2: # doassert
         // CHECK: assert-failure
 
-        // CHECK: block3: # inbounds
-        // CHECK: ty:uint64 %temp.7 = (builtin ReadFromBuffer ((external call return data), uint32 0))
-        // CHECK: branchcond (unsigned less uint32 8 < %temp.6), block5, block6
+        // CHECK: block3: # ret_success
+        // CHECK: ty:uint32 %temp.7 = (builtin ArrayLength ((external call return data)))
+        // CHECK: branchcond (unsigned uint32 8 <= %temp.7), block6, block7
 
-        // CHECK: block4: # out_of_bounds
+        // CHECK: block6: # inbounds
+        // CHECK: ty:uint64 %temp.8 = (builtin ReadFromBuffer ((external call return data), uint32 0))
+        // CHECK: branchcond (unsigned less uint32 8 < %temp.7), block8, block9
+
+        // CHECK: block7: # out_of_bounds
         // CHECK: assert-failure
 
-        // CHECK: block5: # not_all_bytes_read
+        // CHECK: block8: # not_all_bytes_read
         // CHECK: assert-failure
     }
 
     // BEGIN-CHECK: ExternalFunctions::ExternalFunctions::function::storage_callback
     function storage_callback() public {
-        // CHECK: %temp.8 = load storage slot(uint256 0) ty:function(int32) external returns (uint64)
-        // CHECK: ty:bytes %abi_encoded.temp.9 = (alloc bytes len uint32 40)
-        // CHECK: writebuffer buffer:%abi_encoded.temp.9 offset:uint32 0 value:hex"f503f5fe"
-        // CHECK: writebuffer buffer:%abi_encoded.temp.9 offset:uint32 4 value:(load (struct function(int32) external returns (uint64)(%temp.8) field 1))
-        // CHECK: writebuffer buffer:%abi_encoded.temp.9 offset:uint32 36 value:(load (struct function(int32) external returns (uint64)(%temp.8) field 0))
-        // CHECK: _ = external call::regular address:(load (builtin GetAddress ())) payload:%abi_encoded.temp.9 value:uint128 0 gas:uint64 0 accounts: seeds:
+        // CHECK: %temp.9 = load storage slot(uint256 0) ty:function(int32) external returns (uint64)
+        // CHECK: ty:bytes %abi_encoded.temp.10 = (alloc bytes len uint32 40)
+        // CHECK: writebuffer buffer:%abi_encoded.temp.10 offset:uint32 0 value:hex"f503f5fe"
+        // CHECK: writebuffer buffer:%abi_encoded.temp.10 offset:uint32 4 value:(load (struct function(int32) external returns (uint64)(%temp.9) field 1))
+        // CHECK: writebuffer buffer:%abi_encoded.temp.10 offset:uint32 36 value:(load (struct function(int32) external returns (uint64)(%temp.9) field 0))
+        // CHECK: external call::regular address:(load (builtin GetAddress ())) payload:%abi_encoded.temp.10 value:uint128 0 gas:uint64 0 accounts: seeds:
         this.bar(func);
     }
 }
@@ -63,24 +65,24 @@ contract CompactEncoding {
         // CHECK: branchcond (unsigned more (builtin ArrayLength ((arg #0))) > uint32 1073741823), block6, block7
 
         // CHECK: block1: # small
-        // CHECK: ty:uint32 %temp.23 = uint32 1
+        // CHECK: ty:uint32 %temp.25 = uint32 1
         // CHECK: branch block5
 
         // CHECK: block2: # medium
-        // CHECK: ty:uint32 %temp.23 = uint32 2
+        // CHECK: ty:uint32 %temp.25 = uint32 2
         // CHECK: branch block5
 
         // CHECK: block3: # medium_or_big
         // CHECK: branchcond (unsigned more (builtin ArrayLength ((arg #0))) > uint32 16383), block4, block2
 
         // CHECK: block4: # big
-        // CHECK: ty:uint32 %temp.23 = uint32 4
+        // CHECK: ty:uint32 %temp.25 = uint32 4
         // CHECK: branch block5
 
         // CHECK: block5: # done
-        // CHECK: ty:bytes %abi_encoded.temp.24 = (alloc bytes len (%temp.23 + (builtin ArrayLength ((arg #0)))))
-        // CHECK: ty:uint32 %temp.25 = (builtin ArrayLength ((arg #0)))
-        // CHECK: branchcond (unsigned more %temp.25 > uint32 1073741823), block13, block14
+        // CHECK: ty:bytes %abi_encoded.temp.26 = (alloc bytes len (%temp.25 + (builtin ArrayLength ((arg #0)))))
+        // CHECK: ty:uint32 %temp.27 = (builtin ArrayLength ((arg #0)))
+        // CHECK: branchcond (unsigned more %temp.27 > uint32 1073741823), block13, block14
 
         // CHECK: block6: # fail
         // CHECK: assert-failure
@@ -89,29 +91,29 @@ contract CompactEncoding {
         // CHECK: branchcond (unsigned more (builtin ArrayLength ((arg #0))) > uint32 63), block3, block1
 
         // CHECK: block8: # small
-        // CHECK: writebuffer buffer:%abi_encoded.temp.24 offset:uint32 0 value:uint8((%temp.25 * uint32 4))
-        // CHECK: ty:uint32 %temp.26 = uint32 1
+        // CHECK: writebuffer buffer:%abi_encoded.temp.26 offset:uint32 0 value:uint8((%temp.27 * uint32 4))
+        // CHECK: ty:uint32 %temp.28 = uint32 1
         // CHECK: branch block12
 
         // CHECK: block9: # medium
-        // CHECK: writebuffer buffer:%abi_encoded.temp.24 offset:uint32 0 value:uint16(((%temp.25 * uint32 4) | uint32 1))
-        // CHECK: ty:uint32 %temp.26 = uint32 2
+        // CHECK: writebuffer buffer:%abi_encoded.temp.26 offset:uint32 0 value:uint16(((%temp.27 * uint32 4) | uint32 1))
+        // CHECK: ty:uint32 %temp.28 = uint32 2
         // CHECK: branch block12
 
         // CHECK: block10: # medium_or_big
-        // CHECK: branchcond (unsigned more %temp.25 > uint32 16383), block11, block9
+        // CHECK: branchcond (unsigned more %temp.27 > uint32 16383), block11, block9
 
         // CHECK: block11: # big
-        // CHECK: writebuffer buffer:%abi_encoded.temp.24 offset:uint32 0 value:((%temp.25 * uint32 4) | uint32 2)
-        // CHECK: ty:uint32 %temp.26 = uint32 4
+        // CHECK: writebuffer buffer:%abi_encoded.temp.26 offset:uint32 0 value:((%temp.27 * uint32 4) | uint32 2)
+        // CHECK: ty:uint32 %temp.28 = uint32 4
         // CHECK: branch block12
 
         // CHECK: block12: # done
-        // CHECK: memcpy src: (arg #0), dest: (advance ptr: %abi_encoded.temp.24, by: (uint32 0 + %temp.26)), bytes_len: %temp.25
-        // CHECK: ty:bytes %enc = %abi_encoded.temp.24
-        // CHECK: ty:uint32 %temp.27 = (builtin ArrayLength (%enc))
-        // CHECK: ty:uint32 %temp.29 = (zext uint32 (builtin ReadFromBuffer (%enc, uint32 0)))
-        // CHECK: switch (%temp.29 & uint32 3):
+        // CHECK: memcpy src: (arg #0), dest: (advance ptr: %abi_encoded.temp.26, by: (uint32 0 + %temp.28)), bytes_len: %temp.27
+        // CHECK: ty:bytes %enc = %abi_encoded.temp.26
+        // CHECK: ty:uint32 %temp.29 = (builtin ArrayLength (%enc))
+        // CHECK: ty:uint32 %temp.31 = (zext uint32 (builtin ReadFromBuffer (%enc, uint32 0)))
+        // CHECK: switch (%temp.31 & uint32 3):
         // CHECK:         case uint32 0: goto block #15
         // CHECK:         case uint32 1: goto block #16
         // CHECK:         case uint32 2: goto block #17
@@ -121,39 +123,39 @@ contract CompactEncoding {
         // CHECK: assert-failure
 
         // CHECK: block14: # prepare
-        // CHECK: branchcond (unsigned more %temp.25 > uint32 63), block10, block8
+        // CHECK: branchcond (unsigned more %temp.27 > uint32 63), block10, block8
 
         // CHECK: block15: # case_0
-        // CHECK: ty:uint32 %temp.28 = (%temp.29 >> uint32 2)
-        // CHECK: ty:uint32 %temp.29 = uint32 1
+        // CHECK: ty:uint32 %temp.30 = (%temp.31 >> uint32 2)
+        // CHECK: ty:uint32 %temp.31 = uint32 1
         // CHECK: branch block19
 
         // CHECK: block16: # case_1
-        // CHECK: ty:uint32 %temp.28 = ((zext uint32 (builtin ReadFromBuffer (%enc, uint32 0))) >> uint32 2)
-        // CHECK: ty:uint32 %temp.29 = uint32 2
+        // CHECK: ty:uint32 %temp.30 = ((zext uint32 (builtin ReadFromBuffer (%enc, uint32 0))) >> uint32 2)
+        // CHECK: ty:uint32 %temp.31 = uint32 2
         // CHECK: branch block19
 
         // CHECK: block17: # case_2
-        // CHECK: ty:uint32 %temp.28 = ((builtin ReadFromBuffer (%enc, uint32 0)) >> uint32 2)
-        // CHECK: ty:uint32 %temp.29 = uint32 4
+        // CHECK: ty:uint32 %temp.30 = ((builtin ReadFromBuffer (%enc, uint32 0)) >> uint32 2)
+        // CHECK: ty:uint32 %temp.31 = uint32 4
         // CHECK: branch block19
 
         // CHECK: block18: # case_default
         // CHECK: assert-failure
 
         // CHECK: block19: # done
-        // CHECK: branchcond (unsigned (uint32 0 + %temp.29) <= %temp.27), block20, block21
+        // CHECK: branchcond (unsigned (uint32 0 + %temp.31) <= %temp.29), block20, block21
 
         // CHECK: block20: # inbounds
-        // CHECK: branchcond (unsigned (uint32 0 + (%temp.28 + %temp.29)) <= %temp.27), block22, block23
+        // CHECK: branchcond (unsigned (uint32 0 + (%temp.30 + %temp.31)) <= %temp.29), block22, block23
 
         // CHECK: block21: # out_of_bounds
         // CHECK: assert-failure
 
         // CHECK: block22: # inbounds
-        // CHECK: ty:string %temp.30 = (alloc string len %temp.28)
-        // CHECK: memcpy src: (advance ptr: %enc, by: (uint32 0 + %temp.29)), dest: %temp.30, bytes_len: %temp.28
-        // CHECK: branchcond (unsigned less (uint32 0 + (%temp.28 + %temp.29)) < %temp.27), block24, block25
+        // CHECK: ty:string %temp.32 = (alloc string len %temp.30)
+        // CHECK: memcpy src: (advance ptr: %enc, by: (uint32 0 + %temp.31)), dest: %temp.32, bytes_len: %temp.30
+        // CHECK: branchcond (unsigned less (uint32 0 + (%temp.30 + %temp.31)) < %temp.29), block24, block25
 
         // CHECK: block23: # out_of_bounds
         // CHECK: assert-failure

+ 101 - 94
tests/codegen_testcases/solidity/unused_variable_elimination.sol

@@ -2,96 +2,104 @@
 
 contract c2 {
     int public cd;
+
     function sum(int32 a, int32 b) public pure returns (int32) {
-        return a+b;
+        return a + b;
     }
-// BEGIN-CHECK: function::doSomething
+
+    // BEGIN-CHECK: function::doSomething
     function doSomething() public returns (int, int) {
-        cd=2;
-// CHECK: store storage slot(uint256 0) ty:int256 =
+        cd = 2;
+        // CHECK: store storage slot(uint256 0) ty:int256 =
         return (1, 2);
     }
 }
 
 contract c {
     int32 g;
-// BEGIN-CHECK: c::function::test
-	function test() public returns (int32) {
-		int32 x = 102;
-        g = 3;
-		return 5;
-// NOT-CHECK: ty:int32 %x = int32 102
 
-	}
+    // BEGIN-CHECK: c::function::test
+    function test() public returns (int32) {
+        int32 x = 102;
+        g = 3;
+        return 5;
+        // NOT-CHECK: ty:int32 %x = int32 102
+    }
 
-// BEGIN-CHECK: c::function::test2
-	function test2() public view returns (int32) {
-		int32 x = 102;
+    // BEGIN-CHECK: c::function::test2
+    function test2() public view returns (int32) {
+        int32 x = 102;
         int32 y = 93;
         int32 t = 9;
-        x = 102 + t*y/(t+5*y) + g;
-		return 2;
-// NOT-CHECK: ty:int32 %x = int32 102
-// NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
-	}
-
-// BEGIN-CHECK: c::function::test3
-	function test3() public view returns (int32) {
-		int32 x;
+        x = 102 + (t * y) / (t + 5 * y) + g;
+        return 2;
+        // NOT-CHECK: ty:int32 %x = int32 102
+        // NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
+    }
+
+    // BEGIN-CHECK: c::function::test3
+    function test3() public view returns (int32) {
+        int32 x;
         int32 y = 93;
         int32 t = 9;
-        x = 102 + t*y/(t+5*y) + g;
-		return 2;
-// NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
-	}
-
-// BEGIN-CHECK: c::function::test4
-	function test4() public view returns (int32) {
-		int32 x;
+        x = 102 + (t * y) / (t + 5 * y) + g;
+        return 2;
+        // NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
+    }
+
+    // BEGIN-CHECK: c::function::test4
+    function test4() public view returns (int32) {
+        int32 x;
         int32 y = 93;
         int32 t = 9;
-        x = 102 + t*y/(t+5*y) + g + test3();
-		return 2;
-// NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
-// CHECK: call c::c::function::test3
-	}
+        x = 102 + (t * y) / (t + 5 * y) + g + test3();
+        return 2;
+        // NOT-CHECK: ty:int32 %x = (int32 103 + %temp.6)
+        // CHECK: call c::c::function::test3
+    }
 
-// BEGIN-CHECK: c::function::test5
+    // BEGIN-CHECK: c::function::test5
     function test5() public returns (int32) {
-		int32 x;
+        int32 x;
         int32[] vec;
         int32 y = 93;
         int32 t = 9;
         c2 ct = new c2();
-        x = 102 + t*y/(t+5*y) + g + test3() - vec.push(2) + ct.sum(1, 2);
-		return 2;
-// CHECK: push array ty:int32[] value:int32 2
-// CHECK: _ = external call::regular address:%ct payload:%abi_encoded.temp.94 value:uint128 0 gas:uint64 0 accounts: seeds:
-}
-
+        x =
+            102 +
+            (t * y) /
+            (t + 5 * y) +
+            g +
+            test3() -
+            vec.push(2) +
+            ct.sum(1, 2);
+        return 2;
+        // CHECK: push array ty:int32[] value:int32 2
+        // CHECK: external call::regular address:%ct
+        // CHECK: return int32 2
+    }
 }
 
 contract c3 {
-// BEGIN-CHECK: c3::function::test6
+    // BEGIN-CHECK: c3::function::test6
     function test6() public returns (int32) {
         c2 ct = new c2();
 
         return 3;
-// CHECK: constructor(no: ) salt: value: gas:uint64 0 address: seeds: c2 encoded buffer: %abi_encoded.temp.117 accounts:
+        // CHECK: constructor(no: ) salt: value: gas:uint64 0 address: seeds: c2 encoded buffer: %abi_encoded.temp.120 accounts:
     }
 
-// BEGIN-CHECK: c3::function::test7
+    // BEGIN-CHECK: c3::function::test7
     function test7() public returns (int32) {
         c2 ct = new c2();
-// constructor salt: value: gas:uint64 0 address: seeds: c2 (encoded buffer: %abi_encoded.temp.119, buffer len: uint32 4)
+        // constructor salt: value: gas:uint64 0 address: seeds: c2 (encoded buffer: %abi_encoded.temp.123, buffer len: uint32 4)
         address ad = address(ct);
-        (bool p, ) = ad.call(hex'ba');
-// CHECK: external call::regular address:%ad payload:(alloc bytes uint32 1 hex"ba") value:uint128 0 gas:uint64 0
-// NOT-CHECk: ty:bool %p = %success.temp.30
-return 3;
+        (bool p, ) = ad.call(hex"ba");
+        // CHECK: external call::regular address:%ad payload:(alloc bytes uint32 1 hex"ba") value:uint128 0 gas:uint64 0
+        // NOT-CHECk: ty:bool %p = %success.temp
+        return 3;
     }
 
-
     struct testStruct {
         int a;
         int b;
@@ -101,9 +109,8 @@ return 3;
     int public it1;
     int private it2;
 
-
-// BEGIN-CHECK: c3::function::test8
-    function test8() public returns (int){
+    // BEGIN-CHECK: c3::function::test8
+    function test8() public returns (int) {
         testStruct storage t2 = t1;
         t2.a = 5;
 
@@ -115,42 +122,42 @@ return 3;
         it2 = 1;
 
         return 2;
-// CHECK: store storage slot((overflowing %t2 + uint256 0)) ty:int256 =
-// CHECK: store storage slot(uint256 2) ty:int256 =
-// NOT-CHECK: ty:struct c1.testStruct %t3 = struct { int256 1, int256 2 }
-// NOT-CHECK: alloc int256[] len uint32 5
-// NOT-CHECK: store storage slot(uint256 3) ty:int256
+        // CHECK: store storage slot((overflowing %t2 + uint256 0)) ty:int256 =
+        // CHECK: store storage slot(uint256 2) ty:int256 =
+        // NOT-CHECK: ty:struct c1.testStruct %t3 = struct { int256 1, int256 2 }
+        // NOT-CHECK: alloc int256[] len uint32 5
+        // NOT-CHECK: store storage slot(uint256 3) ty:int256
     }
 
-// BEGIN-CHECK: c3::function::test9
+    // BEGIN-CHECK: c3::function::test9
     function test9() public view returns (int) {
         int f = 4;
 
-        int c = 32 +4 *(f = it1+it2);
-//CHECK: ty:int256 %f =
+        int c = 32 + 4 * (f = it1 + it2);
+        //CHECK: ty:int256 %f =
         return f;
     }
 
-// BEGIN-CHECK: c3::function::test10
+    // BEGIN-CHECK: c3::function::test10
     function test10() public view returns (int) {
         int f = 4;
 
-        int c = 32 +4 *(f = it1+it2);
-// CHECK: ty:int256 %c = (int256 32 + (sext int256 (int64 4 * (trunc int64 (%temp.126 + %temp.127)))))
-// NOT-CHECK: ty:int256 %f = (%temp.10 + %temp.11)
+        int c = 32 + 4 * (f = it1 + it2);
+        // CHECK: ty:int256 %c = (int256 32 + (sext int256 (int64 4 * (trunc int64 (%temp.130 + %temp.131)))))
+        // NOT-CHECK: ty:int256 %f = (%temp.
         return c;
     }
 
-// BEGIN-CHECK: c3::function::test11
+    // BEGIN-CHECK: c3::function::test11
     function test11() public returns (int) {
         c2 ct = new c2();
         (int a, int b) = ct.doSomething();
-// CHECK: ty:int256 %b =
-// NOT-CHECK: ty:int256 %a =
+        // CHECK: ty:int256 %b =
+        // NOT-CHECK: ty:int256 %a =
         return b;
     }
 
-// BEGIN-CHECK: c3::function::test12
+    // BEGIN-CHECK: c3::function::test12
     function test12() public returns (int) {
         c2 ct = new c2();
         int a = 1;
@@ -161,9 +168,8 @@ return 3;
         return a;
     }
 
-// BEGIN-CHECK: c3::function::test13
-    function test13() public returns (int){
-
+    // BEGIN-CHECK: c3::function::test13
+    function test13() public returns (int) {
         int[] memory vec = new int[](5);
         // CHECK: alloc int256[] len uint32 5
         vec[0] = 3;
@@ -173,40 +179,41 @@ return 3;
 
     int[] testArr;
 
-// BEGIN-CHECK: c3::function::test14
+    // BEGIN-CHECK: c3::function::test14
     function test14() public returns (int) {
         int[] storage ptrArr = testArr;
 
-// CHECK: store storage slot(%temp.146) ty:int256 storage = int256 3
+        // CHECK: store storage slot(%temp.154) ty:int256 storage = int256 3
         ptrArr.push(3);
 
         return ptrArr[0];
     }
 
-// BEGIN-CHECK: c3::function::test15
+    // BEGIN-CHECK: c3::function::test15
     function test15() public returns (int) {
         int[4] memory arr = [1, 2, 3, 4];
-// CHECK: ty:int256[4] %arr = [4] [ int256 1, int256 2, int256 3, int256 4 ]
+        // CHECK: ty:int256[4] %arr = [4] [ int256 1, int256 2, int256 3, int256 4 ]
         return arr[2];
     }
 
-// BEGIN-CHECK: c3::function::test16
+    // BEGIN-CHECK: c3::function::test16
     function test16() public returns (int) {
         int[4] memory arr = [1, 2, 3, 4];
-// NOT-CHECK: ty:int256[4] %arr = [4] [ int256 1, int256 2, int256 3, int256 4 ]
+        // NOT-CHECK: ty:int256[4] %arr = [4] [ int256 1, int256 2, int256 3, int256 4 ]
         return 2;
     }
 
-// BEGIN-CHECK: c3::function::test17
+    // BEGIN-CHECK: c3::function::test17
     function test17() public pure returns (int) {
         int x;
-// NOT-CHECK: ty:int256 %x
+        // NOT-CHECK: ty:int256 %x
         int[] vec = new int[](2);
-        x = 5*vec.pop();
+        x = 5 * vec.pop();
         return 0;
-// CHECK: pop array ty:int256[]
+        // CHECK: pop array ty:int256[]
     }
-// BEGIN-CHECK: c3::function::test18
+
+    // BEGIN-CHECK: c3::function::test18
     function test18(address payable addr) public returns (bool) {
         bool p;
         p = false || addr.send(msg.value);
@@ -215,23 +222,23 @@ return 3;
     }
 
     int[] arrt;
-// BEGIN-CHECK: c3::function::test19
+
+    // BEGIN-CHECK: c3::function::test19
     function test19() public returns (int) {
         int x;
-    // NOT-CHECK: ty:int256 %x
+        // NOT-CHECK: ty:int256 %x
         int y;
         x = y + arrt.pop();
         // NOT-CHECK: clear storage slot
         return 0;
     }
 
-bytes bar;
-// BEGIN-CHECK: c3::function::test20
+    bytes bar;
+
+    // BEGIN-CHECK: c3::function::test20
     function test20() public {
         bytes1 x = bar.push();
-// NOT-CHECK: ty:bytes1 %x
-// CHECK: push storage ty:bytes1 slot
+        // NOT-CHECK: ty:bytes1 %x
+        // CHECK: push storage ty:bytes1 slot
     }
-
 }
-

+ 7 - 2
tests/polkadot.rs

@@ -546,8 +546,9 @@ impl Runtime {
 
         if ret == 0 {
             vm.accept_state(state.into_data(), value);
+            return Ok(0);
         }
-        Ok(ret)
+        Ok(2) // Callee reverted
     }
 
     #[seal(0)]
@@ -602,8 +603,9 @@ impl Runtime {
 
         if flags == 0 {
             vm.accept_state(state.into_data(), value);
+            return Ok(0);
         }
-        Ok(flags)
+        Ok(2) // Callee reverted
     }
 
     #[seal(0)]
@@ -895,6 +897,9 @@ impl MockSubstrate {
     /// `input` must contain the selector fo the constructor.
     pub fn raw_constructor(&mut self, input: Vec<u8>) {
         self.invoke("deploy", input).unwrap();
+        if let HostReturn::Data(flags, _) = self.0.data().output {
+            assert!(flags == 0)
+        }
     }
 
     /// Call the contract function `name` with the given input `args`.

+ 189 - 13
tests/polkadot_tests/calls.rs

@@ -2,6 +2,8 @@
 
 use crate::{build_solidity, build_solidity_with_options};
 use parity_scale_codec::{Decode, Encode};
+use primitive_types::U256;
+use solang::codegen::revert::{PanicCode, SolidityError};
 
 #[derive(Debug, PartialEq, Eq, Encode, Decode)]
 struct RevertReturn(u32, String);
@@ -275,9 +277,7 @@ fn try_catch_external_calls() {
     );
 
     runtime.constructor(0, Vec::new());
-    // FIXME: Try catch is broken; it tries to ABI decode an "Error(string)" in all cases,
-    // leading to a revert in the ABI decoder in case of "revert()" without any return data.
-    //runtime.function("test", Vec::new());
+    runtime.function("test", Vec::new());
 
     #[derive(Debug, PartialEq, Eq, Encode, Decode)]
     struct Ret(u32);
@@ -510,20 +510,20 @@ fn local_destructure_call() {
 #[test]
 fn payable_constructors() {
     // no contructors means constructor is not payable
-    // however there is no check for value transfers on constructor so endowment can be received
     let mut runtime = build_solidity(
         r##"
         contract c {
-            function test(string a) public {
-            }
+            function test(string a) public {}
         }"##,
     );
 
     runtime.set_transferred_value(1);
-    runtime.constructor(0, Vec::new());
+    runtime.raw_constructor_failure(runtime.contracts()[0].code.constructors[0].clone());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: non payable constructor"));
 
     // contructors w/o payable means can't send value
-    // however there is no check for value transfers on constructor so endowment can be received
     let mut runtime = build_solidity(
         r##"
         contract c {
@@ -531,13 +531,15 @@ fn payable_constructors() {
                 int32 a = 0;
             }
 
-            function test(string a) public {
-            }
+            function test(string a) public {}
         }"##,
     );
 
     runtime.set_transferred_value(1);
-    runtime.constructor(0, Vec::new());
+    runtime.raw_constructor_failure(runtime.contracts()[0].code.constructors[0].clone());
+    assert!(runtime
+        .debug_buffer()
+        .contains("runtime_error: non payable constructor"));
 
     // contructors w/ payable means can send value
     let mut runtime = build_solidity(
@@ -547,8 +549,7 @@ fn payable_constructors() {
                 int32 a = 0;
             }
 
-            function test(string a) public {
-            }
+            function test(string a) public {}
         }"##,
     );
 
@@ -1027,3 +1028,178 @@ fn constructors_and_messages_distinct_in_dispatcher() {
     // Expect calling the function via "deploy" to trap the contract
     runtime.raw_constructor_failure(function);
 }
+
+#[test]
+fn error_bubbling() {
+    let mut runtime = build_solidity(
+        r##"contract C {
+        function raw_call() public payable returns (bytes ret) {
+            B b = new B();
+            (bool ok, ret) = address(b).call{value: 5000}(bytes4(0x00000000));
+        }
+    
+        function normal_call() public payable {
+            B b = new B();
+            b.b();
+        }
+    
+        function ext_func_call() public payable {
+            A a = new A();
+            function() external payable func = a.a;
+            func{value: 1000}();
+            a.a();
+        }
+    
+    }
+    
+    contract B {
+        @selector([0, 0, 0, 0])
+        function b() public payable {
+            A a = new A();
+            a.a();
+        }
+    }
+    
+    contract A {
+        function a() public payable {
+            revert("no");
+        }
+    }
+    "##,
+    );
+
+    runtime.set_transferred_value(20000);
+    let expected_output = ([0x08u8, 0xc3, 0x79, 0xa0], "no".to_string()).encode();
+
+    // The raw call must not bubble up
+    runtime.function("raw_call", vec![]);
+    assert_eq!(runtime.output(), expected_output.encode());
+
+    runtime.function_expect_failure("normal_call", vec![]);
+    assert_eq!(runtime.output(), expected_output);
+    assert!(runtime.debug_buffer().contains("external call failed"));
+
+    runtime.function_expect_failure("ext_func_call", vec![]);
+    assert_eq!(runtime.output(), expected_output);
+    assert!(runtime.debug_buffer().contains("external call failed"));
+}
+
+#[test]
+fn constructor_reverts_bubbling() {
+    let mut runtime = build_solidity(
+        r##"
+        contract A {
+            B public b;
+            constructor(bool r) payable {
+                b = new B(r);
+            }
+        }
+    
+        contract B {
+            C public c;
+            constructor(bool r) payable {
+                c = new C(r);
+            }
+        }
+    
+        contract C {
+            uint public foo;
+            constructor(bool r) {
+                if (!r) revert("no");
+            }
+        }"##,
+    );
+
+    runtime.set_transferred_value(20000);
+    runtime.constructor(0, true.encode());
+
+    let mut input = runtime.contracts()[0].code.constructors[0].clone();
+    input.push(0);
+    runtime.raw_constructor_failure(input);
+
+    let expected_output = ([0x08u8, 0xc3, 0x79, 0xa0], "no".to_string()).encode();
+    assert_eq!(runtime.output(), expected_output);
+}
+
+#[test]
+fn try_catch_uncaught_bubbles_up() {
+    let mut runtime = build_solidity(
+        r##"contract C {
+        function c() public payable {
+            B b = new B();
+            b.b{value: 1000}();
+        }
+    }
+    
+    contract B {
+        function b() public payable {
+            A a = new A();
+            try a.a(0) {} catch Error(string) {}
+        }
+    }
+    
+    contract A {
+        function a(uint div) public pure returns(uint) {
+            return 123 / div;
+        }
+    }
+    "##,
+    );
+
+    runtime.set_transferred_value(10000);
+    runtime.function_expect_failure("c", vec![]);
+
+    let panic = PanicCode::DivisionByZero;
+    let expected_output = (
+        SolidityError::Panic(panic).selector().to_be_bytes(),
+        U256::from(panic as u8),
+    )
+        .encode();
+
+    assert_eq!(runtime.output(), expected_output);
+    assert!(runtime.debug_buffer().contains("external call failed"));
+}
+
+#[test]
+fn try_catch_transfer_fail() {
+    let mut runtime = build_solidity_with_options(
+        r#"contract runner {
+        function test(uint128 amount) public returns (bytes) {
+            try new aborting{value: amount}(true) returns (
+                aborting a
+            ) {} catch Error(string x) {
+                return hex"41";
+            } catch (bytes raw) {
+                return raw;
+            }
+
+            return hex"ff";
+        }
+    }
+
+    contract aborting {
+        constructor(bool abort) {
+            if (abort) {
+                revert("bar");
+            }
+        }
+
+        function foo() public pure {}
+    }"#,
+        true,
+        true,
+    );
+
+    // Expect the contract to catch the reverting child constructor
+    runtime.function("test", 0u128.encode());
+    assert_eq!(runtime.output(), vec![0x41u8].encode());
+    assert!(runtime.debug_buffer().contains("seal_instantiate=2"));
+
+    // Trying to instantiate with value while having insufficient funds result in
+    // seal_instantiate failing with transfer failed (return code 5).
+    // Now, the "catch (bytes raw)" clause should catch that, because there is no
+    // return data to be decoded.
+    runtime.function("test", 1u128.encode());
+    assert_eq!(runtime.output(), Vec::<u8>::new().encode());
+    assert!(runtime.debug_buffer().contains("seal_instantiate=5"));
+}

+ 1 - 1
tests/polkadot_tests/functions.rs

@@ -69,7 +69,7 @@ fn constructor_wrong_selector() {
         }",
     );
 
-    runtime.raw_constructor(vec![0xaa, 0xbb, 0xcc, 0xdd]);
+    runtime.raw_constructor_failure(vec![0xaa, 0xbb, 0xcc, 0xdd]);
     runtime.function("get", Vec::new());
 }
 

+ 53 - 0
tests/polkadot_tests/value.rs

@@ -540,3 +540,56 @@ fn nonpayable_constructor_reverts() {
     runtime.function("c", Vec::new());
     assert_eq!(runtime.output(), storage_value.encode());
 }
+
+#[test]
+fn transfer_bubble_up() {
+    let mut runtime = build_solidity(
+        r##"
+        contract C {
+            function c(uint128 amount) public payable {
+                Other o = new Other{value: 1000}();
+                o.payback(amount);
+            }
+        }
+
+        contract Other {
+            constructor() payable {}
+            function payback(uint128 amount) public payable {
+                payable(msg.sender).transfer(amount);
+            }
+        }"##,
+    );
+
+    runtime.set_transferred_value(2000);
+    runtime.function("c", 100u128.encode());
+
+    runtime.function_expect_failure("c", 1000000u128.encode());
+    assert!(runtime.output().is_empty());
+}
+
+#[test]
+fn send_does_not_bubble_up() {
+    let mut runtime = build_solidity(
+        r##"
+        contract C {
+            function c(uint128 amount) public payable returns(bool) {
+                Other o = new Other{value: 1000}();
+                return o.payback(amount);
+            }
+        }
+
+        contract Other {
+            constructor() payable {}
+            function payback(uint128 amount) public payable returns(bool) {
+                return payable(msg.sender).send(amount);
+            }
+        }"##,
+    );
+
+    runtime.set_transferred_value(2000);
+    runtime.function("c", 100u128.encode());
+    assert_eq!(runtime.output(), true.encode());
+
+    runtime.function("c", 1000000u128.encode());
+    assert_eq!(runtime.output(), false.encode());
+}

+ 44 - 40
tests/undefined_variable_detection.rs

@@ -10,7 +10,11 @@ use std::ffi::OsStr;
 fn parse_and_codegen(src: &'static str) -> Namespace {
     let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
-    let mut ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM);
+    let mut ns = parse_and_resolve(
+        OsStr::new("test.sol"),
+        &mut cache,
+        Target::default_polkadot(),
+    );
     let opt = Options {
         dead_storage: false,
         constant_folding: false,
@@ -604,39 +608,40 @@ contract test {
 
 #[test]
 fn try_catch() {
-    //TODO: Fix this test case
-
-    // let file = r#"
-    // contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} }
-    // contract Example {
-    //     AddNumbers addContract;
-    //     event StringFailure(string stringFailure);
-    //     event BytesFailure(bytes bytesFailure);
-    //
-    //     function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) {
-    //         bytes r;
-    //         try addContract.add(_a, _b) returns (uint256 _value) {
-    //             r = hex"ABCD";
-    //             return r;
-    //         } catch Error(string memory _err) {
-    //             r = hex"ABCD";
-    //             emit StringFailure(_err);
-    //         } catch (bytes memory _err) {
-    //             emit BytesFailure(_err);
-    //         }
-    //
-    //         return r;
-    //     }
-    //
-    // }
-    // "#;
-    //
-    // let ns = parse_and_codegen(file);
-    // let errors = ns.diagnostics.errors();
-    // assert_eq!(errors.len(), 1);
-    // assert_eq!(errors[0].message, "Variable 'r' is undefined");
-    // assert_eq!(errors[0].notes.len(), 1);
-    // assert_eq!(errors[0].notes[0].message, "Variable read before being defined");
+    let file = r#"
+     contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} }
+     contract Example {
+         AddNumbers addContract;
+         event StringFailure(string stringFailure);
+         event BytesFailure(bytes bytesFailure);
+    
+         function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) {
+             bytes r;
+             try addContract.add(_a, _b) returns (uint256 _value) {
+                 r = hex"ABCD";
+                 return r;
+             } catch Error(string memory _err) {
+                 r = hex"ABCD";
+                 emit StringFailure(_err);
+             } catch (bytes memory _err) {
+                 emit BytesFailure(_err);
+             }
+    
+             return r;
+         }
+    
+     }
+     "#;
+
+    let ns = parse_and_codegen(file);
+    let errors = ns.diagnostics.errors();
+    assert_eq!(errors.len(), 1);
+    assert_eq!(errors[0].message, "Variable 'r' is undefined");
+    assert_eq!(errors[0].notes.len(), 1);
+    assert_eq!(
+        errors[0].notes[0].message,
+        "Variable read before being defined"
+    );
 
     let file = r#"
     contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} }
@@ -688,7 +693,6 @@ fn try_catch() {
 
             return r;
         }
-
     }
     "#;
 
@@ -696,11 +700,11 @@ fn try_catch() {
     let errors = ns.diagnostics.errors();
     assert_eq!(errors.len(), 1);
     assert_eq!(errors[0].message, "Variable 'r' is undefined");
-    assert_eq!(errors[0].notes.len(), 1);
-    assert_eq!(
-        errors[0].notes[0].message,
-        "Variable read before being defined"
-    );
+    assert_eq!(errors[0].notes.len(), 2);
+    assert!(errors[0]
+        .notes
+        .iter()
+        .all(|note| { note.message == "Variable read before being defined" }));
 
     let file = r#"
     contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} }