Selaa lähdekoodia

Print the return values of seal API calls into the debug buffer (#1047)

Signed-off-by: xermicus <bigcyrill@hotmail.com>
Co-authored-by: Sean Young <sean@mess.org>
Cyrill Leutwiler 3 vuotta sitten
vanhempi
sitoutus
4ad568fff3

+ 40 - 9
docs/optimizer.rst → docs/code_gen_options.rst

@@ -1,5 +1,14 @@
+Code Generation Options
+=======================
+
+There are compiler flags to control code generation. They can be divided into two categories: 
+
+* Optimizer passes are enabled by default and make the generated code more optimal. 
+* Debugging options are not enabled by default, but they greatly improve
+  developer experience, at the cost of increased gas and storage usage.
+
 Optimizer Passes
-================
+----------------
 
 Solang generates its own internal IR, before the LLVM IR is generated. This internal IR allows us to do
 several optimizations which LLVM cannot do, since it is not aware of higher-level language constructs.
@@ -10,7 +19,7 @@ So we need to do our own optimizations for these types, and we cannot rely on LL
 .. _constant-folding:
 
 Constant Folding Pass
----------------------
++++++++++++++++++++++
 
 There is a constant folding (also called constant propagation) pass done, before all the other passes. This
 helps arithmetic of large types, and also means that the functions are constant folded when their arguments
@@ -27,7 +36,7 @@ the hover will tell you the value of the hash.
 .. _strength-reduce:
 
 Strength Reduction Pass
------------------------
++++++++++++++++++++++++
 
 Strength reduction is when expensive arithmetic is replaced with cheaper ones. So far, the following types
 of arithmetic may be replaced:
@@ -59,7 +68,7 @@ will see this noted.
 .. _dead-storage:
 
 Dead Storage pass
------------------
++++++++++++++++++
 
 Loading from contract storage, or storing to contract storage is expensive. This optimization removes any
 redundant load from and store to contract storage. If the same variable is read twice, then the value from
@@ -91,7 +100,7 @@ having this optimization pass on by comparing the output of `solang --no-dead-st
 .. _vector-to-slice:
 
 Vector to Slice Pass
---------------------
+++++++++++++++++++++
 
 A `bytes` or `string` variable can be stored in a vector, which is a modifyable in-memory buffer, and a slice
 which is a pointer to readonly memory and an a length. Since a vector is modifyable, each instance requires
@@ -128,7 +137,7 @@ having this optimization pass on by comparing the output of `solang --no-vector-
 .. _unused-variable-elimination:
 
 Unused Variable Elimination
-----------------------------
++++++++++++++++++++++++++++
 
 
 During the semantic analysis, Solang detects unused variables and raises warnings for them.
@@ -157,7 +166,7 @@ expressions inside the function.
 .. _common-subexpression-elimination:
 
 Common Subexpression Elimination
----------------------------------
+++++++++++++++++++++++++++++++++
 
 
 Solang performs common subexpression elimination by doing two passes over the CFG (Control
@@ -190,7 +199,7 @@ this contract and check the CFG, using `solang --emit cfg`.
 .. _Array-Bound-checks-optimizations:
 
 Array Bound checks optimization
--------------------------------
++++++++++++++++++++++++++++++++
 
 Whenever an array access is done, there must be a check for ensuring we are not accessing
 beyond the end of an array. Sometimes, the array length could be known. For example:
@@ -218,4 +227,26 @@ always true, so the check is omitted.
 This also means that, whenever the length of an array is accessed using '.length', it is replaced with a constant.
 
 Note that this optimization does not cover every case. When an array is passed
-as a function argument, for instance, the length is unknown.
+as a function argument, for instance, the length is unknown.
+
+Debugging Options
+-----------------
+
+It may be desirable to have access to debug information regarding the contract execution. 
+However, this will lead to more instructions as well as higher gas usage. Hence, it is disabled by default,
+but can be enabled using CLI flags. Just make sure not to use them in production deployments.
+
+.. _log-api-return-codes:
+
+Log runtime API call results
+++++++++++++++++++++++++++++
+
+Runtime API calls are not guaranteed to succeed.
+By design, the low level results of these calls are abstracted away in Solidity.
+For development purposes, it can be desirable to observe the low level return code of such calls.
+The ``--log-api-return-codes`` flag will make the contract print the return code of runtime calls, if there are any.
+
+.. note::
+
+    This is only implemented for the Substrate target.
+

+ 1 - 1
docs/index.rst

@@ -78,7 +78,7 @@ Contents
    :maxdepth: 3
    :caption: Extras
 
-   optimizer
+   code_gen_options
    testing
    contributing
 

+ 3 - 0
docs/running.rst

@@ -112,6 +112,9 @@ Options:
 \\-\\-no\\-cse
    Disable the :ref:`common-subexpression-elimination` optimization
 
+\\-\\-log\\-api\\-return\\-codes
+   Disable the :ref:`common-subexpression-elimination` optimization
+
 Generating Documentation Usage
 ______________________________
 

+ 11 - 0
src/bin/solang.rs

@@ -171,6 +171,12 @@ fn main() {
                             .long("math-overflow")
                             .display_order(6),
                     )
+                    .arg(
+                        Arg::new("LOGAPIRETURNS")
+                            .help("Log the return codes of runtime API calls in the environment")
+                            .long("log-api-return-codes")
+                            .action(ArgAction::SetTrue),
+                    )
                     .arg(
                         Arg::new("GENERATEDEBUGINFORMATION")
                             .help("Enable generating debug information for LLVM IR")
@@ -378,6 +384,8 @@ fn compile(matches: &ArgMatches) {
 
     let generate_debug_info = matches.contains_id("GENERATEDEBUGINFORMATION");
 
+    let log_api_return_codes = *matches.get_one::<bool>("LOGAPIRETURNS").unwrap();
+
     let mut resolver = imports_arg(matches);
 
     let opt_level = match matches.get_one::<String>("OPT").unwrap().as_str() {
@@ -399,6 +407,7 @@ fn compile(matches: &ArgMatches) {
             .get_one::<bool>("COMMONSUBEXPRESSIONELIMINATION")
             .unwrap(),
         opt_level,
+        log_api_return_codes,
     };
 
     let mut namespaces = Vec::new();
@@ -440,6 +449,7 @@ fn compile(matches: &ArgMatches) {
             opt_level.into(),
             math_overflow_check,
             generate_debug_info,
+            opt.log_api_return_codes,
         );
 
         if !save_intermediates(&binary, matches) {
@@ -631,6 +641,7 @@ fn process_file(
             opt.opt_level.into(),
             opt.math_overflow_check,
             opt.generate_debug_information,
+            opt.log_api_return_codes,
         );
 
         if save_intermediates(&binary, matches) {

+ 3 - 0
src/codegen/mod.rs

@@ -90,6 +90,7 @@ pub struct Options {
     pub common_subexpression_elimination: bool,
     pub generate_debug_information: bool,
     pub opt_level: OptimizationLevel,
+    pub log_api_return_codes: bool,
 }
 
 impl Default for Options {
@@ -103,6 +104,7 @@ impl Default for Options {
             common_subexpression_elimination: true,
             generate_debug_information: false,
             opt_level: OptimizationLevel::Default,
+            log_api_return_codes: false,
         }
     }
 }
@@ -162,6 +164,7 @@ pub fn codegen(ns: &mut Namespace, opt: &Options) {
                         opt.opt_level.into(),
                         opt.math_overflow_check,
                         opt.generate_debug_information,
+                        opt.log_api_return_codes,
                     );
 
                     let code = binary.code(Generate::Linked).expect("llvm build");

+ 8 - 0
src/emit/binary.rs

@@ -43,6 +43,7 @@ pub struct Binary<'a> {
     pub(crate) constructor_abort_value_transfers: bool,
     pub(crate) math_overflow_check: bool,
     pub(crate) generate_debug_info: bool,
+    pub(crate) log_api_return_codes: bool,
     pub builder: Builder<'a>,
     pub dibuilder: DebugInfoBuilder<'a>,
     pub compile_unit: DICompileUnit<'a>,
@@ -68,6 +69,7 @@ impl<'a> Binary<'a> {
         opt: OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Self {
         let std_lib = load_stdlib(context, &ns.target);
         match ns.target {
@@ -80,6 +82,7 @@ impl<'a> Binary<'a> {
                 opt,
                 math_overflow_check,
                 generate_debug_info,
+                log_api_return_codes,
             ),
             Target::Solana => solana::SolanaTarget::build(
                 context,
@@ -90,6 +93,7 @@ impl<'a> Binary<'a> {
                 opt,
                 math_overflow_check,
                 generate_debug_info,
+                log_api_return_codes,
             ),
             Target::EVM => unimplemented!(),
         }
@@ -103,6 +107,7 @@ impl<'a> Binary<'a> {
         opt: OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Self {
         assert!(namespaces.iter().all(|ns| ns.target == Target::Solana));
 
@@ -115,6 +120,7 @@ impl<'a> Binary<'a> {
             opt,
             math_overflow_check,
             generate_debug_info,
+            log_api_return_codes,
         )
     }
 
@@ -237,6 +243,7 @@ impl<'a> Binary<'a> {
         std_lib: &Module<'a>,
         runtime: Option<Box<Binary<'a>>>,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Self {
         LLVM_INIT.get_or_init(|| {
             inkwell::targets::Target::initialize_webassembly(&Default::default());
@@ -310,6 +317,7 @@ impl<'a> Binary<'a> {
             constructor_abort_value_transfers: false,
             math_overflow_check,
             generate_debug_info,
+            log_api_return_codes,
             builder,
             dibuilder,
             compile_unit,

+ 1 - 0
src/emit/expression.rs

@@ -131,6 +131,7 @@ pub(super) fn expression<'a, T: TargetRuntime<'a> + ?Sized>(
                 bin.opt,
                 bin.math_overflow_check,
                 bin.generate_debug_info,
+                bin.log_api_return_codes,
             );
 
             let code = if *runtime && target_bin.runtime.is_some() {

+ 4 - 0
src/emit/solana/mod.rs

@@ -44,6 +44,7 @@ impl SolanaTarget {
         opt: OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Binary<'a> {
         let mut target = SolanaTarget {
             abi: ethabiencoder::EthAbiDecoder { bswap: true },
@@ -60,6 +61,7 @@ impl SolanaTarget {
             std_lib,
             None,
             generate_debug_info,
+            log_api_return_codes,
         );
 
         binary
@@ -118,6 +120,7 @@ impl SolanaTarget {
         opt: OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Binary<'a> {
         let mut target = SolanaTarget {
             abi: ethabiencoder::EthAbiDecoder { bswap: true },
@@ -134,6 +137,7 @@ impl SolanaTarget {
             std_lib,
             None,
             generate_debug_info,
+            log_api_return_codes,
         );
 
         binary

+ 48 - 0
src/emit/substrate/mod.rs

@@ -132,6 +132,7 @@ impl SubstrateTarget {
         opt: OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> Binary<'a> {
         let mut binary = Binary::new(
             context,
@@ -143,6 +144,7 @@ impl SubstrateTarget {
             std_lib,
             None,
             generate_debug_info,
+            log_api_return_codes,
         );
 
         binary.set_early_value_aborts(contract, ns);
@@ -1851,3 +1853,49 @@ fn event_id<'b>(
 
     Some(binary.context.i8_type().const_int(event_id as u64, false))
 }
+
+/// Print the return code of API calls to the debug buffer.
+fn log_return_code<'b>(binary: &Binary<'b>, api: &'static str, code: IntValue) {
+    if !binary.log_api_return_codes {
+        return;
+    }
+
+    emit_context!(binary);
+
+    let fmt = format!("{}=", api);
+    let msg = fmt.as_bytes();
+    let length = i32_const!(msg.len() as u64 + 16);
+    let out_buf =
+        binary
+            .builder
+            .build_array_alloca(binary.context.i8_type(), length, "seal_ret_code_buf");
+    let mut out_buf_offset = out_buf;
+
+    let msg_string = binary.emit_global_string(&fmt, msg, true);
+    let msg_len = binary.context.i32_type().const_int(msg.len() as u64, false);
+    call!(
+        "__memcpy",
+        &[out_buf_offset.into(), msg_string.into(), msg_len.into()]
+    );
+    out_buf_offset = unsafe { binary.builder.build_gep(out_buf_offset, &[msg_len], "") };
+
+    let code = binary
+        .builder
+        .build_int_z_extend(code, binary.context.i64_type(), "val_64bits");
+    out_buf_offset = call!("uint2dec", &[out_buf_offset.into(), code.into()])
+        .try_as_basic_value()
+        .left()
+        .unwrap()
+        .into_pointer_value();
+
+    let msg_len = binary.builder.build_int_sub(
+        binary
+            .builder
+            .build_ptr_to_int(out_buf_offset, binary.context.i32_type(), "out_buf_idx"),
+        binary
+            .builder
+            .build_ptr_to_int(out_buf, binary.context.i32_type(), "out_buf_ptr"),
+        "msg_len",
+    );
+    call!("seal_debug_message", &[out_buf.into(), msg_len.into()]);
+}

+ 9 - 3
src/emit/substrate/storage.rs

@@ -2,7 +2,7 @@
 
 use crate::emit::binary::Binary;
 use crate::emit::storage::StorageSlot;
-use crate::emit::substrate::SubstrateTarget;
+use crate::emit::substrate::{log_return_code, SubstrateTarget};
 use crate::emit::TargetRuntime;
 use crate::emit_context;
 use crate::sema::ast::{ArrayLength, Namespace, Type};
@@ -32,12 +32,14 @@ impl StorageSlot for SubstrateTarget {
                 .const_cast(binary.context.i32_type(), false)
         };
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot).into(),
             i32_const!(32).into(),
             cast_byte_ptr!(dest).into(),
             dest_size.into()
         );
+
+        log_return_code(binary, "seal_set_storage", ret);
     }
 
     fn get_storage_address<'a>(
@@ -61,6 +63,8 @@ impl StorageSlot for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -92,7 +96,7 @@ impl StorageSlot for SubstrateTarget {
     fn storage_delete_single_slot<'a>(&self, binary: &Binary<'a>, slot: PointerValue) {
         emit_context!(binary);
 
-        call!(
+        let ret = call!(
             "seal_clear_storage",
             &[cast_byte_ptr!(slot).into(), i32_const!(32).into()]
         )
@@ -100,6 +104,8 @@ impl StorageSlot for SubstrateTarget {
         .left()
         .unwrap()
         .into_int_value();
+
+        log_return_code(binary, "seal_clear_storage", ret);
     }
 
     fn storage_load_slot<'a>(

+ 57 - 12
src/emit/substrate/target.rs

@@ -4,7 +4,7 @@ use crate::codegen::cfg::{HashTy, ReturnCode};
 use crate::emit::binary::Binary;
 use crate::emit::expression::expression;
 use crate::emit::storage::StorageSlot;
-use crate::emit::substrate::{event_id, SubstrateTarget, SCRATCH_SIZE};
+use crate::emit::substrate::{event_id, log_return_code, SubstrateTarget, SCRATCH_SIZE};
 use crate::emit::{TargetRuntime, Variable};
 use crate::sema::ast;
 use crate::sema::ast::{Function, Namespace, Type};
@@ -28,7 +28,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
     ) {
         emit_context!(binary);
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot).into(),
             i32_const!(32).into(),
             cast_byte_ptr!(dest).into(),
@@ -39,6 +39,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
                 .const_cast(binary.context.i32_type(), false)
                 .into()
         );
+
+        log_return_code(binary, "seal_set_storage", ret);
     }
 
     fn get_storage_extfunc(
@@ -75,7 +77,7 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         let scratch_len = binary.scratch_len.unwrap().as_pointer_value();
         binary.builder.build_store(scratch_len, len);
 
-        let _exists = call!(
+        let ret = call!(
             "seal_get_storage",
             &[
                 cast_byte_ptr!(slot).into(),
@@ -86,7 +88,10 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         )
         .try_as_basic_value()
         .left()
-        .unwrap();
+        .unwrap()
+        .into_int_value();
+
+        log_return_code(binary, "seal_get_storage: ", ret);
 
         // TODO: decide behaviour if not exist
 
@@ -123,21 +128,29 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
 
         binary.builder.position_at_end(set_block);
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot).into(),
             i32_const!(32).into(),
             cast_byte_ptr!(data).into(),
             len.into()
         );
 
+        log_return_code(binary, "seal_set_storage", ret);
+
         binary.builder.build_unconditional_branch(done_storage);
 
         binary.builder.position_at_end(delete_block);
 
-        call!(
+        let ret = call!(
             "seal_clear_storage",
             &[cast_byte_ptr!(slot).into(), i32_const!(32).into()]
-        );
+        )
+        .try_as_basic_value()
+        .left()
+        .unwrap()
+        .into_int_value();
+
+        log_return_code(binary, "seal_clear_storage", ret);
 
         binary.builder.build_unconditional_branch(done_storage);
 
@@ -165,6 +178,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage: ", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -223,6 +238,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage: ", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -309,6 +326,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -381,6 +400,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -427,12 +448,14 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         // set the result
         binary.builder.build_store(offset, val);
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot_ptr).into(),
             i32_const!(32).into(),
             scratch_buf.into(),
             length.into()
         );
+
+        log_return_code(binary, "seal_set_storage", ret);
     }
 
     /// Push a byte onto a bytes string in storage
@@ -467,6 +490,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -500,13 +525,15 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             .builder
             .build_int_add(length, i32_const!(1), "new_length");
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot_ptr).into(),
             i32_const!(32).into(),
             scratch_buf.into(),
             length.into()
         );
 
+        log_return_code(binary, "seal_set_storage", ret);
+
         val
     }
 
@@ -538,6 +565,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -594,13 +623,15 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             None
         };
 
-        seal_set_storage!(
+        let ret = seal_set_storage!(
             cast_byte_ptr!(slot_ptr).into(),
             i32_const!(32).into(),
             scratch_buf.into(),
             new_length.into()
         );
 
+        log_return_code(binary, "seal_set_storage", ret);
+
         val
     }
 
@@ -631,6 +662,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
             scratch_len.into()
         );
 
+        log_return_code(binary, "seal_get_storage", exists);
+
         let exists = binary.builder.build_int_compare(
             IntPredicate::EQ,
             exists,
@@ -1000,10 +1033,16 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
     fn print(&self, binary: &Binary, string_ptr: PointerValue, string_len: IntValue) {
         emit_context!(binary);
 
-        call!(
+        let ret = call!(
             "seal_debug_message",
             &[string_ptr.into(), string_len.into()]
-        );
+        )
+        .try_as_basic_value()
+        .left()
+        .unwrap()
+        .into_int_value();
+
+        log_return_code(binary, "seal_debug_message", ret);
     }
 
     fn create_contract<'b>(
@@ -1125,6 +1164,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         .unwrap()
         .into_int_value();
 
+        log_return_code(binary, "seal_instantiate", ret);
+
         let is_success =
             binary
                 .builder
@@ -1206,6 +1247,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         .unwrap()
         .into_int_value();
 
+        log_return_code(binary, "seal_call", ret);
+
         let is_success =
             binary
                 .builder
@@ -1271,6 +1314,8 @@ impl<'a> TargetRuntime<'a> for SubstrateTarget {
         .unwrap()
         .into_int_value();
 
+        log_return_code(binary, "seal_transfer", ret);
+
         let is_success =
             binary
                 .builder

+ 4 - 0
src/lib.rs

@@ -118,6 +118,7 @@ pub fn compile(
     opt_level: inkwell::OptimizationLevel,
     target: Target,
     math_overflow_check: bool,
+    log_api_return_codes: bool,
 ) -> (Vec<(Vec<u8>, String)>, sema::ast::Namespace) {
     let mut ns = parse_and_resolve(filename, resolver, target);
 
@@ -130,6 +131,7 @@ pub fn compile(
         &mut ns,
         &codegen::Options {
             math_overflow_check,
+            log_api_return_codes,
             opt_level: opt_level.into(),
             ..Default::default()
         },
@@ -160,6 +162,7 @@ pub fn compile_many<'a>(
     opt: inkwell::OptimizationLevel,
     math_overflow_check: bool,
     generate_debug_info: bool,
+    log_api_return_codes: bool,
 ) -> emit::binary::Binary<'a> {
     emit::binary::Binary::build_bundle(
         context,
@@ -168,6 +171,7 @@ pub fn compile_many<'a>(
         opt,
         math_overflow_check,
         generate_debug_info,
+        log_api_return_codes,
     )
 }
 

+ 2 - 0
src/sema/contracts.rs

@@ -62,6 +62,7 @@ impl ast::Contract {
         opt: inkwell::OptimizationLevel,
         math_overflow_check: bool,
         generate_debug_info: bool,
+        log_api_return_codes: bool,
     ) -> emit::binary::Binary {
         emit::binary::Binary::build(
             context,
@@ -71,6 +72,7 @@ impl ast::Contract {
             opt,
             math_overflow_check,
             generate_debug_info,
+            log_api_return_codes,
         )
     }
 

+ 2 - 0
tests/contract.rs

@@ -88,6 +88,7 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
                     Default::default(),
                     false,
                     false,
+                    false,
                 );
             }
             Target::Substrate { .. } => {
@@ -101,6 +102,7 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
                             Default::default(),
                             false,
                             false,
+                            false,
                         );
                     }
                 }

+ 1 - 0
tests/solana.rs

@@ -141,6 +141,7 @@ fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> Vi
         inkwell::OptimizationLevel::Default,
         math_overflow_flag,
         false,
+        false,
     );
 
     let code = binary

+ 7 - 2
tests/substrate.rs

@@ -1186,9 +1186,13 @@ impl MockSubstrate {
 }
 
 pub fn build_solidity(src: &str) -> MockSubstrate {
-    build_solidity_with_overflow_check(src, false)
+    build_solidity_with_options(src, false, false)
 }
-pub fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> MockSubstrate {
+pub fn build_solidity_with_options(
+    src: &str,
+    math_overflow_flag: bool,
+    log_api_return_codes: bool,
+) -> MockSubstrate {
     let mut cache = FileResolver::new();
 
     cache.set_file_contents("test.sol", src.to_string());
@@ -1199,6 +1203,7 @@ pub fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -
         inkwell::OptimizationLevel::Default,
         Target::default_substrate(),
         math_overflow_flag,
+        log_api_return_codes,
     );
 
     ns.print_diagnostics_in_plain(&cache, false);

+ 32 - 1
tests/substrate_tests/calls.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::build_solidity;
+use crate::{build_solidity, build_solidity_with_options};
 use parity_scale_codec::{Decode, Encode};
 
 #[derive(Debug, PartialEq, Eq, Encode, Decode)]
@@ -762,3 +762,34 @@ fn try_catch_reachable() {
         }"##,
     );
 }
+
+#[test]
+fn log_api_call_return_values_works() {
+    let mut runtime = build_solidity_with_options(
+        r##"
+        contract Test {
+            constructor () payable {}
+        
+            function test() public {
+                try new Other() returns (Other o) {
+                    o.foo();
+                } 
+                catch {}
+            }
+        }
+        contract Other {
+            function foo() public pure {
+                print("hi!");
+            }
+        }
+        "##,
+        false,
+        true,
+    );
+
+    runtime.function("test", vec![]);
+    assert_eq!(
+        &runtime.printbuf,
+        "seal_instantiate=0hi!seal_debug_message=0seal_call=0"
+    );
+}

+ 17 - 11
tests/substrate_tests/expressions.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::{build_solidity, build_solidity_with_overflow_check};
+use crate::{build_solidity, build_solidity_with_options};
 use num_bigint::{BigInt, BigUint, RandBigInt, Sign};
 use parity_scale_codec::{Decode, Encode};
 use rand::seq::SliceRandom;
@@ -956,7 +956,7 @@ fn test_power_overflow_boundaries() {
         }"#
         .replace("intN", &format!("int{}", width));
 
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         let base = BigUint::from(2_u32);
         let mut base_data = base.to_bytes_le();
@@ -1156,7 +1156,7 @@ fn test_overflow_boundaries() {
             }
         }"#
         .replace("intN", &format!("int{}", width));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1]. We generate these boundaries:
         let upper_boundary = BigInt::from(2_u32).pow(width - 1).sub(1_u32);
@@ -1287,7 +1287,7 @@ fn test_overflow_detect_signed() {
             }
         }"#
         .replace("intN", &format!("int{}", width));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1] .Generate a value that will overflow this range:
         let limit = BigInt::from(2_u32).pow(width - 1).sub(1_u32);
@@ -1349,7 +1349,7 @@ fn test_overflow_detect_unsigned() {
             }
         }"#
         .replace("intN", &format!("int{}", width));
-        let mut contract = build_solidity_with_overflow_check(&src, true);
+        let mut contract = build_solidity_with_options(&src, true, false);
 
         // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1].
         let limit = BigUint::from(2_u32).pow(width).sub(1_u32);
@@ -1694,7 +1694,7 @@ fn destructure() {
 #[test]
 #[should_panic]
 fn addition_overflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract overflow {
             function foo(uint8 x) internal returns (uint8) {
@@ -1708,6 +1708,7 @@ fn addition_overflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1715,7 +1716,7 @@ fn addition_overflow() {
 
 #[test]
 fn unchecked_addition_overflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract overflow {
             function foo(uint8 x) internal returns (uint8) {
@@ -1731,6 +1732,7 @@ fn unchecked_addition_overflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1739,7 +1741,7 @@ fn unchecked_addition_overflow() {
 #[test]
 #[should_panic]
 fn subtraction_underflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract underflow {
             function foo(uint64 x) internal returns (uint64) {
@@ -1753,6 +1755,7 @@ fn subtraction_underflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1760,7 +1763,7 @@ fn subtraction_underflow() {
 
 #[test]
 fn unchecked_subtraction_underflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract underflow {
             function foo(uint64 x) internal returns (uint64) {
@@ -1776,6 +1779,7 @@ fn unchecked_subtraction_underflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1784,7 +1788,7 @@ fn unchecked_subtraction_underflow() {
 #[test]
 #[should_panic]
 fn multiplication_overflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract overflow {
             function foo(int8 x) internal returns (int8) {
@@ -1798,6 +1802,7 @@ fn multiplication_overflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());
@@ -1805,7 +1810,7 @@ fn multiplication_overflow() {
 
 #[test]
 fn unchecked_multiplication_overflow() {
-    let mut runtime = build_solidity_with_overflow_check(
+    let mut runtime = build_solidity_with_options(
         r#"
         contract overflow {
             function foo(int8 x) internal returns (int8) {
@@ -1821,6 +1826,7 @@ fn unchecked_multiplication_overflow() {
         }
         "#,
         true,
+        false,
     );
 
     runtime.function("bar", Vec::new());

+ 2 - 0
tests/substrate_tests/inheritance.rs

@@ -36,6 +36,7 @@ fn test_abstract() {
         inkwell::OptimizationLevel::Default,
         Target::default_substrate(),
         false,
+        false,
     );
 
     assert!(!ns.diagnostics.any_errors());
@@ -74,6 +75,7 @@ fn test_abstract() {
         inkwell::OptimizationLevel::Default,
         Target::default_substrate(),
         false,
+        false,
     );
 
     assert!(!ns.diagnostics.any_errors());

+ 1 - 0
tests/undefined_variable_detection.rs

@@ -20,6 +20,7 @@ fn parse_and_codegen(src: &'static str) -> Namespace {
         opt_level: OptimizationLevel::Default,
         math_overflow_check: false,
         generate_debug_information: false,
+        log_api_return_codes: false,
     };
 
     codegen(&mut ns, &opt);