소스 검색

Multiplication overflow detection for integer width > 64 bits (#988)

* mul overflow > 64 bits

Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
salaheldinsoliman 3 년 전
부모
커밋
a78b61adc2
11개의 변경된 파일1268개의 추가작업 그리고 118개의 파일을 삭제
  1. 4 2
      Cargo.toml
  2. 447 54
      src/emit/mod.rs
  3. 64 10
      stdlib/bigint.c
  4. BIN
      stdlib/bpf/bigint.bc
  5. BIN
      stdlib/wasm/bigint.bc
  6. 5 1
      tests/solana.rs
  7. 364 1
      tests/solana_tests/primitives.rs
  8. 1 1
      tests/solana_tests/signature_verify.rs
  9. 2 0
      tests/solana_tests/simple.rs
  10. 4 47
      tests/substrate.rs
  11. 377 2
      tests/substrate_tests/expressions.rs

+ 4 - 2
Cargo.toml

@@ -16,7 +16,8 @@ cc = "1.0"
 
 [dependencies]
 regex = "1"
-num-bigint = "0.4"
+rand = "0.8"
+num-bigint = { version = "0.4", features = ["rand"]}
 num-traits = "0.2"
 num-integer = "0.1.44"
 parity-wasm = "0.45"
@@ -56,7 +57,8 @@ num-derive = "0.3"
 parity-scale-codec = "3.1"
 ethabi = "17.0"
 wasmi = "0.11"
-rand = "0.7"
+# rand version 0.7 is needed for ed25519_dalek::keypair::generate, used in solana_tests/signature_verify.rs
+rand_07 = { package = "rand", version = "0.7" }
 sha2 = "0.10"
 # solana_rbpf makes api changes in patch versions
 solana_rbpf = "=0.2.32"

+ 447 - 54
src/emit/mod.rs

@@ -1827,14 +1827,52 @@ pub trait TargetRuntime<'a> {
                 let right = self.expression(bin, r, vartab, function, ns);
 
                 let bits = left.into_int_value().get_type().get_bit_width();
+                let o = bin.build_alloca(function, left.get_type(), "");
+                let f = self.power(bin, *unchecked, bits, res_ty.is_signed_int(), o);
 
-                let f = self.power(bin, *unchecked, bits, res_ty.is_signed_int());
-
-                bin.builder
-                    .build_call(f, &[left.into(), right.into()], "power")
+                // If the function returns zero, then the operation was successful.
+                let error_return = bin
+                    .builder
+                    .build_call(f, &[left.into(), right.into(), o.into()], "power")
                     .try_as_basic_value()
                     .left()
-                    .unwrap()
+                    .unwrap();
+
+                // Load the result pointer
+                let res = bin.builder.build_load(o, "");
+
+                if !bin.math_overflow_check || *unchecked || ns.target != Target::Solana {
+                    // In Substrate, overflow case will hit an unreachable expression, so no additional checks are needed.
+                    res
+                } else {
+                    // In Solana, a return other than zero will abort execution. We need to check if power() returned a zero or not.
+                    let error_block = bin.context.append_basic_block(function, "error");
+                    let return_block = bin.context.append_basic_block(function, "return_block");
+
+                    let error_ret = bin.builder.build_int_compare(
+                        IntPredicate::NE,
+                        error_return.into_int_value(),
+                        error_return.get_type().const_zero().into_int_value(),
+                        "",
+                    );
+
+                    bin.builder
+                        .build_conditional_branch(error_ret, error_block, return_block);
+                    bin.builder.position_at_end(error_block);
+
+                    self.assert_failure(
+                        bin,
+                        bin.context
+                            .i8_type()
+                            .ptr_type(AddressSpace::Generic)
+                            .const_null(),
+                        bin.context.i32_type().const_zero(),
+                    );
+
+                    bin.builder.position_at_end(return_block);
+
+                    res
+                }
             }
             Expression::Equal(_, l, r) => {
                 if l.ty().is_address() {
@@ -5549,7 +5587,277 @@ pub trait TargetRuntime<'a> {
         vector.into()
     }
 
-    // emit a multiply for any width with or without overflow checking
+    // Signed overflow detection is handled by the following steps:
+    // 1- Do an unsigned multiplication first, This step will check if the generated value will fit in N bits. (unsigned overflow)
+    // 2- Get the result, and negate it if needed.
+    // 3- Check for signed overflow, by checking for an unexpected change in the sign of the result.
+    fn signed_ovf_detect(
+        &self,
+        bin: &Binary<'a>,
+        mul_ty: IntType<'a>,
+        mul_bits: u32,
+        left: IntValue<'a>,
+        right: IntValue<'a>,
+        bits: u32,
+        function: FunctionValue<'a>,
+    ) -> IntValue<'a> {
+        // We check for signed overflow based on the facts:
+        //  - * - = +
+        //  + * + = +
+        //  - * + = - (if op1 and op2 != 0)
+        // if one of the operands is zero, discard the last rule.
+        let left_negative = bin.builder.build_int_compare(
+            IntPredicate::SLT,
+            left,
+            left.get_type().const_zero(),
+            "left_negative",
+        );
+
+        let left_abs = bin
+            .builder
+            .build_select(
+                left_negative,
+                bin.builder.build_int_neg(left, "signed_left"),
+                left,
+                "left_abs",
+            )
+            .into_int_value();
+
+        let right_negative = bin.builder.build_int_compare(
+            IntPredicate::SLT,
+            right,
+            right.get_type().const_zero(),
+            "right_negative",
+        );
+
+        let right_abs = bin
+            .builder
+            .build_select(
+                right_negative,
+                bin.builder.build_int_neg(right, "signed_right"),
+                right,
+                "right_abs",
+            )
+            .into_int_value();
+
+        let l = bin.build_alloca(function, mul_ty, "");
+        let r = bin.build_alloca(function, mul_ty, "");
+        let o = bin.build_alloca(function, mul_ty, "");
+
+        bin.builder
+            .build_store(l, bin.builder.build_int_z_extend(left_abs, mul_ty, ""));
+        bin.builder
+            .build_store(r, bin.builder.build_int_z_extend(right_abs, mul_ty, ""));
+
+        let return_val = bin.builder.build_call(
+            bin.module.get_function("__mul32_with_builtin_ovf").unwrap(),
+            &[
+                bin.builder
+                    .build_pointer_cast(
+                        l,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "left",
+                    )
+                    .into(),
+                bin.builder
+                    .build_pointer_cast(
+                        r,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "right",
+                    )
+                    .into(),
+                bin.builder
+                    .build_pointer_cast(
+                        o,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "output",
+                    )
+                    .into(),
+                bin.context
+                    .i32_type()
+                    .const_int(mul_bits as u64 / 32, false)
+                    .into(),
+            ],
+            "",
+        );
+
+        let res = bin.builder.build_load(o, "mul");
+        let ovf_any_type = if mul_bits != bits {
+            // If there are any set bits, then there is an overflow.
+            let check_ovf = bin.builder.build_right_shift(
+                res.into_int_value(),
+                mul_ty.const_int((bits).into(), false),
+                false,
+                "",
+            );
+            bin.builder.build_int_compare(
+                IntPredicate::NE,
+                check_ovf,
+                check_ovf.get_type().const_zero(),
+                "",
+            )
+        } else {
+            // If no size extension took place, there is no overflow in most significant N bits
+            bin.context.bool_type().const_zero()
+        };
+
+        let negate_result = bin
+            .builder
+            .build_xor(left_negative, right_negative, "negate_result");
+
+        let res = bin.builder.build_select(
+            negate_result,
+            bin.builder
+                .build_int_neg(res.into_int_value(), "unsigned_res"),
+            res.into_int_value(),
+            "res",
+        );
+
+        let error_block = bin.context.append_basic_block(function, "error");
+        let return_block = bin.context.append_basic_block(function, "return_block");
+
+        // Extract sign bit of the operands and the result
+        let left_sign_bit = self.extract_sign_bit(bin, left, left.get_type());
+        let right_sign_bit = self.extract_sign_bit(bin, right, right.get_type());
+        let res_sign_bit = if mul_bits == bits {
+            // If no extension took place, get the leftmost bit(sign bit).
+            self.extract_sign_bit(bin, res.into_int_value(), res.into_int_value().get_type())
+        } else {
+            // If extension took place, truncate the result to the type of the operands then extract the leftmost bit(sign bit).
+            self.extract_sign_bit(
+                bin,
+                bin.builder
+                    .build_int_truncate(res.into_int_value(), left.get_type(), ""),
+                left.get_type(),
+            )
+        };
+
+        let value_fits_n_bits = bin.builder.build_not(
+            bin.builder.build_or(
+                return_val
+                    .try_as_basic_value()
+                    .left()
+                    .unwrap()
+                    .into_int_value(),
+                ovf_any_type,
+                "",
+            ),
+            "",
+        );
+
+        let left_is_zero =
+            bin.builder
+                .build_int_compare(IntPredicate::EQ, left, left.get_type().const_zero(), "");
+        let right_is_zero = bin.builder.build_int_compare(
+            IntPredicate::EQ,
+            right,
+            right.get_type().const_zero(),
+            "",
+        );
+
+        // If one of the operands is zero
+        let mul_by_zero = bin.builder.build_or(left_is_zero, right_is_zero, "");
+
+        // Will resolve to one if signs are differnet
+        let different_signs = bin.builder.build_xor(left_sign_bit, right_sign_bit, "");
+
+        let not_ok_operation = bin
+            .builder
+            .build_not(bin.builder.build_xor(different_signs, res_sign_bit, ""), "");
+
+        // Here, we disregard the last rule mentioned above if there is a multiplication by zero.
+        bin.builder.build_conditional_branch(
+            bin.builder.build_and(
+                bin.builder.build_or(not_ok_operation, mul_by_zero, ""),
+                value_fits_n_bits,
+                "",
+            ),
+            return_block,
+            error_block,
+        );
+
+        bin.builder.position_at_end(error_block);
+
+        self.assert_failure(
+            bin,
+            bin.context
+                .i8_type()
+                .ptr_type(AddressSpace::Generic)
+                .const_null(),
+            bin.context.i32_type().const_zero(),
+        );
+
+        bin.builder.position_at_end(return_block);
+
+        bin.builder
+            .build_int_truncate(res.into_int_value(), left.get_type(), "")
+    }
+
+    // Call void __mul32 and return the result.
+    fn call_mul32_without_ovf(
+        &self,
+        bin: &Binary<'a>,
+        l: PointerValue<'a>,
+        r: PointerValue<'a>,
+        o: PointerValue<'a>,
+        mul_bits: u32,
+        mul_type: IntType<'a>,
+    ) -> IntValue<'a> {
+        bin.builder.build_call(
+            bin.module.get_function("__mul32").unwrap(),
+            &[
+                bin.builder
+                    .build_pointer_cast(
+                        l,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "left",
+                    )
+                    .into(),
+                bin.builder
+                    .build_pointer_cast(
+                        r,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "right",
+                    )
+                    .into(),
+                bin.builder
+                    .build_pointer_cast(
+                        o,
+                        bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                        "output",
+                    )
+                    .into(),
+                bin.context
+                    .i32_type()
+                    .const_int(mul_bits as u64 / 32, false)
+                    .into(),
+            ],
+            "",
+        );
+
+        let res = bin.builder.build_load(o, "mul");
+
+        bin.builder
+            .build_int_truncate(res.into_int_value(), mul_type, "")
+    }
+
+    // Utility function to extract the sign bit of an IntValue
+    fn extract_sign_bit(
+        &self,
+        bin: &Binary<'a>,
+        operand: IntValue<'a>,
+        int_type: IntType<'a>,
+    ) -> IntValue<'a> {
+        let n_bits_to_shift = int_type.get_bit_width() - 1;
+        let val_to_shift = int_type.const_int(n_bits_to_shift as u64, false);
+        let shifted = bin
+            .builder
+            .build_right_shift(operand, val_to_shift, false, "");
+        bin.builder
+            .build_int_truncate(shifted, bin.context.bool_type(), "")
+    }
+
+    // Emit a multiply for any width with or without overflow checking
     fn mul(
         &self,
         bin: &Binary<'a>,
@@ -5561,12 +5869,13 @@ pub trait TargetRuntime<'a> {
     ) -> IntValue<'a> {
         let bits = left.get_type().get_bit_width();
 
-        if bits > 64 {
-            // round up the number of bits to the next 32
+        // Mul with overflow is not supported beyond this bit range, so we implement our own function
+        if bits > 32 {
+            // Round up the number of bits to the next 32
             let mul_bits = (bits + 31) & !31;
             let mul_ty = bin.context.custom_width_int_type(mul_bits);
 
-            // round up bits
+            // Round up bits
             let l = bin.build_alloca(function, mul_ty, "");
             let r = bin.build_alloca(function, mul_ty, "");
             let o = bin.build_alloca(function, mul_ty, "");
@@ -5574,57 +5883,127 @@ pub trait TargetRuntime<'a> {
             if mul_bits == bits {
                 bin.builder.build_store(l, left);
                 bin.builder.build_store(r, right);
-            } else if signed {
-                bin.builder
-                    .build_store(l, bin.builder.build_int_s_extend(left, mul_ty, ""));
-                bin.builder
-                    .build_store(r, bin.builder.build_int_s_extend(right, mul_ty, ""));
-            } else {
+            }
+            // LLVM-IR can handle multiplication of sizes up to 64 bits. If the size is larger, we need to implement our own mutliplication function.
+            // We divide the operands into sizes of 32 bits (check __mul32 in stdlib/bigint.c documentation).
+            // If the size is not divisble by 32, we extend it to the next 32 bits. For example, int72 will be extended to int96.
+            // Here, we zext the operands to the nearest 32 bits. zext is called instead of sext because we need to do unsigned multiplication by default.
+            // It will not matter in terms of mul without overflow, because we always truncate the result to the bit size of the operands.
+            // In mul with overflow however, it is needed so that overflow can be detected if the most significant bits of the result are not zeros.
+            else {
                 bin.builder
                     .build_store(l, bin.builder.build_int_z_extend(left, mul_ty, ""));
                 bin.builder
                     .build_store(r, bin.builder.build_int_z_extend(right, mul_ty, ""));
             }
 
-            bin.builder.build_call(
-                bin.module.get_function("__mul32").unwrap(),
-                &[
-                    bin.builder
-                        .build_pointer_cast(
-                            l,
-                            bin.context.i32_type().ptr_type(AddressSpace::Generic),
-                            "left",
-                        )
-                        .into(),
-                    bin.builder
-                        .build_pointer_cast(
-                            r,
-                            bin.context.i32_type().ptr_type(AddressSpace::Generic),
-                            "right",
-                        )
-                        .into(),
-                    bin.builder
-                        .build_pointer_cast(
-                            o,
-                            bin.context.i32_type().ptr_type(AddressSpace::Generic),
-                            "output",
-                        )
-                        .into(),
+            if bin.math_overflow_check && !unchecked {
+                if signed {
+                    return self
+                        .signed_ovf_detect(bin, mul_ty, mul_bits, left, right, bits, function);
+                }
+
+                // Unsigned overflow detection Approach:
+                // If the size is a multiple of 32, we call __mul32_with_builtin_ovf and it returns an overflow flag (check __mul32_with_builtin_ovf in stdlib/bigint.c documentation)
+                // If that is not the case, some extra work has to be done. We have to check the extended bits for any set bits. If there is any, an overflow occured.
+                // For example, if we have uint72, it will be extended to uint96. __mul32 with ovf will raise an ovf flag if the result overflows 96 bits, not 72.
+                // We account for that by checking the extended leftmost bits. In the example mentioned, they will be 96-72=24 bits.
+                let return_val = bin.builder.build_call(
+                    bin.module.get_function("__mul32_with_builtin_ovf").unwrap(),
+                    &[
+                        bin.builder
+                            .build_pointer_cast(
+                                l,
+                                bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                                "left",
+                            )
+                            .into(),
+                        bin.builder
+                            .build_pointer_cast(
+                                r,
+                                bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                                "right",
+                            )
+                            .into(),
+                        bin.builder
+                            .build_pointer_cast(
+                                o,
+                                bin.context.i32_type().ptr_type(AddressSpace::Generic),
+                                "output",
+                            )
+                            .into(),
+                        bin.context
+                            .i32_type()
+                            .const_int(mul_bits as u64 / 32, false)
+                            .into(),
+                    ],
+                    "ovf",
+                );
+
+                let res = bin.builder.build_load(o, "mul");
+
+                let error_block = bin.context.append_basic_block(function, "error");
+                let return_block = bin.context.append_basic_block(function, "return_block");
+
+                // If the operands were extended to nearest 32 bit size, check the most significant N bits, where N equals bit width after extension minus original bit width.
+                let ovf_any_type = if mul_bits != bits {
+                    // If there are any set bits, then there is an overflow.
+                    let check_ovf = bin.builder.build_right_shift(
+                        res.into_int_value(),
+                        mul_ty.const_int((bits).into(), false),
+                        false,
+                        "",
+                    );
+                    bin.builder.build_int_compare(
+                        IntPredicate::NE,
+                        check_ovf,
+                        check_ovf.get_type().const_zero(),
+                        "",
+                    )
+                } else {
+                    // If no size extension took place, there is no overflow in most significant N bits
+                    bin.context.bool_type().const_zero()
+                };
+
+                // Until this point, we only checked the extended bits for ovf. But mul ovf can take place any where from bit size to double bit size.
+                // For example: If we have uint72, it will be extended to uint96. We only checked the most significant 24 bits for overflow, which can happen up to 72*2=144 bits.
+                // bool __mul32_with_builtin_ovf takes care of overflowing bits beyond 96.
+                // What is left now is to or these two ovf flags, and check if any one of them is set. If so, an overflow occured.
+                let lowbit = bin.builder.build_int_truncate(
+                    bin.builder.build_or(
+                        ovf_any_type,
+                        return_val
+                            .try_as_basic_value()
+                            .left()
+                            .unwrap()
+                            .into_int_value(),
+                        "",
+                    ),
+                    bin.context.bool_type(),
+                    "bit",
+                );
+
+                // If ovf, raise an error, else return the result.
+                bin.builder
+                    .build_conditional_branch(lowbit, error_block, return_block);
+
+                bin.builder.position_at_end(error_block);
+
+                self.assert_failure(
+                    bin,
                     bin.context
-                        .i32_type()
-                        .const_int(mul_bits as u64 / 32, false)
-                        .into(),
-                ],
-                "",
-            );
+                        .i8_type()
+                        .ptr_type(AddressSpace::Generic)
+                        .const_null(),
+                    bin.context.i32_type().const_zero(),
+                );
 
-            let res = bin.builder.build_load(o, "mul");
+                bin.builder.position_at_end(return_block);
 
-            if mul_bits == bits {
-                res.into_int_value()
-            } else {
                 bin.builder
                     .build_int_truncate(res.into_int_value(), left.get_type(), "")
+            } else {
+                return self.call_mul32_without_ovf(bin, l, r, o, mul_bits, left.get_type());
             }
         } else if bin.math_overflow_check && !unchecked {
             self.build_binary_op_with_overflow_check(
@@ -5646,6 +6025,7 @@ pub trait TargetRuntime<'a> {
         unchecked: bool,
         bits: u32,
         signed: bool,
+        o: PointerValue<'a>,
     ) -> FunctionValue<'a> {
         /*
             int ipow(int base, int exp)
@@ -5660,7 +6040,6 @@ pub trait TargetRuntime<'a> {
                         break;
                     base *= base;
                 }
-
                 return result;
             }
         */
@@ -5679,9 +6058,13 @@ pub trait TargetRuntime<'a> {
         let pos = bin.builder.get_insert_block().unwrap();
 
         // __upower(base, exp)
-        let function =
-            bin.module
-                .add_function(&name, ty.fn_type(&[ty.into(), ty.into()], false), None);
+        let function = bin.module.add_function(
+            &name,
+            bin.context
+                .i64_type()
+                .fn_type(&[ty.into(), ty.into(), o.get_type().into()], false),
+            None,
+        );
 
         let entry = bin.context.append_basic_block(function, "entry");
         let loop_block = bin.context.append_basic_block(function, "loop");
@@ -5724,6 +6107,8 @@ pub trait TargetRuntime<'a> {
             signed,
         );
 
+        let multiply = bin.builder.get_insert_block().unwrap();
+
         bin.builder.build_unconditional_branch(nomultiply);
         bin.builder.position_at_end(nomultiply);
 
@@ -5743,7 +6128,13 @@ pub trait TargetRuntime<'a> {
         bin.builder.build_conditional_branch(zero, done, notdone);
         bin.builder.position_at_end(done);
 
-        bin.builder.build_return(Some(&result3.as_basic_value()));
+        // If successful operation, load the result in the output pointer then return zero.
+        bin.builder.build_store(
+            function.get_nth_param(2).unwrap().into_pointer_value(),
+            result3.as_basic_value(),
+        );
+        bin.builder
+            .build_return(Some(&bin.context.i64_type().const_zero()));
 
         bin.builder.position_at_end(notdone);
 
@@ -5756,6 +6147,8 @@ pub trait TargetRuntime<'a> {
             signed,
         );
 
+        let notdone = bin.builder.get_insert_block().unwrap();
+
         base.add_incoming(&[(&base2, notdone)]);
         result.add_incoming(&[(&result3.as_basic_value(), notdone)]);
         exp.add_incoming(&[(&exp2, notdone)]);

+ 64 - 10
stdlib/bigint.c

@@ -3,20 +3,20 @@
 #include <stdbool.h>
 
 /*
-	In wasm/bpf, the instruction for multiplying two 64 bit values results in a 64 bit value. In
+    In wasm/bpf, the instruction for multiplying two 64 bit values results in a 64 bit value. In
     other words, the result is truncated. The largest values we can multiply without truncation
-	is 32 bit (by casting to 64 bit and doing a 64 bit multiplication). So, we divvy the work
-	up into a 32 bit multiplications.
+    is 32 bit (by casting to 64 bit and doing a 64 bit multiplication). So, we divvy the work
+    up into a 32 bit multiplications.
 
-	No overflow checking is done.
+    No overflow checking is done.
 
-	0		0		0		r5		r4		r3		r2		r1
-	0		0		0		0		l4		l3		l2		l1 *
+    0		0		0		r5		r4		r3		r2		r1
+    0		0		0		0		l4		l3		l2		l1 *
     ------------------------------------------------------------
-	0		0		0		r5*l1	r4*l1	r3*l1	r2*l1	r1*l1
-	0		0		r5*l2	r4*l2	r3*l2	r2*l2 	r1*l2	0
-	0		r5*l3	r4*l3	r3*l3	r2*l3 	r1*l3	0		0
-	r5*l4	r4*l4	r3*l4	r2*l4 	r1*l4	0		0 		0  +
+    0		0		0		r5*l1	r4*l1	r3*l1	r2*l1	r1*l1
+    0		0		r5*l2	r4*l2	r3*l2	r2*l2 	r1*l2	0
+    0		r5*l3	r4*l3	r3*l3	r2*l3 	r1*l3	0		0
+    r5*l4	r4*l4	r3*l4	r2*l4 	r1*l4	0		0 		0  +
     ------------------------------------------------------------
 */
 void __mul32(uint32_t left[], uint32_t right[], uint32_t out[], int len)
@@ -62,6 +62,60 @@ void __mul32(uint32_t left[], uint32_t right[], uint32_t out[], int len)
     }
 }
 
+// A version of __mul32 that detects overflow. 
+bool __mul32_with_builtin_ovf(uint32_t left[], uint32_t right[], uint32_t out[], int len)
+{
+    bool overflow = false;
+    uint64_t val1 = 0, carry = 0;
+    int left_len = len, right_len = len;
+    while (left_len > 0 && !left[left_len - 1])
+        left_len--;
+    while (right_len > 0 && !right[right_len - 1])
+        right_len--;
+    int right_start = 0, right_end = 0;
+    int left_start = 0;
+    // We extend len to check for possible overflow. len = bit_width / 32. Checking for overflow for intN (where N = number of bits) requires checking for any set bits beyond N up to N*2.
+    len = len * 2;
+    for (int l = 0; l < len; l++)
+    {
+        int i = 0;
+        if (l >= left_len)
+            right_start++;
+        if (l >= right_len)
+            left_start++;
+        if (right_end < right_len)
+            right_end++;
+
+        for (int r = right_end - 1; r >= right_start; r--)
+        {
+            uint64_t m = (uint64_t)left[left_start + i] * (uint64_t)right[r];
+            i++;
+            if (__builtin_add_overflow(val1, m, &val1))
+                carry += 0x100000000;
+        }
+
+        // If the loop is within the operand bit size, just do the assignment
+        if (l < len / 2)
+        {
+            out[l] = val1;
+        }
+
+        // If the loop extends to more than the bit size, we check for overflow.
+        else if (l >= len / 2)
+        {
+            if (val1 > 0)
+            {
+                overflow = true;
+                break;
+            }
+        }
+
+        val1 = (val1 >> 32) | carry;
+        carry = 0;
+    }
+    return overflow;
+}
+
 // Some compiler runtime builtins we need.
 
 // 128 bit shift left.

BIN
stdlib/bpf/bigint.bc


BIN
stdlib/wasm/bigint.bc


+ 5 - 1
tests/solana.rs

@@ -116,6 +116,10 @@ struct Assign {
 }
 
 fn build_solidity(src: &str) -> VirtualMachine {
+    build_solidity_with_overflow_check(src, false)
+}
+
+fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> VirtualMachine {
     let mut cache = FileResolver::new();
 
     cache.set_file_contents("test.sol", src.to_string());
@@ -135,7 +139,7 @@ fn build_solidity(src: &str) -> VirtualMachine {
         namespaces,
         "bundle.sol",
         inkwell::OptimizationLevel::Default,
-        false,
+        math_overflow_flag,
         false,
     );
 

+ 364 - 1
tests/solana_tests/primitives.rs

@@ -1,8 +1,10 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::build_solidity;
+use crate::build_solidity_with_overflow_check;
 use ethabi::ethereum_types::U256;
-use num_bigint::{BigInt, BigUint};
+use num_bigint::{BigInt, BigUint, RandBigInt, Sign};
+use rand::seq::SliceRandom;
 use rand::Rng;
 use std::ops::Add;
 use std::ops::BitAnd;
@@ -349,7 +351,9 @@ fn uint() {
             }
 
             function mul(uintN a, uintN b) public returns (uintN) {
+                unchecked {
                 return a * b;
+                }
             }
 
             function div(uintN a, uintN b) public returns (uintN) {
@@ -361,7 +365,9 @@ fn uint() {
             }
 
             function pow(uintN a, uintN b) public returns (uintN) {
+                unchecked {
                 return a ** b;
+                }
             }
 
             function or(uintN a, uintN b) public returns (uintN) {
@@ -599,6 +605,361 @@ fn truncate_uint(n: &mut U256, width: usize) {
     }
 }
 
+#[test]
+fn test_power_overflow_boundaries() {
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function pow(uintN a, uintN b) public returns (uintN) { 
+                return a ** b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+        contract.constructor("test", &[]);
+
+        let return_value = contract.function(
+            "pow",
+            &[
+                ethabi::Token::Uint(U256::from(2)),
+                ethabi::Token::Uint(U256::from(width - 1)),
+            ],
+            &[],
+            None,
+        );
+
+        let res = BigUint::from(2_u32).pow((width - 1) as u32);
+
+        assert_eq!(
+            return_value,
+            vec![ethabi::Token::Uint(U256::from_big_endian(
+                &res.to_bytes_be()
+            ))]
+        );
+
+        let sesa = contract.function_must_fail(
+            "pow",
+            &[
+                ethabi::Token::Uint(U256::from(2)),
+                ethabi::Token::Uint(U256::from(width + 1)),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(sesa, Ok(0));
+    }
+}
+
+#[test]
+fn test_overflow_boundaries() {
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        // 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);
+        let lower_boundary = BigInt::from(2_u32).pow(width - 1).mul(-1);
+        let second_op = BigInt::from(1_u32);
+
+        // Multiply the boundaries by 1.
+        contract.constructor("test", &[]);
+        let return_value = contract.function(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&upper_boundary, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&second_op, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+        assert_eq!(
+            return_value,
+            vec![ethabi::Token::Int(bigint_to_eth(
+                &upper_boundary,
+                width.try_into().unwrap(),
+            ))]
+        );
+
+        let return_value = contract.function(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&lower_boundary, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&second_op, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+        assert_eq!(
+            return_value,
+            vec![ethabi::Token::Int(bigint_to_eth(
+                &lower_boundary,
+                width.try_into().unwrap(),
+            ))]
+        );
+
+        let upper_boundary_plus_one = BigInt::from(2_u32).pow(width - 1);
+
+        // We subtract 2 instead of one to make the number even, so that no rounding occurs when we divide by 2 later on.
+        let lower_boundary_minus_two = BigInt::from(2_u32).pow(width - 1).mul(-1_i32).sub(2_i32);
+
+        let upper_second_op = upper_boundary_plus_one.div(2);
+
+        let lower_second_op = lower_boundary_minus_two.div(2);
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&upper_second_op, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&BigInt::from(2), width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&lower_second_op, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&BigInt::from(2), width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&upper_boundary, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&upper_boundary, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&lower_boundary, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&lower_boundary, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(&upper_boundary, width.try_into().unwrap())),
+                ethabi::Token::Int(bigint_to_eth(&lower_boundary, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+    }
+}
+
+#[test]
+fn test_mul_within_range_signed() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let mut contract = build_solidity(&src);
+
+        // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1]. Here we generate a random number within this range and multiply it by -1, 1 or 0.
+        let first_operand_rand = rng.gen_bigint(width - 1).sub(1_u32);
+        println!("First op : {:?}", first_operand_rand);
+
+        let side = vec![-1, 0, 1];
+        // -1, 1 or 0
+        let second_op = BigInt::from(*side.choose(&mut rng).unwrap() as i32);
+        println!("second op : {:?}", second_op);
+
+        contract.constructor("test", &[]);
+        let return_value = contract.function(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(
+                    &first_operand_rand,
+                    width.try_into().unwrap(),
+                )),
+                ethabi::Token::Int(bigint_to_eth(&second_op, width.try_into().unwrap())),
+            ],
+            &[],
+            None,
+        );
+
+        let res = first_operand_rand.mul(second_op);
+        assert_eq!(
+            return_value,
+            vec![ethabi::Token::Int(bigint_to_eth(
+                &res,
+                width.try_into().unwrap(),
+            ))]
+        );
+    }
+}
+
+#[test]
+fn test_mul_within_range() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(uintN a, uintN b) public returns (uintN) { 
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+        contract.constructor("test", &[]);
+        for _ in 0..10 {
+            // Max number to fit unsigned N bits is (2^N)-1
+            let limit = BigUint::from(2_u32).pow(width).sub(1_u32);
+
+            // Generate a random number within the the range [0, 2^N -1]
+            let first_operand_rand = rng.gen_biguint(width.into()).add(1_u32);
+
+            // Calculate a number that when multiplied by first_operand_rand, the result will not overflow N bits (the result of this division will cast the float result to int result, therefore lowering it. The result of multiplication will never overflow).
+            let second_operand_rand = limit.div(&first_operand_rand);
+
+            let return_value = contract.function(
+                "mul",
+                &[
+                    ethabi::Token::Uint(U256::from_big_endian(&first_operand_rand.to_bytes_be())),
+                    ethabi::Token::Uint(U256::from_big_endian(&second_operand_rand.to_bytes_be())),
+                ],
+                &[],
+                None,
+            );
+            let res = first_operand_rand * second_operand_rand;
+
+            assert_eq!(
+                return_value,
+                vec![ethabi::Token::Uint(U256::from_big_endian(
+                    &res.to_bytes_be()
+                ))]
+            );
+        }
+    }
+}
+
+#[test]
+fn test_overflow_detect_signed() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        contract.constructor("test", &[]);
+
+        // 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).add(1_u32);
+
+        // Divide Limit by a random number
+        let first_operand_rand = rng.gen_bigint((width - 1).into()).sub(1_u32);
+
+        // Calculate a number that when multiplied by first_operand_rand, the result will overflow N bits
+        let mut second_operand_rand = limit / &first_operand_rand;
+
+        if let Sign::Minus = second_operand_rand.sign() {
+            // Decrease by 1 if negative, this is to make sure the result will overflow
+            second_operand_rand = second_operand_rand.sub(1);
+        } else {
+            // Increase by 1 if psotive
+            second_operand_rand = second_operand_rand.add(1);
+        }
+
+        let res = contract.function_must_fail(
+            "mul",
+            &[
+                ethabi::Token::Int(bigint_to_eth(
+                    &first_operand_rand,
+                    width.try_into().unwrap(),
+                )),
+                ethabi::Token::Int(bigint_to_eth(
+                    &second_operand_rand,
+                    width.try_into().unwrap(),
+                )),
+            ],
+            &[],
+            None,
+        );
+
+        assert_ne!(res, Ok(0));
+    }
+}
+
+#[test]
+fn test_overflow_detect_unsigned() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(uintN a, uintN b) public returns (uintN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        contract.constructor("test", &[]);
+
+        for _ in 0..10 {
+            // N bits can hold the range [0, (2^N)-1]. Generate a value that overflows N bits
+            let limit = BigUint::from(2_u32).pow(width);
+
+            // Generate a random number within the the range [0, 2^N -1]
+            let first_operand_rand = rng.gen_biguint(width.into()).add(1_u32);
+
+            // Calculate a number that when multiplied by first_operand_rand, the result will overflow N bits
+            let second_operand_rand = limit.div(&first_operand_rand).add(1_usize);
+
+            let res = contract.function_must_fail(
+                "mul",
+                &[
+                    ethabi::Token::Uint(U256::from_big_endian(&first_operand_rand.to_bytes_be())),
+                    ethabi::Token::Uint(U256::from_big_endian(&second_operand_rand.to_bytes_be())),
+                ],
+                &[],
+                None,
+            );
+            assert_ne!(res, Ok(0));
+        }
+    }
+}
+
 #[test]
 fn int() {
     let mut rng = rand::thread_rng();
@@ -615,7 +976,9 @@ fn int() {
             }
 
             function mul(intN a, intN b) public returns (intN) {
+                unchecked {
                 return a * b;
+                }
             }
 
             function div(intN a, intN b) public returns (intN) {

+ 1 - 1
tests/solana_tests/signature_verify.rs

@@ -46,7 +46,7 @@ fn verify() {
 
     vm.constructor("foo", &[]);
 
-    let mut csprng = rand::thread_rng();
+    let mut csprng = rand_07::thread_rng();
     let keypair: Keypair = Keypair::generate(&mut csprng);
 
     let message: &[u8] =

+ 2 - 0
tests/solana_tests/simple.rs

@@ -259,8 +259,10 @@ fn two_arrays() {
 
             constructor() {
                 for(uint i = 0; i < 10; i++) {
+                    unchecked {
                     array1.push((i*uint(sha256("i"))));
                     array2.push(((i+1)*uint(sha256("i"))));
+                    }
                }
             }
         }"#,

+ 4 - 47
tests/substrate.rs

@@ -1195,53 +1195,10 @@ impl MockSubstrate {
     }
 }
 
-pub fn build_solidity(src: &'static str) -> MockSubstrate {
-    let mut cache = FileResolver::new();
-
-    cache.set_file_contents("test.sol", src.to_string());
-
-    let (res, ns) = compile(
-        OsStr::new("test.sol"),
-        &mut cache,
-        inkwell::OptimizationLevel::Default,
-        Target::default_substrate(),
-        false,
-    );
-
-    ns.print_diagnostics_in_plain(&cache, false);
-
-    assert!(!ns.diagnostics.any_errors());
-
-    assert!(!res.is_empty());
-
-    let programs: Vec<Program> = res
-        .iter()
-        .map(|res| Program {
-            code: res.0.clone(),
-            abi: abi::substrate::load(&res.1).unwrap(),
-        })
-        .collect();
-
-    let mut accounts = HashMap::new();
-
-    let account = account_new();
-
-    accounts.insert(account, (programs[0].code.clone(), 0));
-
-    let vm = VirtualMachine::new(account, account_new(), 0);
-
-    MockSubstrate {
-        accounts,
-        printbuf: String::new(),
-        store: HashMap::new(),
-        programs,
-        vm,
-        current_program: 0,
-        events: Vec::new(),
-    }
+pub fn build_solidity(src: &str) -> MockSubstrate {
+    build_solidity_with_overflow_check(src, false)
 }
-
-pub fn build_solidity_with_overflow_check(src: &'static str) -> MockSubstrate {
+pub fn build_solidity_with_overflow_check(src: &str, math_overflow_flag: bool) -> MockSubstrate {
     let mut cache = FileResolver::new();
 
     cache.set_file_contents("test.sol", src.to_string());
@@ -1251,7 +1208,7 @@ pub fn build_solidity_with_overflow_check(src: &'static str) -> MockSubstrate {
         &mut cache,
         inkwell::OptimizationLevel::Default,
         Target::default_substrate(),
-        true,
+        math_overflow_flag,
     );
 
     ns.print_diagnostics_in_plain(&cache, false);

+ 377 - 2
tests/substrate_tests/expressions.rs

@@ -1,10 +1,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::{build_solidity, build_solidity_with_overflow_check};
-use num_bigint::BigInt;
-use num_bigint::Sign;
+use num_bigint::{BigInt, BigUint, RandBigInt, Sign};
 use parity_scale_codec::{Decode, Encode};
+use rand::seq::SliceRandom;
 use rand::Rng;
+use std::ops::Add;
+use std::ops::Div;
+use std::ops::Mul;
+use std::ops::Sub;
 
 #[test]
 fn celcius_and_fahrenheit() {
@@ -941,6 +945,57 @@ fn large_power() {
     );
 }
 
+#[test]
+fn test_power_overflow_boundaries() {
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function pow(uintN a, uintN b) public returns (uintN) { 
+                return a ** b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        let base = BigUint::from(2_u32);
+        let mut base_data = base.to_bytes_le();
+
+        let exp = BigUint::from(width - 1);
+        let mut exp_data = exp.to_bytes_le();
+
+        let width_rounded = (width as usize / 8).next_power_of_two();
+
+        base_data.resize((width_rounded) as usize, 0);
+        exp_data.resize((width_rounded) as usize, 0);
+
+        contract.function(
+            "pow",
+            base_data
+                .clone()
+                .into_iter()
+                .chain(exp_data.into_iter())
+                .collect(),
+        );
+
+        let res = BigUint::from(2_usize).pow((width - 1).try_into().unwrap());
+        let mut res_data = res.to_bytes_le();
+        res_data.resize(width / 8, 0);
+        contract.vm.output.truncate((width / 8) as usize);
+
+        assert_eq!(contract.vm.output, res_data);
+
+        let exp = exp.add(1_usize);
+        let mut exp_data = exp.to_bytes_le();
+        exp_data.resize((width_rounded) as usize, 0);
+
+        contract.function_expect_failure(
+            "pow",
+            base_data.into_iter().chain(exp_data.into_iter()).collect(),
+        );
+    }
+}
+
 #[test]
 fn multiply() {
     let mut rng = rand::thread_rng();
@@ -950,7 +1005,9 @@ fn multiply() {
         "
         contract c {
             function multiply(uint a, uint b) public returns (uint) {
+                unchecked {
                 return a * b;
+                }
             }
 
             function multiply_with_cast() public returns (uint64) {
@@ -1001,6 +1058,310 @@ fn multiply() {
     }
 }
 
+#[test]
+fn test_mul_within_range_signed() {
+    // We generate a random value that fits N bits. Then, we multiply that value by 1, -1 or 0.
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let width_rounded = (width / 8_usize).next_power_of_two();
+
+        let mut runtime = build_solidity(&src);
+
+        let a = rng.gen_bigint((width - 1).try_into().unwrap()).sub(1_u32);
+        let a_sign = a.sign();
+        let mut a_data = a.to_signed_bytes_le();
+
+        let side = vec![-1, 0, 1];
+        let b = BigInt::from(*side.choose(&mut rng).unwrap() as i32);
+        let b_sign = b.sign();
+        let mut b_data = b.to_signed_bytes_le();
+
+        a_data.resize((width_rounded) as usize, sign_extend(a_sign));
+        b_data.resize((width_rounded) as usize, sign_extend(b_sign));
+
+        runtime.function(
+            "mul",
+            a_data.into_iter().chain(b_data.into_iter()).collect(),
+        );
+
+        runtime.vm.output.truncate((width / 8) as usize);
+
+        let value = a * b;
+        let value_sign = value.sign();
+
+        let mut value_data = value.to_signed_bytes_le();
+        value_data.resize((width / 8) as usize, sign_extend(value_sign));
+
+        assert_eq!(value_data, runtime.vm.output);
+    }
+}
+
+#[test]
+fn test_mul_within_range() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(uintN a, uintN b) public returns (uintN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+
+        let width_rounded = (width as usize / 8).next_power_of_two();
+        let mut runtime = build_solidity(&src);
+
+        // The range of values that can be held in signed N bits is [-2^(N-1), 2^(N-1)-1]. Here we generate a random number within this range and multiply it by 1
+        let a = rng.gen_biguint((width).try_into().unwrap()).sub(1_u32);
+
+        let mut a_data = a.to_bytes_le();
+
+        let b = BigUint::from(1_u32);
+
+        let mut b_data = b.to_bytes_le();
+        a_data.resize((width_rounded) as usize, 0);
+        b_data.resize((width_rounded) as usize, 0);
+
+        runtime.function(
+            "mul",
+            a_data.into_iter().chain(b_data.into_iter()).collect(),
+        );
+
+        runtime.vm.output.truncate((width / 8) as usize);
+
+        let value = a * b;
+
+        let mut value_data = value.to_bytes_le();
+        value_data.resize((width / 8) as usize, 0);
+
+        assert_eq!(value_data, runtime.vm.output);
+    }
+}
+
+#[test]
+fn test_overflow_boundaries() {
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        // 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);
+        let mut up_data = upper_boundary.to_signed_bytes_le();
+
+        let lower_boundary = BigInt::from(2_u32).pow(width - 1).mul(-1_i32);
+        let mut low_data = lower_boundary.to_signed_bytes_le();
+
+        let second_op = BigInt::from(1_u32);
+        let mut sec_data = second_op.to_signed_bytes_le();
+
+        let width_rounded = (width as usize / 8).next_power_of_two();
+
+        up_data.resize((width_rounded) as usize, 0);
+        low_data.resize((width_rounded) as usize, 255);
+        sec_data.resize((width_rounded) as usize, 0);
+
+        // Multiply the boundaries by 1.
+        contract.function(
+            "mul",
+            up_data
+                .clone()
+                .into_iter()
+                .chain(sec_data.clone().into_iter())
+                .collect(),
+        );
+
+        let res = upper_boundary.clone().mul(1_u32);
+        let mut res_data = res.to_signed_bytes_le();
+        res_data.resize((width / 8) as usize, 0);
+        contract.vm.output.truncate((width / 8) as usize);
+
+        assert_eq!(res_data, contract.vm.output);
+
+        contract.function(
+            "mul",
+            low_data
+                .clone()
+                .into_iter()
+                .chain(sec_data.clone().into_iter())
+                .collect(),
+        );
+
+        let res = lower_boundary.clone().mul(1_u32);
+        let mut res_data = res.to_signed_bytes_le();
+        res_data.resize((width / 8) as usize, 0);
+        contract.vm.output.truncate((width / 8) as usize);
+
+        assert_eq!(res_data, contract.vm.output);
+
+        let upper_boundary_plus_one = BigInt::from(2_u32).pow(width - 1);
+
+        // We subtract 2 instead of one to make the number even, so that no rounding occurs when we divide by 2 later on.
+        let lower_boundary_minus_two = BigInt::from(2_u32).pow(width - 1).mul(-1_i32).sub(2_i32);
+
+        let upper_second_op = upper_boundary_plus_one.div(2_u32);
+        let mut upper_second_op_data = upper_second_op.to_signed_bytes_le();
+
+        let lower_second_op = lower_boundary_minus_two.div(2_u32);
+        let mut lower_second_op_data = lower_second_op.to_signed_bytes_le();
+
+        let mut two_data = BigInt::from(2_u32).to_signed_bytes_le();
+
+        upper_second_op_data.resize((width_rounded) as usize, 0);
+        two_data.resize((width_rounded) as usize, 0);
+        lower_second_op_data.resize((width_rounded) as usize, 255);
+
+        // This will generate a value more than the upper boundary.
+        contract.function_expect_failure(
+            "mul",
+            upper_second_op_data
+                .clone()
+                .into_iter()
+                .chain(two_data.clone().into_iter())
+                .collect(),
+        );
+
+        // Generate a value less than the lower boundary
+        contract.function_expect_failure(
+            "mul",
+            lower_second_op_data
+                .clone()
+                .into_iter()
+                .chain(two_data.clone().into_iter())
+                .collect(),
+        );
+
+        // Upper boundary * Upper boundary
+        contract.function_expect_failure(
+            "mul",
+            up_data
+                .clone()
+                .into_iter()
+                .chain(up_data.clone().into_iter())
+                .collect(),
+        );
+
+        // Lower boundary * Lower boundary
+        contract.function_expect_failure(
+            "mul",
+            low_data
+                .clone()
+                .into_iter()
+                .chain(low_data.clone().into_iter())
+                .collect(),
+        );
+
+        // Lower boundary * Upper boundary
+        contract.function_expect_failure(
+            "mul",
+            low_data
+                .clone()
+                .into_iter()
+                .chain(up_data.clone().into_iter())
+                .collect(),
+        );
+    }
+}
+
+#[test]
+fn test_overflow_detect_signed() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(intN a, intN b) public returns (intN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        // 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).add(1_u32);
+
+        let first_operand_rand = rng.gen_bigint((width - 1).into()).sub(1_u32);
+        let first_op_sign = first_operand_rand.sign();
+        let mut first_op_data = first_operand_rand.to_signed_bytes_le();
+
+        let width_rounded = (width as usize / 8).next_power_of_two();
+        first_op_data.resize((width_rounded) as usize, sign_extend(first_op_sign));
+
+        // Calculate a number that when multiplied by first_operand_rand, the result will overflow N bits
+        let mut second_operand_rand = limit / &first_operand_rand;
+
+        if let Sign::Minus = second_operand_rand.sign() {
+            // Decrease by 1 if negative, this is to make sure the result will overflow
+            second_operand_rand = second_operand_rand.sub(1);
+        } else {
+            // Increase by 1 if psotive
+            second_operand_rand = second_operand_rand.add(1);
+        }
+
+        let second_op_sign = second_operand_rand.sign();
+        let mut second_op_data = second_operand_rand.to_signed_bytes_le();
+        second_op_data.resize((width_rounded) as usize, sign_extend(second_op_sign));
+
+        contract.function_expect_failure(
+            "mul",
+            first_op_data
+                .into_iter()
+                .chain(second_op_data.into_iter())
+                .collect(),
+        );
+    }
+}
+
+#[test]
+fn test_overflow_detect_unsigned() {
+    let mut rng = rand::thread_rng();
+    for width in (8..=256).step_by(8) {
+        let src = r#"
+        contract test {
+            function mul(uintN a, uintN b) public returns (uintN) {
+                return a * b;
+            }
+        }"#
+        .replace("intN", &format!("int{}", width));
+        let mut contract = build_solidity_with_overflow_check(&src, true);
+
+        // 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 = BigUint::from(2_u32).pow(width).add(1_u32);
+
+        let first_operand_rand = rng.gen_biguint((width).into()).sub(1_u32);
+        let mut first_op_data = first_operand_rand.to_bytes_le();
+
+        let width_rounded = (width as usize / 8).next_power_of_two();
+        first_op_data.resize((width_rounded) as usize, 0);
+
+        // Calculate a number that when multiplied by first_operand_rand, the result will overflow N bits
+        let second_operand_rand = (limit / &first_operand_rand).add(1_u32);
+
+        let mut second_op_data = second_operand_rand.to_bytes_le();
+        second_op_data.resize((width_rounded) as usize, 0);
+
+        contract.function_expect_failure(
+            "mul",
+            first_op_data
+                .into_iter()
+                .chain(second_op_data.into_iter())
+                .collect(),
+        );
+    }
+}
+
 #[test]
 fn bytes_bitwise() {
     #[derive(Debug, PartialEq, Eq, Encode, Decode)]
@@ -1329,6 +1690,7 @@ fn addition_overflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1351,6 +1713,7 @@ fn unchecked_addition_overflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1372,6 +1735,7 @@ fn subtraction_underflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1394,6 +1758,7 @@ fn unchecked_subtraction_underflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1415,6 +1780,7 @@ fn multiplication_overflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1437,6 +1803,7 @@ fn unchecked_multiplication_overflow() {
             }
         }
         "#,
+        true,
     );
 
     runtime.function("bar", Vec::new());
@@ -1509,3 +1876,11 @@ fn address_pass_by_value() {
 
     runtime.function("bar", Vec::new());
 }
+
+fn sign_extend(sign: Sign) -> u8 {
+    if sign == Sign::Minus {
+        255
+    } else {
+        0
+    }
+}