Skip to content

Commit

Permalink
Implement checks for modulo arithmetic
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Mar 16, 2021
1 parent 30efce0 commit 364ec10
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 12 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 @@ -133,7 +133,7 @@ mod tests {
case("foo = 1 * 2", "$foo := checked_mul_u256(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 := checked_mod_unsigned(1, 2)"),
case("foo = 1 & 2", "$foo := and(1, 2)"),
case("foo = 1 | 2", "$foo := or(1, 2)"),
case("foo = 1 ^ 2", "$foo := xor(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 @@ -283,9 +283,11 @@ pub fn expr_bin_operation(
true => Ok(expression! { sar([yul_right], [yul_left]) }),
false => Ok(expression! { shr([yul_right], [yul_left]) }),
},
fe::BinOperator::Mod => match typ.is_signed_integer() {
true => Ok(expression! { smod([yul_left], [yul_right]) }),
false => Ok(expression! { mod([yul_left], [yul_right]) }),
fe::BinOperator::Mod => match typ {
Type::Base(Base::Numeric(integer)) => {
Ok(expression! { [names::checked_mod(integer)]([yul_left], [yul_right]) })
}
_ => unreachable!(),
},
fe::BinOperator::Pow => Ok(expression! { exp([yul_left], [yul_right]) }),
_ => unimplemented!(),
Expand Down Expand Up @@ -690,7 +692,7 @@ mod tests {
case("1 * 2", "checked_mul_u256(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", "checked_mod_unsigned(1, 2)"),
case("1 & 2", "and(1, 2)"),
case("1 | 2", "or(1, 2)"),
case("1 ^ 2", "xor(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 @@ -21,6 +21,16 @@ pub fn checked_div(size: &Integer) -> yul::Identifier {
identifier! {(format!("checked_div_{}", size.to_lowercase()))}
}

/// Generate a function name to perform checked modulo
pub fn checked_mod(size: &Integer) -> yul::Identifier {
let sign: &str = if size.is_signed() {
"signed"
} else {
"unsigned"
};
identifier! {(format!("checked_mod_{}", sign.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
24 changes: 24 additions & 0 deletions compiler/src/yul/runtime/functions/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ pub fn checked_div_fns() -> Vec<yul::Statement> {
]
}

/// Return a vector of runtime functions for checked modulo arithmetic
pub fn checked_mod_fns() -> Vec<yul::Statement> {
vec![checked_mod_unsigned(), checked_mod_signed()]
}

/// Return a vector of runtime functions for multiplications with
/// over-/underflow protection
pub fn checked_mul_fns() -> Vec<yul::Statement> {
Expand Down Expand Up @@ -74,12 +79,31 @@ pub fn all() -> Vec<yul::Statement> {
[
checked_add_fns(),
checked_div_fns(),
checked_mod_fns(),
checked_mul_fns(),
checked_sub_fns(),
]
.concat()
}

fn checked_mod_unsigned() -> yul::Statement {
function_definition! {
function checked_mod_unsigned(val1, val2) -> result {
(if (iszero(val2)) { (revert(0, 0)) })
(result := mod(val1, val2))
}
}
}

fn checked_mod_signed() -> yul::Statement {
function_definition! {
function checked_mod_signed(val1, val2) -> result {
(if (iszero(val2)) { (revert(0, 0)) })
(result := smod(val1, val2))
}
}
}

fn checked_mul_unsigned(size: Integer) -> yul::Statement {
if size.is_signed() {
panic!("Expected unsigned integer")
Expand Down
51 changes: 48 additions & 3 deletions compiler/tests/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,9 @@ fn test_assert() {
case("return_division_i256.fe", &[int_token(-42), int_token(42)], int_token(-1)),
case("return_pow_u256.fe", &[uint_token(2), uint_token(0)], uint_token(1)),
case("return_pow_u256.fe", &[uint_token(2), uint_token(4)], uint_token(16)),
case("return_mod_u256.fe", &[uint_token(5), uint_token(0)], uint_token(0)),
case("return_mod_u256.fe", &[uint_token(5), uint_token(2)], uint_token(1)),
case("return_mod_u256.fe", &[uint_token(5), uint_token(3)], uint_token(2)),
case("return_mod_u256.fe", &[uint_token(5), uint_token(5)], uint_token(0)),
case("return_mod_i256.fe", &[int_token(5), int_token(0)], int_token(0)),
case("return_mod_i256.fe", &[int_token(5), int_token(2)], int_token(1)),
case("return_mod_i256.fe", &[int_token(5), int_token(3)], int_token(2)),
case("return_mod_i256.fe", &[int_token(5), int_token(5)], int_token(0)),
Expand Down Expand Up @@ -779,14 +777,61 @@ fn checked_arithmetic() {
&[config.i_min.clone(), int_token(-1)],
);

// unsigned: 3 / -2 works
// signed: 3 / -2 works
harness.test_function(
&mut executor,
&format!("div_i{}", config.size),
&[int_token(3), int_token(-2)],
Some(&int_token(-1)),
);

// MODULO
// unsigned: anything % 0 fails
harness.test_function_reverts(
&mut executor,
&format!("mod_u{}", config.size),
&[config.u_max.clone(), uint_token(0)],
);

// unsigned: max_value % 2 works
harness.test_function(
&mut executor,
&format!("mod_u{}", config.size),
&[config.u_max.clone(), uint_token(2)],
Some(&uint_token(1)),
);

// signed: anything % 0 fails
harness.test_function_reverts(
&mut executor,
&format!("mod_i{}", config.size),
&[config.i_max.clone(), int_token(0)],
);

// unsigned: max_value % 2 works
harness.test_function(
&mut executor,
&format!("mod_i{}", config.size),
&[config.i_max.clone(), int_token(2)],
Some(&int_token(1)),
);

// signed: 13 % -3 works
harness.test_function(
&mut executor,
&format!("mod_i{}", config.size),
&[int_token(13), int_token(-3)],
Some(&int_token(1)),
);

// signed: -13 % 3 works
harness.test_function(
&mut executor,
&format!("mod_i{}", config.size),
&[int_token(-13), int_token(3)],
Some(&int_token(-1)),
);

// MULTIPLICATION
// unsigned: max_value * 2 fails
harness.test_function_reverts(
Expand Down
44 changes: 40 additions & 4 deletions compiler/tests/fixtures/features/checked_arithmetic.fe
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ contract CheckedArithmetic:
pub def sub_i8(left: i8, right: i8) -> i8:
return left - right

pub def mul_u256(left: u256, right: u256) -> u256:
return left * right

pub def div_u256(left: u256, right: u256) -> u256:
return left / right

Expand Down Expand Up @@ -111,6 +108,9 @@ contract CheckedArithmetic:
pub def div_i8(left: i8, right: i8) -> i8:
return left / right

pub def mul_u256(left: u256, right: u256) -> u256:
return left * right

pub def mul_u128(left: u128, right: u128) -> u128:
return left * right

Expand Down Expand Up @@ -142,4 +142,40 @@ contract CheckedArithmetic:
return left * right

pub def mul_i8(left: i8, right: i8) -> i8:
return left * right
return left * right

pub def mod_u256(left: u256, right: u256) -> u256:
return left % right

pub def mod_u128(left: u128, right: u128) -> u128:
return left % right

pub def mod_u64(left: u64, right: u64) -> u64:
return left % right

pub def mod_u32(left: u32, right: u32) -> u32:
return left % right

pub def mod_u16(left: u16, right: u16) -> u16:
return left % right

pub def mod_u8(left: u8, right: u8) -> u8:
return left % right

pub def mod_i256(left: i256, right: i256) -> i256:
return left % right

pub def mod_i128(left: i128, right: i128) -> i128:
return left % right

pub def mod_i64(left: i64, right: i64) -> i64:
return left % right

pub def mod_i32(left: i32, right: i32) -> i32:
return left % right

pub def mod_i16(left: i16, right: i16) -> i16:
return left % right

pub def mod_i8(left: i8, right: i8) -> i8:
return left % right
1 change: 1 addition & 0 deletions newsfragments/312.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Perform checks for modulo operations on integers

0 comments on commit 364ec10

Please sign in to comment.