diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index 66357da51a..3e5ba37e49 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -239,7 +239,10 @@ pub fn expr_bin_operation( .typ; return match op.node { - fe::BinOperator::Add => Ok(expression! { add([yul_left], [yul_right]) }), + fe::BinOperator::Add => match typ.is_signed_integer() { + true => Ok(expression! { checked_add_i256([yul_left], [yul_right]) }), + false => Ok(expression! { checked_add_u256([yul_left], [yul_right]) }), + }, fe::BinOperator::Sub => Ok(expression! { sub([yul_left], [yul_right]) }), fe::BinOperator::Mult => Ok(expression! { mul([yul_left], [yul_right]) }), fe::BinOperator::Div => match typ.is_signed_integer() { diff --git a/compiler/src/yul/runtime/functions/math.rs b/compiler/src/yul/runtime/functions/math.rs new file mode 100644 index 0000000000..0a39855e03 --- /dev/null +++ b/compiler/src/yul/runtime/functions/math.rs @@ -0,0 +1,25 @@ +use yultsur::*; + +/// Add two u256 numbers. Revert if result overflows. +pub fn checked_add_u256() -> yul::Statement { + function_definition! { + function checked_add_u256(val1, val2) -> sum { + // overflow, if val1 > (max_value - val2) + (if (gt(val1, (sub(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, val2)))) { (revert(0, 0)) }) + (sum := add(val1, val2)) + } + } +} + +/// Add two i256 numbers. Revert if result over- or underflows. +pub fn checked_add_i256() -> yul::Statement { + function_definition! { + function checked_add_i256(val1, val2) -> sum { + // overflow, if val1 >= 0 and val2 > (max_value - val1) + (if (and((iszero((slt(val1, 0)))), (sgt(val2, (sub(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, val1)))))) { (revert(0, 0)) }) + // underflow, if val1 < 0 and val2 < (min_val - val1) + (if (and((slt(val1, 0)), (slt(val2, (sub(0x8000000000000000000000000000000000000000000000000000000000000000, val1)))))) { (revert(0, 0)) }) + (sum := add(val1, val2)) + } + } +} diff --git a/compiler/src/yul/runtime/functions/mod.rs b/compiler/src/yul/runtime/functions/mod.rs index ef3d3aa319..39e2f76c12 100644 --- a/compiler/src/yul/runtime/functions/mod.rs +++ b/compiler/src/yul/runtime/functions/mod.rs @@ -4,6 +4,7 @@ use yultsur::*; pub mod abi; pub mod contracts; pub mod data; +pub mod math; pub mod structs; /// Returns all functions that should be available during runtime. @@ -35,5 +36,7 @@ pub fn std() -> Vec { abi::pack(AbiDecodeLocation::Memory), contracts::create2(), contracts::create(), + math::checked_add_u256(), + math::checked_add_i256(), ] } diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index bffeab329e..12546114aa 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -707,6 +707,59 @@ fn sized_vals_in_sto() { }); } +#[test] +fn checked_arithmetic() { + with_executor(&|mut executor| { + let harness = deploy_contract( + &mut executor, + "checked_arithmetic.fe", + "CheckedArithmetic", + &[], + ); + + let u256_max = ethabi::Token::Uint(U256::MAX); + let i256_max = ethabi::Token::Int(U256::from(2).pow(U256::from(255)) - 1); + let i256_min = ethabi::Token::Int(get_2s_complement_for_negative( + U256::from(2).pow(U256::from(255)), + )); + + harness.test_function_reverts( + &mut executor, + "add_u256", + &[u256_max.clone(), uint_token(1)], + ); + + harness.test_function( + &mut executor, + "add_u256", + &[u256_max.clone(), uint_token(0)], + Some(&u256_max), + ); + + harness.test_function_reverts(&mut executor, "add_i256", &[i256_max.clone(), int_token(1)]); + + harness.test_function( + &mut executor, + "add_i256", + &[i256_max.clone(), uint_token(0)], + Some(&i256_max), + ); + + harness.test_function_reverts( + &mut executor, + "add_i256", + &[i256_min.clone(), int_token(-1)], + ); + + harness.test_function( + &mut executor, + "add_i256", + &[i256_min.clone(), int_token(0)], + Some(&i256_max), + ); + }); +} + #[test] fn structs() { with_executor(&|mut executor| { diff --git a/compiler/tests/fixtures/checked_arithmetic.fe b/compiler/tests/fixtures/checked_arithmetic.fe new file mode 100644 index 0000000000..2918feb6f7 --- /dev/null +++ b/compiler/tests/fixtures/checked_arithmetic.fe @@ -0,0 +1,7 @@ +contract CheckedArithmetic: + + pub def add_u256(left: u256, right: u256) -> u256: + return left + right + + pub def add_i256(left: i256, right: i256) -> i256: + return left + right diff --git a/newsfragments/265.feature.md b/newsfragments/265.feature.md new file mode 100644 index 0000000000..4063a482ee --- /dev/null +++ b/newsfragments/265.feature.md @@ -0,0 +1,4 @@ +Do over/underflow checks for additions (SafeMath). + +With this change all additions (e.g `x + y`) for signed and unsigned +integers check for over- and underflows and revert if necessary. \ No newline at end of file