From 30efce02c7666ff9670ea5fef1595fb134904bb8 Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Mon, 15 Mar 2021 11:30:15 +0100 Subject: [PATCH] Implement checks for divison operations Related to #153 --- compiler/src/yul/mappers/assignments.rs | 2 +- compiler/src/yul/mappers/expressions.rs | 10 ++-- compiler/src/yul/names.rs | 10 ++++ compiler/src/yul/runtime/functions/math.rs | 48 ++++++++++++++++++- compiler/tests/features.rs | 38 +++++++++++++++ .../fixtures/features/checked_arithmetic.fe | 36 ++++++++++++++ newsfragments/308.feature.md | 1 + 7 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 newsfragments/308.feature.md diff --git a/compiler/src/yul/mappers/assignments.rs b/compiler/src/yul/mappers/assignments.rs index 1d1cd96312..4e471b329d 100644 --- a/compiler/src/yul/mappers/assignments.rs +++ b/compiler/src/yul/mappers/assignments.rs @@ -131,7 +131,7 @@ mod tests { case("foo = 1 + 2", "$foo := checked_add_u256(1, 2)"), case("foo = 1 - 2", "$foo := checked_sub_unsigned(1, 2)"), case("foo = 1 * 2", "$foo := checked_mul_u256(1, 2)"), - case("foo = 1 / 2", "$foo := div(1, 2)"), + case("foo = 1 / 2", "$foo := checked_div_unsigned(1, 2)"), case("foo = 1 ** 2", "$foo := exp(1, 2)"), case("foo = 1 % 2", "$foo := mod(1, 2)"), case("foo = 1 & 2", "$foo := and(1, 2)"), diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index 5126f5c598..9bcf752c39 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -269,9 +269,11 @@ pub fn expr_bin_operation( } _ => unreachable!(), }, - fe::BinOperator::Div => match typ.is_signed_integer() { - true => Ok(expression! { sdiv([yul_left], [yul_right]) }), - false => Ok(expression! { div([yul_left], [yul_right]) }), + fe::BinOperator::Div => match typ { + Type::Base(Base::Numeric(integer)) => { + Ok(expression! { [names::checked_div(integer)]([yul_left], [yul_right]) }) + } + _ => unreachable!(), }, fe::BinOperator::BitAnd => Ok(expression! { and([yul_left], [yul_right]) }), fe::BinOperator::BitOr => Ok(expression! { or([yul_left], [yul_right]) }), @@ -686,7 +688,7 @@ mod tests { case("1 + 2", "checked_add_u256(1, 2)"), case("1 - 2", "checked_sub_unsigned(1, 2)"), case("1 * 2", "checked_mul_u256(1, 2)"), - case("1 / 2", "div(1, 2)"), + case("1 / 2", "checked_div_unsigned(1, 2)"), case("1 ** 2", "exp(1, 2)"), case("1 % 2", "mod(1, 2)"), case("1 & 2", "and(1, 2)"), diff --git a/compiler/src/yul/names.rs b/compiler/src/yul/names.rs index caf8a425e5..6b89e0dddf 100644 --- a/compiler/src/yul/names.rs +++ b/compiler/src/yul/names.rs @@ -11,6 +11,16 @@ pub fn checked_add(size: &Integer) -> yul::Identifier { identifier! {(format!("checked_add_{}", size.to_lowercase()))} } +/// Generate a function name to perform checked division +pub fn checked_div(size: &Integer) -> yul::Identifier { + let size: &str = if size.is_signed() { + size.into() + } else { + "unsigned" + }; + identifier! {(format!("checked_div_{}", size.to_lowercase()))} +} + /// Generate a function name to perform checked multiplication pub fn checked_mul(size: &Integer) -> yul::Identifier { let size: &str = size.into(); diff --git a/compiler/src/yul/runtime/functions/math.rs b/compiler/src/yul/runtime/functions/math.rs index c7d07b7379..34e8461cf5 100644 --- a/compiler/src/yul/runtime/functions/math.rs +++ b/compiler/src/yul/runtime/functions/math.rs @@ -22,6 +22,20 @@ pub fn checked_add_fns() -> Vec { ] } +/// Return a vector of runtime functions for divisions with +/// over-/underflow protection +pub fn checked_div_fns() -> Vec { + vec![ + checked_div_unsigned(), + checked_div_signed(Integer::I256), + checked_div_signed(Integer::I128), + checked_div_signed(Integer::I64), + checked_div_signed(Integer::I32), + checked_div_signed(Integer::I16), + checked_div_signed(Integer::I8), + ] +} + /// Return a vector of runtime functions for multiplications with /// over-/underflow protection pub fn checked_mul_fns() -> Vec { @@ -57,7 +71,13 @@ pub fn checked_sub_fns() -> Vec { // Return all math runtime functions pub fn all() -> Vec { - [checked_add_fns(), checked_mul_fns(), checked_sub_fns()].concat() + [ + checked_add_fns(), + checked_div_fns(), + checked_mul_fns(), + checked_sub_fns(), + ] + .concat() } fn checked_mul_unsigned(size: Integer) -> yul::Statement { @@ -131,6 +151,32 @@ fn checked_add_signed(size: Integer) -> yul::Statement { } } +fn checked_div_unsigned() -> yul::Statement { + function_definition! { + function checked_div_unsigned(val1, val2) -> result { + (if (iszero(val2)) { (revert(0, 0)) }) + (result := div(val1, val2)) + } + } +} + +fn checked_div_signed(size: Integer) -> yul::Statement { + if !size.is_signed() { + panic!("Expected signed integer") + } + let (min_value, _) = get_min_max(size.clone()); + let fn_name = names::checked_div(&size); + function_definition! { + function [fn_name](val1, val2) -> result { + (if (iszero(val2)) { (revert(0, 0)) }) + + // overflow for min_val / -1 + (if (and( (eq(val1, [min_value])), (eq(val2, (sub(0, 1))))) ) { (revert(0, 0)) }) + (result := sdiv(val1, val2)) + } + } +} + fn checked_sub_unsigned() -> yul::Statement { function_definition! { function checked_sub_unsigned(val1, val2) -> diff { diff --git a/compiler/tests/features.rs b/compiler/tests/features.rs index b4c31c0ac5..9a8cfc9818 100644 --- a/compiler/tests/features.rs +++ b/compiler/tests/features.rs @@ -749,6 +749,44 @@ fn checked_arithmetic() { Some(&config.i_max), ); + // DIVISON + // unsigned: anything / 0 fails + harness.test_function_reverts( + &mut executor, + &format!("div_u{}", config.size), + &[config.u_max.clone(), uint_token(0)], + ); + + // unsigned: 3 / 2 works + harness.test_function( + &mut executor, + &format!("div_u{}", config.size), + &[uint_token(3), uint_token(2)], + Some(&uint_token(1)), + ); + + // signed: anything / 0 fails + harness.test_function_reverts( + &mut executor, + &format!("div_i{}", config.size), + &[config.i_max.clone(), int_token(0)], + ); + + // signed: min_value / -1 fails + harness.test_function_reverts( + &mut executor, + &format!("div_i{}", config.size), + &[config.i_min.clone(), int_token(-1)], + ); + + // unsigned: 3 / -2 works + harness.test_function( + &mut executor, + &format!("div_i{}", config.size), + &[int_token(3), int_token(-2)], + Some(&int_token(-1)), + ); + // MULTIPLICATION // unsigned: max_value * 2 fails harness.test_function_reverts( diff --git a/compiler/tests/fixtures/features/checked_arithmetic.fe b/compiler/tests/fixtures/features/checked_arithmetic.fe index d7b2fa9e65..6eefe32ca7 100644 --- a/compiler/tests/fixtures/features/checked_arithmetic.fe +++ b/compiler/tests/fixtures/features/checked_arithmetic.fe @@ -75,6 +75,42 @@ contract CheckedArithmetic: pub def mul_u256(left: u256, right: u256) -> u256: return left * right + pub def div_u256(left: u256, right: u256) -> u256: + return left / right + + pub def div_u128(left: u128, right: u128) -> u128: + return left / right + + pub def div_u64(left: u64, right: u64) -> u64: + return left / right + + pub def div_u32(left: u32, right: u32) -> u32: + return left / right + + pub def div_u16(left: u16, right: u16) -> u16: + return left / right + + pub def div_u8(left: u8, right: u8) -> u8: + return left / right + + pub def div_i256(left: i256, right: i256) -> i256: + return left / right + + pub def div_i128(left: i128, right: i128) -> i128: + return left / right + + pub def div_i64(left: i64, right: i64) -> i64: + return left / right + + pub def div_i32(left: i32, right: i32) -> i32: + return left / right + + pub def div_i16(left: i16, right: i16) -> i16: + return left / right + + pub def div_i8(left: i8, right: i8) -> i8: + return left / right + pub def mul_u128(left: u128, right: u128) -> u128: return left * right diff --git a/newsfragments/308.feature.md b/newsfragments/308.feature.md new file mode 100644 index 0000000000..79265c39c3 --- /dev/null +++ b/newsfragments/308.feature.md @@ -0,0 +1 @@ +Perform checks for divison operations on integers \ No newline at end of file