Skip to content

Commit

Permalink
Implement checks for divison operations
Browse files Browse the repository at this point in the history
Related to #153
  • Loading branch information
cburgdorf committed Mar 15, 2021
1 parent 3c97016 commit 30efce0
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 6 deletions.
2 changes: 1 addition & 1 deletion compiler/src/yul/mappers/assignments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/yul/mappers/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]) }),
Expand Down Expand Up @@ -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)"),
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/yul/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
48 changes: 47 additions & 1 deletion compiler/src/yul/runtime/functions/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ pub fn checked_add_fns() -> Vec<yul::Statement> {
]
}

/// Return a vector of runtime functions for divisions with
/// over-/underflow protection
pub fn checked_div_fns() -> Vec<yul::Statement> {
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<yul::Statement> {
Expand Down Expand Up @@ -57,7 +71,13 @@ pub fn checked_sub_fns() -> Vec<yul::Statement> {

// Return all math runtime functions
pub fn all() -> Vec<yul::Statement> {
[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 {
Expand Down Expand Up @@ -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 {
Expand Down
38 changes: 38 additions & 0 deletions compiler/tests/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
36 changes: 36 additions & 0 deletions compiler/tests/fixtures/features/checked_arithmetic.fe
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions newsfragments/308.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Perform checks for divison operations on integers

0 comments on commit 30efce0

Please sign in to comment.