diff --git a/crates/analyzer/src/namespace/types.rs b/crates/analyzer/src/namespace/types.rs index c8944cefe5..2aa103527e 100644 --- a/crates/analyzer/src/namespace/types.rs +++ b/crates/analyzer/src/namespace/types.rs @@ -70,6 +70,20 @@ impl AbiType { _ => todo!("recursive encoding"), } } + + /// `true` if the encoded value is stored in the data section, `false` if it is not. + pub fn has_data(&self) -> bool { + match self { + AbiType::Uint { .. } => false, + AbiType::StaticArray { .. } => false, + AbiType::Tuple { .. } => false, + AbiType::Int { .. } => false, + AbiType::Bool => false, + AbiType::Address => false, + AbiType::String { .. } => true, + AbiType::Bytes { .. } => true, + } + } } /// Data can be decoded from memory or calldata. @@ -381,6 +395,21 @@ impl FixedSize { FixedSize::Base(Base::Bool) } + /// Creates an instance of address. + pub fn address() -> Self { + FixedSize::Base(Base::Address) + } + + /// Creates an instance of u256. + pub fn u256() -> Self { + FixedSize::Base(Base::Numeric(Integer::U256)) + } + + /// Creates an instance of u8. + pub fn u8() -> Self { + FixedSize::Base(Base::Numeric(Integer::U8)) + } + /// Creates an instance of `()`. pub fn unit() -> Self { FixedSize::Base(Base::Unit) diff --git a/crates/test-files/fixtures/features/abi_decode_checks.fe b/crates/test-files/fixtures/features/abi_decode_checks.fe new file mode 100644 index 0000000000..db5e239744 --- /dev/null +++ b/crates/test-files/fixtures/features/abi_decode_checks.fe @@ -0,0 +1,22 @@ +contract Foo: + pub def decode_u256(a: u256): + pass + + pub def decode_u128_bool(a: u128, b: bool): + pass + + pub def decode_u256_bytes_tuple_array( + a: u256, + b: u8[100], + c: (address, u8), + d: i16[26] + ): + pass + + pub def decode_string_address_bytes_bool( + a: String<80>, + b: address, + c: u8[1000], + d: bool + ): + pass diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index e0af52bcd6..5bf0edd053 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -60,18 +60,28 @@ impl ContractHarness { name: &str, input: &[ethabi::Token], ) -> evm::Capture<(evm::ExitReason, Vec), std::convert::Infallible> { + let input = self.build_calldata(name, input); + self.capture_call_raw_bytes(executor, input) + } + + pub fn build_calldata(&self, name: &str, input: &[ethabi::Token]) -> Vec { let function = &self.abi.functions[name][0]; + function + .encode_input(input) + .unwrap_or_else(|_| panic!("Unable to encode input for {}", name)) + } + pub fn capture_call_raw_bytes( + &self, + executor: &mut Executor, + input: Vec, + ) -> evm::Capture<(evm::ExitReason, Vec), std::convert::Infallible> { let context = evm::Context { address: self.address, caller: self.caller, apparent_value: self.value, }; - let input = function - .encode_input(input) - .unwrap_or_else(|_| panic!("Unable to encode input for {}", name)); - executor.call(self.address, None, input, None, false, context) } @@ -114,6 +124,13 @@ impl ContractHarness { validate_revert(self.capture_call(executor, name, input), revert_data) } + pub fn test_call_reverts(&self, executor: &mut Executor, input: Vec) { + match self.capture_call_raw_bytes(executor, input) { + evm::Capture::Exit((ExitReason::Revert(_), _)) => {} + _ => panic!("function did not revert"), + } + } + // Executor must be passed by value to get emitted events. pub fn events_emitted(&self, executor: Executor, events: &[(&str, &[ethabi::Token])]) { let raw_logs = executor @@ -616,10 +633,15 @@ pub fn bytes_token(s: &str) -> ethabi::Token { } #[allow(dead_code)] -pub fn u256_array_token(v: &[u64]) -> ethabi::Token { +pub fn uint_array_token(v: &[u64]) -> ethabi::Token { ethabi::Token::FixedArray(v.iter().map(|n| uint_token(*n)).collect()) } +#[allow(dead_code)] +pub fn int_array_token(v: &[i64]) -> ethabi::Token { + ethabi::Token::FixedArray(v.iter().map(|n| int_token(*n)).collect()) +} + #[allow(dead_code)] pub fn address_array_token(v: &[&str]) -> ethabi::Token { ethabi::Token::FixedArray(v.iter().map(|s| address_token(s)).collect()) diff --git a/crates/tests/src/features.rs b/crates/tests/src/features.rs index b803a300a6..5f799dc2f7 100644 --- a/crates/tests/src/features.rs +++ b/crates/tests/src/features.rs @@ -254,7 +254,7 @@ fn return_array() { &mut executor, "bar", &[uint_token(42)], - Some(&u256_array_token(&[0, 0, 0, 42, 0])), + Some(&uint_array_token(&[0, 0, 0, 42, 0])), ) }) } @@ -268,7 +268,7 @@ fn multi_param() { &mut executor, "bar", &[uint_token(4), uint_token(42), uint_token(420)], - Some(&u256_array_token(&[4, 42, 420])), + Some(&uint_array_token(&[4, 42, 420])), ) }) } @@ -296,7 +296,7 @@ fn test_map(fixture_file: &str) { harness.test_function( &mut executor, "write_bar", - &[uint_token(420), uint_token(12)], + &[uint_token(26), uint_token(12)], None, ); @@ -310,7 +310,7 @@ fn test_map(fixture_file: &str) { harness.test_function( &mut executor, "read_bar", - &[uint_token(420)], + &[uint_token(26)], Some(&uint_token(12)), ); }) @@ -651,7 +651,7 @@ fn sized_vals_in_sto() { let harness = deploy_contract(&mut executor, "sized_vals_in_sto.fe", "Foo", &[]); let num = uint_token(68); - let nums = u256_array_token(&(0..42).into_iter().collect::>()); + let nums = uint_array_token(&(0..42).into_iter().collect::>()); let string = string_token("there are 26 protons in fe"); harness.test_function(&mut executor, "write_num", &[num.clone()], None); @@ -1128,7 +1128,7 @@ fn external_contract() { &mut executor, "call_build_array", &[foo_address, uint_token(a), uint_token(b)], - Some(&u256_array_token(&[a, c, b])), + Some(&uint_array_token(&[a, c, b])), ); foo_harness.events_emitted(executor, &[("MyEvent", &[my_num, my_addrs, my_string])]); @@ -1276,3 +1276,245 @@ fn tuple_destructuring() { ); }); } + +#[test] +fn abi_decode_checks() { + with_executor(&|mut executor| { + let harness = deploy_contract(&mut executor, "abi_decode_checks.fe", "Foo", &[]); + + // decode_u256 + { + let input = [uint_token(99999999)]; + let data = harness.build_calldata("decode_u256", &input); + + // add a byte + let mut tampered_data = data.clone(); + tampered_data.push(42); + harness.test_call_reverts(&mut executor, tampered_data); + + // remove last 8 bytes + let mut tampered_data = data.clone(); + tampered_data.truncate(data.len() - 8); + harness.test_call_reverts(&mut executor, tampered_data); + } + + // decode_u128_bool + { + let input = [uint_token(99999999), bool_token(true)]; + let data = harness.build_calldata("decode_u128_bool", &input); + + // add a byte + let mut tampered_data = data.clone(); + tampered_data.push(42); + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of `u128` + let mut tampered_data = data.clone(); + // 4 bytes past end of selector (4 + 4) + tampered_data[9] = 26; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of u128 + let mut tampered_data = data; + // 8 bytes past end of u128 (4 + 32 + 8) + tampered_data[44] = 1; + harness.test_call_reverts(&mut executor, tampered_data); + } + + // decode_u256_bytes_tuple_array + { + let head_size = 32 + 32 + 64 + (26 * 32); + let input = [ + uint_token(99999999), + bytes_token( + iter::repeat("ten bytes.") + .take(10) + .collect::() + .as_str(), + ), + tuple_token(&[address_token("a"), uint_token(42)]), + int_array_token(&(-10..16).collect::>()), + ]; + let data = harness.build_calldata("decode_u256_bytes_tuple_array", &input); + + // add a byte + let mut tampered_data = data.clone(); + tampered_data.push(42); + harness.test_call_reverts(&mut executor, tampered_data); + + // remove a byte + let mut tampered_data = data.clone(); + tampered_data.truncate(tampered_data.len() - 1); + harness.test_call_reverts(&mut executor, tampered_data); + + // give invalid length to bytes. it expects 100, we give 99 + let mut tampered_data = data.clone(); + // final byte in data size location for bytes[100] + let byte_index = 4 + head_size + 31; + tampered_data[byte_index] = 99; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of bytes + let mut tampered_data = data.clone(); + // the first byte directly following the bytes' data + let byte_index = 4 + head_size + 32 + 100; + tampered_data[byte_index] = 128; // set the first bit to `1` + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of bytes + let mut tampered_data = data.clone(); + // the first byte directly following the bytes' data + let byte_index = 4 + head_size + 32 + 127; + tampered_data[byte_index] = 1; // set the last bit to `1` + // sanity check + assert_eq!(tampered_data.len(), byte_index + 1); + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the tuple + let mut tampered_data = data.clone(); + // first byte in the address padding + let byte_index = 4 + 32 + 32; + // set the last bit in the address padding to `1` + tampered_data[byte_index] = 128; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the tuple + let mut tampered_data = data.clone(); + // last byte in the address padding + let byte_index = 4 + 32 + 32 + 11; + // set the last bit in the address padding to `1` + tampered_data[byte_index] = 1; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the tuple + let mut tampered_data = data.clone(); + // 5 bytes past the end of address + let byte_index = 4 + 32 + 32 + 32 + 5; + tampered_data[byte_index] = 26; + harness.test_call_reverts(&mut executor, tampered_data); + + // place zero byte in padded region of a negative int + let mut tampered_data = data.clone(); + // index 2 of array and 0 bytes in + let byte_index = 4 + 32 + 32 + 64 + (2 * 32); + tampered_data[byte_index] = 0; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of a positive int + let mut tampered_data = data; + // index 12 of array and 4 bytes in + let byte_index = 4 + 32 + 32 + 64 + (12 * 32 + 4); + tampered_data[byte_index] = 26; + harness.test_call_reverts(&mut executor, tampered_data); + } + + // decode_string_address_bytes_bool + { + let func_name = "decode_string_address_bytes_bool"; + let input = [ + string_token("hello Fe"), + address_token("baddad"), + bytes_token( + iter::repeat("ten bytes.") + .take(100) + .collect::() + .as_str(), + ), + bool_token(true), + ]; + let data = harness.build_calldata(func_name, &input); + + let head_size = 32 + 32 + 32 + 32; + let string_data_size = 64; + let string_size = 8; + let bytes_data_size = 32 + 1024; + let bytes_size = 1000; + + // add 100 bytes + let mut tampered_data = data.clone(); + tampered_data.append(vec![42; 100].as_mut()); + harness.test_call_reverts(&mut executor, tampered_data); + + // remove 20 bytes + let mut tampered_data = data.clone(); + tampered_data.truncate(tampered_data.len() - 20); + harness.test_call_reverts(&mut executor, tampered_data); + + // set string length to value that extends beyond the next data offset + let mut tampered_data = data.clone(); + let byte_index = 4 + head_size + 31; + // data section would now occupy 64 + 32 bytes, instead of 32 + 32 bytes + // this would break the equivalence of string's `data_offset + data_size` and + // the bytes' `data_offset`, making the encoding invalid + tampered_data[byte_index] = 33; + // the string length is completely valid otherwise. 32 for example will not revert + // tampered_data[byte_index] = 32; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the string + let mut tampered_data = data.clone(); + // last byte in string encoding + let byte_index = 4 + head_size + string_data_size - 1; + // set last bit to 1 + tampered_data[byte_index] = 1; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the string + let mut tampered_data = data.clone(); + // first byte in padded section of string encoding + let byte_index = 4 + head_size + 32 + string_size; + // set first bit to 1 + tampered_data[byte_index] = 128; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the bytes + let mut tampered_data = data.clone(); + // last byte in bytes encoding + let byte_index = 4 + head_size + string_data_size + bytes_data_size - 1; + // set last bit to 1 + tampered_data[byte_index] = 1; + harness.test_call_reverts(&mut executor, tampered_data); + + // place non-zero byte in padded region of the bytes + let mut tampered_data = data; + // first byte in padded section of string encoding + let byte_index = 4 + head_size + string_data_size + 32 + bytes_size; + // set first bit to 1 + tampered_data[byte_index] = 128; + harness.test_call_reverts(&mut executor, tampered_data); + + // invalid since bytes has size 990 instead of 1000 + let invalid_input = [ + string_token("hello Fe"), + address_token("baddad"), + bytes_token( + iter::repeat("ten bytes.") + .take(99) + .collect::() + .as_str(), + ), + bool_token(true), + ]; + harness.test_function_reverts(&mut executor, func_name, &invalid_input, &[]); + + // invalid since string has size 100, which is greater than 80 + let invalid_input = [ + string_token( + iter::repeat("hello Fe..") + .take(10) + .collect::() + .as_str(), + ), + address_token("baddad"), + bytes_token( + iter::repeat("ten bytes.") + .take(100) + .collect::() + .as_str(), + ), + bool_token(true), + ]; + harness.test_function_reverts(&mut executor, func_name, &invalid_input, &[]); + } + }); +} diff --git a/crates/tests/src/stress.rs b/crates/tests/src/stress.rs index 3cf49f396f..3a84c1e63c 100644 --- a/crates/tests/src/stress.rs +++ b/crates/tests/src/stress.rs @@ -42,8 +42,8 @@ fn data_copying_stress() { harness.test_function(&mut executor, "emit_my_event", &[], None); - let my_array = u256_array_token(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - let my_mutated_array = u256_array_token(&[1, 2, 3, 5, 5, 6, 7, 8, 9, 10]); + let my_array = uint_array_token(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + let my_mutated_array = uint_array_token(&[1, 2, 3, 5, 5, 6, 7, 8, 9, 10]); let my_addrs = address_array_token(&["0", "1", "2"]); let my_second_addr = address_token("1"); @@ -80,7 +80,7 @@ fn data_copying_stress() { &mut executor, "assign_my_nums_and_return", &[], - Some(&u256_array_token(&[42, 26, 0, 1, 255])), + Some(&uint_array_token(&[42, 26, 0, 1, 255])), ); harness.test_function(&mut executor, "set_my_addrs", &[my_addrs], None); @@ -112,7 +112,7 @@ fn abi_encoding_stress() { let my_addrs = address_array_token(&["a", "b", "c", "d", "e"]); let my_u128 = uint_token(42); let my_string = string_token("my string"); - let my_u16s = u256_array_token(&(0..255).collect::>()); + let my_u16s = uint_array_token(&(0..255).collect::>()); let my_bool = bool_token(true); let my_bytes = bytes_token( iter::repeat("ten bytes.") diff --git a/crates/yulgen/src/mappers/functions.rs b/crates/yulgen/src/mappers/functions.rs index c8f3b1448d..66b1fa1e10 100644 --- a/crates/yulgen/src/mappers/functions.rs +++ b/crates/yulgen/src/mappers/functions.rs @@ -132,7 +132,7 @@ fn revert(context: &mut Context, stmt: &Node) -> yul::Statement { context.revert_errors.insert(val.clone()); let revert_data = expressions::expr(context, error_expr); - let size = abi_operations::encode_size(&[val.clone()], vec![revert_data.clone()]); + let size = abi_operations::encoding_size(&[val.clone()], vec![revert_data.clone()]); let revert_fn = names::revert_name(&val.name, &val.get_field_types()); return statement! { @@ -177,7 +177,7 @@ fn assert(context: &mut Context, stmt: &Node) -> yul::Statement { .expect("missing expression"); if let Type::String(str) = &msg_attributes.typ { - let size = abi_operations::encode_size(&[str.clone()], vec![msg.clone()]); + let size = abi_operations::encoding_size(&[str.clone()], vec![msg.clone()]); let fixed_size = FixedSize::String(str.clone()); context.assert_strings.insert(str.clone()); let revert_fn = names::error_revert_name(&[fixed_size]); diff --git a/crates/yulgen/src/names/abi.rs b/crates/yulgen/src/names/abi.rs index 1e60a2a752..a2403007b2 100644 --- a/crates/yulgen/src/names/abi.rs +++ b/crates/yulgen/src/names/abi.rs @@ -18,18 +18,18 @@ pub fn decode_data(_types: &[T], location: AbiDecodeLocation) -> identifier! { (name) } } -pub fn decode_component(typ: AbiType, location: AbiDecodeLocation) -> yul::Identifier { +pub fn decode_component(typ: &AbiType, location: AbiDecodeLocation) -> yul::Identifier { match typ { AbiType::Address => decode_component_address(location), AbiType::Bool => decode_component_bool(location), - AbiType::Uint { size } => decode_component_uint(size, location), - AbiType::Int { size } => decode_component_int(size, location), + AbiType::Uint { size } => decode_component_uint(*size, location), + AbiType::Int { size } => decode_component_int(*size, location), AbiType::StaticArray { inner, size } => { - decode_component_static_array(&inner, size, location) + decode_component_static_array(inner, *size, location) } - AbiType::Tuple { components: elems } => decode_component_tuple(&elems, location), - AbiType::String { max_size } => decode_component_string(max_size, location), - AbiType::Bytes { size } => decode_component_bytes(size, location), + AbiType::Tuple { components: elems } => decode_component_tuple(elems, location), + AbiType::String { max_size } => decode_component_string(*max_size, location), + AbiType::Bytes { size } => decode_component_bytes(*size, location), } } diff --git a/crates/yulgen/src/operations/abi.rs b/crates/yulgen/src/operations/abi.rs index 19f1c8485f..9b69663489 100644 --- a/crates/yulgen/src/operations/abi.rs +++ b/crates/yulgen/src/operations/abi.rs @@ -4,6 +4,17 @@ use crate::utils::ceil_32; use fe_analyzer::namespace::types::{AbiDecodeLocation, AbiEncoding, AbiType}; use yultsur::*; +/// The size of an encoding known at compile-time. +/// +/// The expressions should only be given literal values. +pub enum EncodingSize { + Exact(yul::Expression), + Bounded { + min: yul::Expression, + max: yul::Expression, + }, +} + /// Returns an expression that encodes the given values and returns a pointer to /// the encoding. pub fn encode(types: &[T], vals: Vec) -> yul::Expression { @@ -12,7 +23,9 @@ pub fn encode(types: &[T], vals: Vec) -> yul::E } /// Returns an expression that gives size of the encoded values. -pub fn encode_size(types: &[T], vals: Vec) -> yul::Expression { +/// +/// It will sum up the sizes known at compile-time with the sizes known during runtime. +pub fn encoding_size(types: &[T], vals: Vec) -> yul::Expression { let mut head_size = 0; let mut known_data_size = 0; let mut unknown_data_size = vec![]; @@ -36,11 +49,43 @@ pub fn encode_size(types: &[T], vals: Vec) -> y expression! { add([static_size], [data_operations::sum(unknown_data_size)]) } } -/// Returns an expression that gives the size of the encoding head. -pub fn encode_head_size(types: &[T]) -> yul::Expression { +/// Returns an expression that gives the size of the encoding's head. +pub fn encoding_head_size(types: &[T]) -> yul::Expression { literal_expression! { (types.iter().map(|typ| typ.abi_type().head_size()).sum::()) } } +/// Returns the known-at-compile-time encoding size. +pub fn encoding_known_size(types: &[T]) -> EncodingSize { + let (min, max) = types.iter().fold((0, 0), |(mut min, mut max), typ| { + min += typ.abi_type().head_size(); + max += typ.abi_type().head_size(); + + match typ.abi_type() { + AbiType::String { max_size } => { + min += 32; + max += ceil_32(max_size) + 32; + } + AbiType::Bytes { size } => { + let size = ceil_32(size) + 32; + min += size; + max += size; + } + _ => {} + } + + (min, max) + }); + + if min == max { + EncodingSize::Exact(literal_expression! { (min) }) + } else { + EncodingSize::Bounded { + min: literal_expression! { (min) }, + max: literal_expression! { (max) }, + } + } +} + /// Decode a segment of memory and return each decoded component as separate values. pub fn decode_data( types: &[T], @@ -54,7 +99,7 @@ pub fn decode_data( /// Decode a single component. pub fn decode_component( - typ: AbiType, + typ: &AbiType, head_start: yul::Expression, offset: yul::Expression, location: AbiDecodeLocation, @@ -63,23 +108,6 @@ pub fn decode_component( expression! { [func_name]([head_start], [offset]) } } -/// Pack each value into a newly allocated segment of memory. -pub fn pack( - ptr: yul::Expression, - array_size: yul::Expression, - inner_data_size: yul::Expression, - location: AbiDecodeLocation, -) -> yul::Expression { - match location { - AbiDecodeLocation::Memory => expression! { - abi_pack_mem([ptr], [array_size], [inner_data_size]) - }, - AbiDecodeLocation::Calldata => expression! { - abi_pack_calldata([ptr], [array_size], [inner_data_size]) - }, - } -} - /// Unpack each value into a newly allocated segment of memory. pub fn unpack( ptr: yul::Expression, @@ -88,3 +116,36 @@ pub fn unpack( ) -> yul::Statement { statement! { abi_unpack([ptr], [array_size], [inner_data_size]) } } + +/// Reverts if the value is not left padded with the given number of bits. +pub fn check_left_padding(size_bits: yul::Expression, val: yul::Expression) -> yul::Statement { + statement! { + if (iszero((is_left_padded([size_bits], [val])))) { + (revert(0, 0)) + } + } +} + +/// Reverts if the value is not right padded with the given number of bits. +pub fn check_right_padding(size_bits: yul::Expression, val: yul::Expression) -> yul::Statement { + statement! { + if (iszero((is_right_padded([size_bits], [val])))) { + (revert(0, 0)) + } + } +} + +/// Reverts if the integer value does not fit within the given number of bytes. +pub fn check_int_size(size: usize, val: yul::Expression) -> yul::Statement { + // the bits to the left of this size should be either all 0s or all 1s + let size_bits = literal_expression! { (size * 8 - 1) }; + let is_all_0s = expression! { iszero((shr([size_bits.clone()], [val.clone()]))) }; + let is_all_1s = expression! { iszero((shr([size_bits], (not([val]))))) }; + let is_all_0s_or_1s = expression! { or([is_all_0s], [is_all_1s]) }; + + statement! { + if (iszero([is_all_0s_or_1s])) { + (revert(0, 0)) + } + } +} diff --git a/crates/yulgen/src/operations/data.rs b/crates/yulgen/src/operations/data.rs index 1147470db7..7b74c1eb04 100644 --- a/crates/yulgen/src/operations/data.rs +++ b/crates/yulgen/src/operations/data.rs @@ -79,7 +79,7 @@ pub fn emit_event(event: EventDef, vals: Vec) -> yul::Statement .unzip(); let encoding = abi_operations::encode(&field_types, field_vals); - let encoding_size = abi_operations::encode_size(&field_types, vals); + let encoding_size = abi_operations::encoding_size(&field_types, vals); // for now we assume these are all base type values and therefore do not need to // be hashed diff --git a/crates/yulgen/src/runtime/abi_dispatcher.rs b/crates/yulgen/src/runtime/abi_dispatcher.rs index c83f28e236..c8fc827abe 100644 --- a/crates/yulgen/src/runtime/abi_dispatcher.rs +++ b/crates/yulgen/src/runtime/abi_dispatcher.rs @@ -52,17 +52,19 @@ fn dispatch_arm(attributes: FunctionAttributes) -> yul::Case { (return(0, 0)) } } else { - let encode = abi_operations::encode( + let encode_expr = abi_operations::encode( &[attributes.return_type.clone()], expressions! { return_val }, ); - let encode_size = - abi_operations::encode_size(&[attributes.return_type], expressions! { return_val }); + let encoding_size = abi_operations::encoding_size( + &[attributes.return_type], + expressions! { return_val }, + ); statements! { (let return_val := [call]) - (let encode_start := [encode]) - (let encode_size := [encode_size]) - (return(encode_start, encode_size)) + (let encoding_start := [encode_expr]) + (let encoding_size := [encoding_size]) + (return(encoding_start, encoding_size)) } } }; diff --git a/crates/yulgen/src/runtime/functions/abi.rs b/crates/yulgen/src/runtime/functions/abi.rs index f006b0cc41..3e6cad432b 100644 --- a/crates/yulgen/src/runtime/functions/abi.rs +++ b/crates/yulgen/src/runtime/functions/abi.rs @@ -1,16 +1,13 @@ use crate::names::abi as abi_names; use crate::operations::abi as abi_operations; +use crate::operations::abi::EncodingSize; use crate::utils::ceil_32; use fe_analyzer::namespace::types::{AbiDecodeLocation, AbiEncoding, AbiType}; use yultsur::*; /// Return all abi runtime functions pub fn all() -> Vec { - vec![ - unpack(), - pack(AbiDecodeLocation::Calldata), - pack(AbiDecodeLocation::Memory), - ] + vec![unpack(), is_left_padded(), is_right_padded()] } /// Creates a batch of encoding function for the given type arrays. @@ -45,7 +42,20 @@ pub fn batch_decode( .collect(); let component_functions: Vec<_> = component_batch .into_iter() - .map(|(typ, location)| decode_component(&typ, location)) + .map(|(typ, location)| { + let top_function = decode_component(&typ.abi_type(), location); + let inner_functions = match &typ.abi_type() { + AbiType::Tuple { components } => components + .iter() + .map(|typ| decode_component(typ, location)) + .collect(), + AbiType::StaticArray { inner, .. } => vec![decode_component(inner, location)], + _ => vec![], + }; + + [vec![top_function], inner_functions].concat() + }) + .flatten() .collect(); let mut yul_functions: Vec<_> = [data_functions, component_functions].concat(); @@ -56,88 +66,195 @@ pub fn batch_decode( /// Creates a function that decodes ABI encoded data. pub fn decode_data(types: &[T], location: AbiDecodeLocation) -> yul::Statement { + #[derive(Clone)] + struct IdentExpr { + ident: yul::Identifier, + expr: yul::Expression, + } + + impl From for IdentExpr { + fn from(string: String) -> Self { + IdentExpr { + ident: identifier! { (string) }, + expr: identifier_expression! { (string) }, + } + } + } + + #[derive(Clone)] + struct DataOffsets { + start: IdentExpr, + end: IdentExpr, + } + + #[derive(Clone)] + struct DecodeVal { + typ: AbiType, + return_val: IdentExpr, + decoded_val: IdentExpr, + head_offset: IdentExpr, + data_offsets: Option, + } + let func_name = abi_names::decode_data(&types, location); - // Create corresponding "val_" and "offset_" names and associate each one - // with a component type. - let typed_names: Vec<(&T, String, String)> = types + let vals: Vec = types .iter() .enumerate() .map(|(i, typ)| { - let val = format!("val_{}", i); - let offset = format!("offset_{}", i); - (typ, val, offset) + let typ = typ.abi_type(); + let data_offsets = if typ.has_data() { + Some(DataOffsets { + start: format!("data_start_offset_{}", i).into(), + end: format!("data_end_offset_{}", i).into(), + }) + } else { + None + }; + + DecodeVal { + typ, + return_val: format!("return_val_{}", i).into(), + decoded_val: format!("decoded_val_{}", i).into(), + head_offset: format!("head_offset_{}", i).into(), + data_offsets, + } }) .collect(); - // Map the return val names to identifiers. - let return_vals: Vec<_> = typed_names - .iter() - .map(|(_, val_name, _)| identifier! { (val_name) }) + let size_check = match abi_operations::encoding_known_size(types) { + EncodingSize::Exact(size) => statements! { + (let encoding_size := sub(data_end, head_start)) + (if (iszero((eq(encoding_size, [size])))) { + (revert(0, 0)) + }) + }, + EncodingSize::Bounded { min, max } => statements! { + (let encoding_size := sub(data_end, head_start)) + (if (or( + (lt(encoding_size, [min])), + (gt(encoding_size, [max])) + )) { + (revert(0, 0)) + }) + }, + }; + + let return_val_idents: Vec<_> = vals + .clone() + .into_iter() + .map(|val| val.return_val.ident) .collect(); // Create declaration statements for each offset. - let offset_decls: Vec<_> = { + let head_offset_decls: Vec<_> = { let mut curr_offset = 0; - typed_names - .iter() - .map(|(typ, _, offset_name)| { - let offset_ident = identifier! { (offset_name) }; - let offset = literal_expression! { (curr_offset) }; - let offset_decl = statement! { let [offset_ident] := [offset] }; - curr_offset += typ.abi_type().head_size(); - offset_decl + vals.clone() + .into_iter() + .map(|val| { + let head_offset = literal_expression! { (curr_offset) }; + let head_offset_decl = statement! { let [val.head_offset.ident] := [head_offset] }; + curr_offset += val.typ.head_size(); + head_offset_decl }) .collect() }; // Call the component decoding functions for each offset value. - let decode_stmts: Vec<_> = typed_names - .iter() - .map(|(typ, val_name, offset_name)| { - let val_ident = identifier! { (val_name) }; + let decode_stmts: Vec<_> = vals + .clone() + .into_iter() + .map(|val| { + let target_idents = if let Some(data_offsets) = val.data_offsets { + identifiers! { + [val.decoded_val.ident] + [data_offsets.start.ident] + [data_offsets.end.ident] + } + } else { + identifiers! { [val.decoded_val.ident] } + }; + let decode_expr = abi_operations::decode_component( - typ.abi_type(), + &val.typ, expression! { head_start }, - identifier_expression! { (offset_name) }, + val.head_offset.expr, location, ); - statement! { [val_ident] := [decode_expr] } + statement! { let [target_idents...] := [decode_expr] } }) .collect(); + let encoding_head_size = abi_operations::encoding_head_size(types); + let data_offset_checks: Vec<_> = { + let (mut start_offset_exprs, mut end_offset_exprs): (Vec<_>, Vec<_>) = vals + .clone() + .into_iter() + .filter_map(|val| { + val.data_offsets + .map(|data_offsets| (data_offsets.start.expr, data_offsets.end.expr)) + }) + .unzip(); + + start_offset_exprs.push(expression! { encoding_size }); + end_offset_exprs.insert(0, encoding_head_size); + + start_offset_exprs + .into_iter() + .zip(end_offset_exprs) + .map(|(start_offset, end_offset)| { + statement! { + if (iszero((eq([start_offset], [end_offset])))) { (revert(0, 0)) } + } + }) + .collect() + }; + + let return_assignments: Vec<_> = vals + .into_iter() + .map(|val| statement! { [val.return_val.ident] := [val.decoded_val.expr] }) + .collect(); + function_definition! { - function [func_name](head_start, data_end) -> [return_vals...] { - [offset_decls...] + function [func_name](head_start, data_end) -> [return_val_idents...] { + [size_check...] + [head_offset_decls...] [decode_stmts...] + [data_offset_checks...] + [return_assignments...] } } } /// Creates a function that decodes a single component in ABI encoded data. -pub fn decode_component(typ: &T, location: AbiDecodeLocation) -> yul::Statement { - match typ.abi_type() { +pub fn decode_component(typ: &AbiType, location: AbiDecodeLocation) -> yul::Statement { + match typ { AbiType::StaticArray { inner, size } => { - decode_component_static_array(*inner, size, location) + decode_component_static_array(inner, *size, location) } AbiType::Tuple { components: elems } => decode_component_tuple(&elems, location), - AbiType::Uint { size } => decode_component_uint(size, location), - AbiType::Int { size } => decode_component_int(size, location), + AbiType::Uint { size } => decode_component_uint(*size, location), + AbiType::Int { size } => decode_component_int(*size, location), AbiType::Bool => decode_component_bool(location), AbiType::Address => decode_component_address(location), - AbiType::String { max_size } => decode_component_string(max_size, location), - AbiType::Bytes { size } => decode_component_bytes(size, location), + AbiType::String { max_size } => decode_component_string(*max_size, location), + AbiType::Bytes { size } => decode_component_bytes(*size, location), } } pub fn decode_component_uint(size: usize, location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_uint(size, location); let decode_expr = load_word(expression! { ptr }, location); + let check_padding = abi_operations::check_left_padding( + literal_expression! { ((32 - size) * 8) }, + expression! { return_val }, + ); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) (return_val := [decode_expr]) + [check_padding] } } } @@ -145,11 +262,13 @@ pub fn decode_component_uint(size: usize, location: AbiDecodeLocation) -> yul::S pub fn decode_component_int(size: usize, location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_int(size, location); let decode_expr = load_word(expression! { ptr }, location); + let check_size = abi_operations::check_int_size(size, expression! { return_val }); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) (return_val := [decode_expr]) + [check_size] } } } @@ -157,11 +276,14 @@ pub fn decode_component_int(size: usize, location: AbiDecodeLocation) -> yul::St pub fn decode_component_bool(location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_bool(location); let decode_expr = load_word(expression! { ptr }, location); + let check_padding = + abi_operations::check_left_padding(expression! { 255 }, expression! { return_val }); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) (return_val := [decode_expr]) + [check_padding] } } } @@ -169,68 +291,103 @@ pub fn decode_component_bool(location: AbiDecodeLocation) -> yul::Statement { pub fn decode_component_address(location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_address(location); let decode_expr = load_word(expression! { ptr }, location); + let check_padding = + abi_operations::check_left_padding(expression! { 96 }, expression! { return_val }); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) (return_val := [decode_expr]) + [check_padding] } } } pub fn decode_component_static_array( - inner: AbiType, + inner: &AbiType, array_size: usize, location: AbiDecodeLocation, ) -> yul::Statement { - let func_name = abi_names::decode_component_static_array(&inner, array_size, location); - - let decode_expr = match inner.packed_size() { - 32 => { - let data_size = literal_expression! { (32 * array_size) }; - copy_data(expression! { ptr }, data_size, location) - } - inner_size => abi_operations::pack( - expression! { ptr }, - literal_expression! { (array_size) }, - literal_expression! { (inner_size) }, - location, - ), - }; + let func_name = abi_names::decode_component_static_array(inner, array_size, location); + let array_size = literal_expression! { (array_size) }; + let inner_packed_size = literal_expression! { (inner.packed_size()) }; + let decode_inner_expr = abi_operations::decode_component( + &inner, + expression! { head_start }, + expression! { inner_offset }, + location, + ); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) - (return_val := [decode_expr]) + (return_val := avail()) + (for {(let i := 0)} (lt(i, [array_size])) {(i := add(i, 1))} + { + (let inner_offset := add(offset, (mul(i, 32)))) + (let decoded_val := [decode_inner_expr]) + (pop((alloc_mstoren(decoded_val, [inner_packed_size])))) + }) } } } pub fn decode_component_tuple(elems: &[AbiType], location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_tuple(elems, location); - let tuple_size = literal_expression! { (elems.len() * 32) }; - let decode_expr = copy_data(expression! { ptr }, tuple_size, location); + let decode_stmts: Vec<_> = elems + .iter() + .enumerate() + .map(|(index, component)| { + let decode_component_expr = abi_operations::decode_component( + &component, + expression! { head_start }, + expression! { component_offset }, + location, + ); + let index = literal_expression! { (index) }; + + block_statement! { + (let component_offset := add(offset, (mul(32, [index])))) + (let decoded_val := [decode_component_expr]) + (pop((alloc_mstoren(decoded_val, 32)))) + } + }) + .collect(); function_definition! { function [func_name](head_start, offset) -> return_val { (let ptr := add(head_start, offset)) - (return_val := [decode_expr]) + (return_val := avail()) + [decode_stmts...] } } } pub fn decode_component_bytes(size: usize, location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_bytes(size, location); + let size = literal_expression! { (size) }; function_definition! { - function [func_name](head_start, head_offset) -> return_val { + function [func_name](head_start, head_offset) -> return_val, data_start_offset, data_end_offset { (let head_ptr := add(head_start, head_offset)) - (let data_start_offset := [load_word(expression! { head_ptr }, location)]) - (let data_start := add(32, (add(head_start, data_start_offset)))) - (let data_size := [literal_expression! { (size) }]) + (data_start_offset := [load_word(expression! { head_ptr }, location)]) + (let data_start := add(head_start, data_start_offset)) + (let bytes_size := [load_word(expression! { data_start }, location)]) + (if (iszero((eq(bytes_size, [size])))) { (revert(0, 0)) } ) + (let data_size := add(bytes_size, 32)) + (let padded_data_size := ceil32(data_size)) + (data_end_offset := add(data_start_offset, padded_data_size)) + (let end_word := [load_word(expression! { sub((add(head_start, data_end_offset)), 32) }, location)]) + (let padding_size_bits := mul((sub(padded_data_size, data_size)), 8)) + [abi_operations::check_right_padding( + expression! { padding_size_bits }, + expression! { end_word } + )] (return_val := [copy_data( - expression! { data_start }, - expression! { data_size }, + // We do not copy the dynamic size value like we do with strings, so we add 32 bytes + // to the start and subtract 32 bytes from the size being copied. + expression! { add(data_start, 32) }, + expression! { sub(data_size, 32) }, location )]) } @@ -239,13 +396,24 @@ pub fn decode_component_bytes(size: usize, location: AbiDecodeLocation) -> yul:: pub fn decode_component_string(max_size: usize, location: AbiDecodeLocation) -> yul::Statement { let func_name = abi_names::decode_component_string(max_size, location); + let max_size = literal_expression! { (max_size) }; function_definition! { - function [func_name](head_start, head_offset) -> return_val { + function [func_name](head_start, head_offset) -> return_val, data_start_offset, data_end_offset { (let head_ptr := add(head_start, head_offset)) - (let data_start_offset := [load_word(expression! { head_ptr }, location)]) + (data_start_offset := [load_word(expression! { head_ptr }, location)]) (let data_start := add(head_start, data_start_offset)) - (let data_size := add(32, [load_word(expression! { data_start }, location)])) + (let string_size := [load_word(expression! { data_start }, location)]) + (if (gt(string_size, [max_size])) { (revert(0, 0)) }) + (let data_size := add(string_size, 32)) + (let padded_data_size := ceil32(data_size)) + (data_end_offset := add(data_start_offset, padded_data_size)) + (let end_word := [load_word(expression! { sub((add(head_start, data_end_offset)), 32) }, location)]) + (let padding_size_bits := mul((sub(padded_data_size, data_size)), 8)) + [abi_operations::check_right_padding( + expression! { padding_size_bits }, + expression! { end_word } + )] (return_val := [copy_data( expression! { data_start }, expression! { data_size }, @@ -255,6 +423,31 @@ pub fn decode_component_string(max_size: usize, location: AbiDecodeLocation) -> } } +/// Returns 0 if the value is not padded on the left with zeros. +/// +/// `size_bits` refers to the size of the padding in bits. +pub fn is_left_padded() -> yul::Statement { + function_definition! { + function is_left_padded(size_bits, val) -> return_val { + (let bits_shifted := sub(256, size_bits)) + (let shifted_val := shr(bits_shifted, val)) + (return_val := iszero(shifted_val)) + } + } +} +/// Returns 0 if the value is not padded on the right with zeros. +/// +/// `size_bits` refers to the size of the padding in bits. +pub fn is_right_padded() -> yul::Statement { + function_definition! { + function is_right_padded(size_bits, val) -> return_val { + (let bits_shifted := sub(256, size_bits)) + (let shifted_val := shl(bits_shifted, val)) + (return_val := iszero(shifted_val)) + } + } +} + fn load_word(ptr: yul::Expression, location: AbiDecodeLocation) -> yul::Expression { match location { AbiDecodeLocation::Memory => expression! { mload([ptr]) }, @@ -287,32 +480,6 @@ pub fn unpack() -> yul::Statement { } } -/// Removes padding from array elements so that they may be stored more -/// efficiently. -pub fn pack(location: AbiDecodeLocation) -> yul::Statement { - let name = match location { - AbiDecodeLocation::Calldata => identifier! { abi_pack_calldata }, - AbiDecodeLocation::Memory => identifier! { abi_pack_mem }, - }; - let load = match location { - AbiDecodeLocation::Calldata => identifier! { calldataload }, - AbiDecodeLocation::Memory => identifier! { mload }, - }; - - function_definition! { - function [name](mptr, array_size, inner_data_size) -> packed_ptr { - (packed_ptr := avail()) - - (for {(let i := 0)} (lt(i, array_size)) {(i := add(i, 1))} - { - (let val_ptr := add(mptr, (mul(i, 32)))) - (let val := [load](val_ptr)) - (pop((alloc_mstoren(val, inner_data_size)))) - }) - } - } -} - /// Generates an encoding function for any set of type parameters. pub fn encode(types: Vec) -> yul::Statement { let func_name = abi_names::encode(&types); @@ -352,7 +519,7 @@ pub fn encode(types: Vec) -> yul::Statement { // Set the return to the available memory address. (return_ptr := avail()) // The data section begins at the end of the head. - (let data_offset := [abi_operations::encode_head_size(&types)]) + (let data_offset := [abi_operations::encoding_head_size(&types)]) [head_encode_stmts...] [data_encode_stmts...] } diff --git a/crates/yulgen/src/runtime/functions/contracts.rs b/crates/yulgen/src/runtime/functions/contracts.rs index 17a8f1d4d9..896fa3af2e 100644 --- a/crates/yulgen/src/runtime/functions/contracts.rs +++ b/crates/yulgen/src/runtime/functions/contracts.rs @@ -38,7 +38,7 @@ pub fn calls(contract: Contract) -> Vec { let encoding_operation = abi_operations::encode(&function.param_types(), param_exprs.clone()); // the size of the encoded data - let encoding_size = abi_operations::encode_size(&function.param_types(), param_exprs); + let encoding_size = abi_operations::encoding_size(&function.param_types(), param_exprs); if function.return_type.is_unit() { // there is no return data to handle @@ -53,8 +53,8 @@ pub fn calls(contract: Contract) -> Vec { } else { let decoding_operation = abi_operations::decode_data( &[function.return_type], - identifier_expression! { outstart }, - identifier_expression! { outstart }, + expression! { outstart }, + expression! { add(outstart, outsize) }, AbiDecodeLocation::Memory, ); // return data must be captured and decoded diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_address_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_address_mem_function.snap index 9d2ea39493..657a5ccdac 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_address_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_address_mem_function.snap @@ -6,4 +6,5 @@ expression: "abi_functions::decode_component_bool(AbiDecodeLocation::Memory)" function abi_decode_component_bool_mem(head_start, offset) -> return_val { let ptr := add(head_start, offset) return_val := mload(ptr) + if iszero(is_left_padded(255, return_val)) { revert(0, 0) } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bool_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bool_calldata_function.snap index d8d34768d1..f82d06ada9 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bool_calldata_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bool_calldata_function.snap @@ -6,4 +6,5 @@ expression: "abi_functions::decode_component_bool(AbiDecodeLocation::Calldata)" function abi_decode_component_bool_calldata(head_start, offset) -> return_val { let ptr := add(head_start, offset) return_val := calldataload(ptr) + if iszero(is_left_padded(255, return_val)) { revert(0, 0) } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bytes_26_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bytes_26_mem_function.snap index a2d546b958..d51ffce51e 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bytes_26_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_bytes_26_mem_function.snap @@ -3,10 +3,17 @@ source: crates/yulgen/tests/yulgen.rs expression: "abi_functions::decode_component_bytes(26, AbiDecodeLocation::Memory)" --- -function abi_decode_component_bytes_26_mem(head_start, head_offset) -> return_val { +function abi_decode_component_bytes_26_mem(head_start, head_offset) -> return_val, data_start_offset, data_end_offset { let head_ptr := add(head_start, head_offset) - let data_start_offset := mload(head_ptr) - let data_start := add(32, add(head_start, data_start_offset)) - let data_size := 26 - return_val := mcopym(data_start, data_size) + data_start_offset := mload(head_ptr) + let data_start := add(head_start, data_start_offset) + let bytes_size := mload(data_start) + if iszero(eq(bytes_size, 26)) { revert(0, 0) } + let data_size := add(bytes_size, 32) + let padded_data_size := ceil32(data_size) + data_end_offset := add(data_start_offset, padded_data_size) + let end_word := mload(sub(add(head_start, data_end_offset), 32)) + let padding_size_bits := mul(sub(padded_data_size, data_size), 8) + if iszero(is_right_padded(padding_size_bits, end_word)) { revert(0, 0) } + return_val := mcopym(add(data_start, 32), sub(data_size, 32)) } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_int16_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_int16_calldata_function.snap index 44f330fb89..667a5f8065 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_int16_calldata_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_int16_calldata_function.snap @@ -1,9 +1,10 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::decode_component_int(16, AbiDecodeLocation::Calldata)" +expression: "abi_functions::decode_component_int(2, AbiDecodeLocation::Calldata)" --- -function abi_decode_component_int128_calldata(head_start, offset) -> return_val { +function abi_decode_component_int16_calldata(head_start, offset) -> return_val { let ptr := add(head_start, offset) return_val := calldataload(ptr) + if iszero(or(iszero(shr(15, return_val)), iszero(shr(15, not(return_val))))) { revert(0, 0) } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_static_array_address_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_static_array_address_calldata_function.snap index f48a1e3c71..c1640d12e5 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_static_array_address_calldata_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_static_array_address_calldata_function.snap @@ -1,9 +1,14 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::decode_component_static_array(AbiType::Address, 42,\n AbiDecodeLocation::Calldata)" +expression: "abi_functions::decode_component_static_array(&AbiType::Address, 42,\n AbiDecodeLocation::Calldata)" --- function abi_decode_component_static_array_42_address_calldata(head_start, offset) -> return_val { let ptr := add(head_start, offset) - return_val := ccopym(ptr, 1344) + return_val := avail() + for { let i := 0 } lt(i, 42) { i := add(i, 1) } { + let inner_offset := add(offset, mul(i, 32)) + let decoded_val := abi_decode_component_address_calldata(head_start, inner_offset) + pop(alloc_mstoren(decoded_val, 32)) + } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_string_26_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_string_26_calldata_function.snap index 8f5f84e87b..db07a3c9c6 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_string_26_calldata_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_string_26_calldata_function.snap @@ -3,10 +3,17 @@ source: crates/yulgen/tests/yulgen.rs expression: "abi_functions::decode_component_bytes(26, AbiDecodeLocation::Calldata)" --- -function abi_decode_component_bytes_26_calldata(head_start, head_offset) -> return_val { +function abi_decode_component_bytes_26_calldata(head_start, head_offset) -> return_val, data_start_offset, data_end_offset { let head_ptr := add(head_start, head_offset) - let data_start_offset := calldataload(head_ptr) - let data_start := add(32, add(head_start, data_start_offset)) - let data_size := 26 - return_val := ccopym(data_start, data_size) + data_start_offset := calldataload(head_ptr) + let data_start := add(head_start, data_start_offset) + let bytes_size := calldataload(data_start) + if iszero(eq(bytes_size, 26)) { revert(0, 0) } + let data_size := add(bytes_size, 32) + let padded_data_size := ceil32(data_size) + data_end_offset := add(data_start_offset, padded_data_size) + let end_word := calldataload(sub(add(head_start, data_end_offset), 32)) + let padding_size_bits := mul(sub(padded_data_size, data_size), 8) + if iszero(is_right_padded(padding_size_bits, end_word)) { revert(0, 0) } + return_val := ccopym(add(data_start, 32), sub(data_size, 32)) } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_tuple_u256_address_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_tuple_u256_address_mem_function.snap index ff581645ae..189ba05bfa 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_tuple_u256_address_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_tuple_u256_address_mem_function.snap @@ -5,5 +5,15 @@ expression: "abi_functions::decode_component_tuple(&[AbiType::Uint{size: 32,},\n --- function abi_decode_component_tuple_uint256_address_mem(head_start, offset) -> return_val { let ptr := add(head_start, offset) - return_val := mcopym(ptr, 64) + return_val := avail() + { + let component_offset := add(offset, mul(32, 0)) + let decoded_val := abi_decode_component_uint256_mem(head_start, component_offset) + pop(alloc_mstoren(decoded_val, 32)) + } + { + let component_offset := add(offset, mul(32, 1)) + let decoded_val := abi_decode_component_address_mem(head_start, component_offset) + pop(alloc_mstoren(decoded_val, 32)) + } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_uint256_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_uint256_mem_function.snap index 3683d7e555..bcc44f712a 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_uint256_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_component_uint256_mem_function.snap @@ -1,9 +1,10 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::decode_component_uint(256, AbiDecodeLocation::Memory)" +expression: "abi_functions::decode_component_uint(32, AbiDecodeLocation::Memory)" --- -function abi_decode_component_uint2048_mem(head_start, offset) -> return_val { +function abi_decode_component_uint256_mem(head_start, offset) -> return_val { let ptr := add(head_start, offset) return_val := mload(ptr) + if iszero(is_left_padded(0, return_val)) { revert(0, 0) } } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap index d94af75c7c..97b343bc20 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_address_bool_mem_function.snap @@ -3,9 +3,14 @@ source: crates/yulgen/tests/yulgen.rs expression: "abi_functions::decode_data(&[Base::Bool, Base::Address],\n AbiDecodeLocation::Memory)" --- -function abi_decode_data_bool_address_mem(head_start, data_end) -> val_0, val_1 { - let offset_0 := 0 - let offset_1 := 32 - val_0 := abi_decode_component_bool_mem(head_start, offset_0) - val_1 := abi_decode_component_address_mem(head_start, offset_1) +function abi_decode_data_bool_address_mem(head_start, data_end) -> return_val_0, return_val_1 { + let encoding_size := sub(data_end, head_start) + if iszero(eq(encoding_size, 64)) { revert(0, 0) } + let head_offset_0 := 0 + let head_offset_1 := 32 + let decoded_val_0 := abi_decode_component_bool_mem(head_start, head_offset_0) + let decoded_val_1 := abi_decode_component_address_mem(head_start, head_offset_1) + if iszero(eq(encoding_size, 64)) { revert(0, 0) } + return_val_0 := decoded_val_0 + return_val_1 := decoded_val_1 } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap new file mode 100644 index 0000000000..08a423915f --- /dev/null +++ b/crates/yulgen/tests/snapshots/yulgen__abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function.snap @@ -0,0 +1,31 @@ +--- +source: crates/yulgen/tests/yulgen.rs +expression: "abi_functions::decode_data(&[FixedSize::u256(),\n FixedSize::Array(Array{inner:\n Base::Numeric(Integer::U8),\n size: 100,}),\n FixedSize::String(FeString{max_size: 42,}),\n FixedSize::bool(), FixedSize::address(),\n FixedSize::Array(Array{inner:\n Base::Numeric(Integer::U8),\n size: 100,})],\n AbiDecodeLocation::Calldata)" + +--- +function abi_decode_data_uint256_bytes_100_string_42_bool_address_bytes_100_calldata(head_start, data_end) -> return_val_0, return_val_1, return_val_2, return_val_3, return_val_4, return_val_5 { + let encoding_size := sub(data_end, head_start) + if or(lt(encoding_size, 544), gt(encoding_size, 608)) { revert(0, 0) } + let head_offset_0 := 0 + let head_offset_1 := 32 + let head_offset_2 := 64 + let head_offset_3 := 96 + let head_offset_4 := 128 + let head_offset_5 := 160 + let decoded_val_0 := abi_decode_component_uint256_calldata(head_start, head_offset_0) + let decoded_val_1, data_start_offset_1, data_end_offset_1 := abi_decode_component_bytes_100_calldata(head_start, head_offset_1) + let decoded_val_2, data_start_offset_2, data_end_offset_2 := abi_decode_component_string_42_calldata(head_start, head_offset_2) + let decoded_val_3 := abi_decode_component_bool_calldata(head_start, head_offset_3) + let decoded_val_4 := abi_decode_component_address_calldata(head_start, head_offset_4) + let decoded_val_5, data_start_offset_5, data_end_offset_5 := abi_decode_component_bytes_100_calldata(head_start, head_offset_5) + if iszero(eq(data_start_offset_1, 192)) { revert(0, 0) } + if iszero(eq(data_start_offset_2, data_end_offset_1)) { revert(0, 0) } + if iszero(eq(data_start_offset_5, data_end_offset_2)) { revert(0, 0) } + if iszero(eq(encoding_size, data_end_offset_5)) { revert(0, 0) } + return_val_0 := decoded_val_0 + return_val_1 := decoded_val_1 + return_val_2 := decoded_val_2 + return_val_3 := decoded_val_3 + return_val_4 := decoded_val_4 + return_val_5 := decoded_val_5 +} diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap b/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap index bccad28393..d1399b8f91 100644 --- a/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap +++ b/crates/yulgen/tests/snapshots/yulgen__abi_dispatcher.snap @@ -6,15 +6,15 @@ expression: "abi_dispatcher::dispatcher(&function_attributes())" switch cloadn(0, 4) case 0x9476f922 { let return_val := $$hello_world() - let encode_start := abi_encode_string_42(return_val) - let encode_size := add(64, ceil32(mload(return_val))) - return(encode_start, encode_size) + let encoding_start := abi_encode_string_42(return_val) + let encoding_size := add(64, ceil32(mload(return_val))) + return(encoding_start, encoding_size) } case 0x771602f7 { let call_val_0, call_val_1 := abi_decode_data_uint256_uint256_calldata(4, calldatasize()) let return_val := $$add(call_val_0, call_val_1) - let encode_start := abi_encode_uint256(return_val) - let encode_size := add(32, 0) - return(encode_start, encode_size) + let encoding_start := abi_encode_uint256(return_val) + let encoding_size := add(32, 0) + return(encoding_start, encoding_size) } default { return(0, 0) } diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_pack_calldata_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_pack_calldata_function.snap deleted file mode 100644 index 2c6ab08d8e..0000000000 --- a/crates/yulgen/tests/snapshots/yulgen__abi_pack_calldata_function.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::pack(AbiDecodeLocation::Calldata)" - ---- -function abi_pack_calldata(mptr, array_size, inner_data_size) -> packed_ptr { - packed_ptr := avail() - for { let i := 0 } lt(i, array_size) { i := add(i, 1) } { - let val_ptr := add(mptr, mul(i, 32)) - let val := calldataload(val_ptr) - pop(alloc_mstoren(val, inner_data_size)) - } -} diff --git a/crates/yulgen/tests/snapshots/yulgen__abi_pack_mem_function.snap b/crates/yulgen/tests/snapshots/yulgen__abi_pack_mem_function.snap deleted file mode 100644 index c8b1ff0ba2..0000000000 --- a/crates/yulgen/tests/snapshots/yulgen__abi_pack_mem_function.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: crates/yulgen/tests/yulgen.rs -expression: "abi_functions::pack(AbiDecodeLocation::Memory)" - ---- -function abi_pack_mem(mptr, array_size, inner_data_size) -> packed_ptr { - packed_ptr := avail() - for { let i := 0 } lt(i, array_size) { i := add(i, 1) } { - let val_ptr := add(mptr, mul(i, 32)) - let val := mload(val_ptr) - pop(alloc_mstoren(val, inner_data_size)) - } -} diff --git a/crates/yulgen/tests/snapshots/yulgen__encode_size_u256_operation.snap b/crates/yulgen/tests/snapshots/yulgen__encode_size_u256_operation.snap index 3e38af9cc6..c29db40973 100644 --- a/crates/yulgen/tests/snapshots/yulgen__encode_size_u256_operation.snap +++ b/crates/yulgen/tests/snapshots/yulgen__encode_size_u256_operation.snap @@ -1,6 +1,6 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "abi_operations::encode_size(&[U256], vec![expression ! { 42 }])" +expression: "abi_operations::encoding_size(&[U256], vec![expression ! { 42 }])" --- add(32, 0) diff --git a/crates/yulgen/tests/snapshots/yulgen__revert_string_error.snap b/crates/yulgen/tests/snapshots/yulgen__revert_string_error.snap index 09e86fc08a..b1c68f0ec1 100644 --- a/crates/yulgen/tests/snapshots/yulgen__revert_string_error.snap +++ b/crates/yulgen/tests/snapshots/yulgen__revert_string_error.snap @@ -1,6 +1,6 @@ --- source: crates/yulgen/tests/yulgen.rs -expression: "revert::generate_revert_fn_for_assert(&[FeString{max_size: 3,}.into()])" +expression: "revert_functions::generate_revert_fn_for_assert(&[FeString{max_size:\n 3,}.into()])" --- function revert_with_0x08c379a0_string_3(data_ptr, size) { diff --git a/crates/yulgen/tests/yulgen.rs b/crates/yulgen/tests/yulgen.rs index b9e1e2956e..0210139b2a 100644 --- a/crates/yulgen/tests/yulgen.rs +++ b/crates/yulgen/tests/yulgen.rs @@ -74,13 +74,24 @@ test_yulgen! { abi_decode_data_address_bool_mem_function, abi_functions::decode_data(&[Base::Bool, Base::Address], AbiDecodeLocation::Memory) } +test_yulgen! { + abi_decode_data_u256_bytes_string_bool_address_bytes_calldata_function, + abi_functions::decode_data(&[ + FixedSize::u256(), + FixedSize::Array(Array { inner: Base::Numeric(Integer::U8), size: 100 }), + FixedSize::String(FeString { max_size: 42 }), + FixedSize::bool(), + FixedSize::address(), + FixedSize::Array(Array { inner: Base::Numeric(Integer::U8), size: 100 }), + ], AbiDecodeLocation::Calldata) +} test_yulgen! { abi_decode_component_uint256_mem_function, - abi_functions::decode_component_uint(256, AbiDecodeLocation::Memory) + abi_functions::decode_component_uint(32, AbiDecodeLocation::Memory) } test_yulgen! { abi_decode_component_int16_calldata_function, - abi_functions::decode_component_int(16, AbiDecodeLocation::Calldata) + abi_functions::decode_component_int(2, AbiDecodeLocation::Calldata) } test_yulgen! { abi_decode_component_bool_calldata_function, @@ -92,15 +103,7 @@ test_yulgen! { } test_yulgen! { abi_decode_component_static_array_address_calldata_function, - abi_functions::decode_component_static_array(AbiType::Address, 42, AbiDecodeLocation::Calldata) -} -test_yulgen! { - abi_pack_calldata_function, - abi_functions::pack(AbiDecodeLocation::Calldata) -} -test_yulgen! { - abi_pack_mem_function, - abi_functions::pack(AbiDecodeLocation::Memory) + abi_functions::decode_component_static_array(&AbiType::Address, 42, AbiDecodeLocation::Calldata) } test_yulgen! { abi_decode_component_tuple_u256_address_mem_function, @@ -186,7 +189,7 @@ test_yulgen! { } test_yulgen! { encode_size_u256_operation, - abi_operations::encode_size(&[U256], vec![expression! { 42 }]) + abi_operations::encoding_size(&[U256], vec![expression! { 42 }]) } test_yulgen! { decode_string_calldata_operation, diff --git a/newsfragments/440.feature.md b/newsfragments/440.feature.md new file mode 100644 index 0000000000..12f9858743 --- /dev/null +++ b/newsfragments/440.feature.md @@ -0,0 +1,10 @@ +The following checks are now performed while decoding data: + +- The size of the encoded data fits within the size range known at compile-time. +- Values are correctly padded. + - unsigned integers, addresses, and bools are checked to have correct left zero padding + - the size of signed integers are checked + - bytes and strings are checked to have correct right padding +- Data section offsets are consistent with the size of preceding values in the data section. +- The dynamic size of strings does not exceed their maximum size. +- The dynamic size of byte arrays (`u8[n]`) is equal to the size of the array.