From 62322527a2ab3a0c699cdd005bc485de2d12484d Mon Sep 17 00:00:00 2001 From: Grant Wuerker Date: Mon, 15 Feb 2021 17:54:58 -0700 Subject: [PATCH] Storage operations use correct addresses. --- analyzer/src/namespace/events.rs | 4 +- analyzer/src/namespace/types.rs | 8 +- common/src/utils/keccak.rs | 32 ++- compiler/src/abi/utils.rs | 4 +- compiler/src/yul/mappers/assignments.rs | 2 +- compiler/src/yul/mappers/contracts.rs | 4 +- compiler/src/yul/mappers/declarations.rs | 2 +- compiler/src/yul/mappers/expressions.rs | 17 +- compiler/src/yul/operations/data.rs | 42 ++-- compiler/src/yul/runtime/functions/data.rs | 215 ++++++++++++------ compiler/src/yul/runtime/functions/mod.rs | 5 +- compiler/tests/evm_contracts.rs | 21 +- compiler/tests/evm_runtime.rs | 166 +++++++++++--- .../tests/fixtures/data_copying_stress.fe | 8 + newsfragments/251.internal.md | 1 + 15 files changed, 377 insertions(+), 154 deletions(-) create mode 100644 newsfragments/251.internal.md diff --git a/analyzer/src/namespace/events.rs b/analyzer/src/namespace/events.rs index 668bdc910b..d99e7b9211 100644 --- a/analyzer/src/namespace/events.rs +++ b/analyzer/src/namespace/events.rs @@ -2,7 +2,7 @@ use crate::namespace::types::{ AbiEncoding, FixedSize, }; -use fe_common::utils::keccak::get_full_signature; +use fe_common::utils::keccak; #[derive(Clone, Debug, PartialEq)] pub struct Event { @@ -64,7 +64,7 @@ impl Event { fn build_event_topic(name: &str, fields: Vec) -> String { let signature = format!("{}({})", name, fields.join(",")); - get_full_signature(signature.as_bytes()) + keccak::full(signature.as_bytes()) } #[cfg(test)] diff --git a/analyzer/src/namespace/types.rs b/analyzer/src/namespace/types.rs index 51c873b9da..746e8d9484 100644 --- a/analyzer/src/namespace/types.rs +++ b/analyzer/src/namespace/types.rs @@ -13,8 +13,6 @@ use std::num::{ use crate::FunctionAttributes; use num_bigint::BigInt; -const ADDRESS_BYTE_LENGTH: usize = 20; - pub fn u256_max() -> BigInt { BigInt::from(2).pow(256) - 1 } @@ -422,7 +420,7 @@ impl FeSized for Base { Base::Numeric(integer) => integer.size(), Base::Bool => 1, Base::Byte => 1, - Base::Address => ADDRESS_BYTE_LENGTH, + Base::Address => 32, } } } @@ -536,7 +534,7 @@ impl AbiEncoding for Base { }, Base::Address => AbiType::Uint { size: AbiUintSize { - data_size: ADDRESS_BYTE_LENGTH, + data_size: 32, padded_size: 32, }, }, @@ -669,7 +667,7 @@ impl AbiEncoding for FeString { impl FeSized for Contract { fn size(&self) -> usize { - ADDRESS_BYTE_LENGTH + 32 } } diff --git a/common/src/utils/keccak.rs b/common/src/utils/keccak.rs index a9e76dc272..39f5295bd2 100644 --- a/common/src/utils/keccak.rs +++ b/common/src/utils/keccak.rs @@ -3,12 +3,32 @@ use tiny_keccak::{ Keccak, }; -pub fn get_full_signature(content: &[u8]) -> String { - get_partial_signature(content, 32) +/// Get the full 32 byte hash of the content. +pub fn full(content: &[u8]) -> String { + partial(content, 32) } -/// Return the keccak256 hash of the given content as an array of bytes -pub fn get_keccak256(content: &[u8]) -> [u8; 32] { +/// Take the first `size` number of bytes of the hash and pad the right side +/// with zeros to 32 bytes. +pub fn partial_right_padded(content: &[u8], size: usize) -> String { + let result = full_as_bytes(content); + let padded_output: Vec = result + .iter() + .enumerate() + .map(|(index, byte)| if index >= size { 0 } else { *byte }) + .collect(); + + format!("0x{}", hex::encode(&padded_output)) +} + +/// Take the first `size` number of bytes of the hash with no padding. +pub fn partial(content: &[u8], size: usize) -> String { + let result = full_as_bytes(content); + format!("0x{}", hex::encode(&result[0..size])) +} + +/// Get the full 32 byte hash of the content as a byte array. +pub fn full_as_bytes(content: &[u8]) -> [u8; 32] { let mut keccak = Keccak::v256(); let mut selector = [0u8; 32]; @@ -17,7 +37,3 @@ pub fn get_keccak256(content: &[u8]) -> [u8; 32] { selector } - -pub fn get_partial_signature(content: &[u8], size: usize) -> String { - format!("0x{}", hex::encode(&get_keccak256(content)[0..size])) -} diff --git a/compiler/src/abi/utils.rs b/compiler/src/abi/utils.rs index 7714bdf37d..1fdb8ae8d9 100644 --- a/compiler/src/abi/utils.rs +++ b/compiler/src/abi/utils.rs @@ -1,4 +1,4 @@ -use fe_common::utils::keccak::get_partial_signature; +use fe_common::utils::keccak; /// Formats the name and fields and calculates the 32 byte keccak256 value of /// the signature. @@ -13,5 +13,5 @@ pub fn func_selector(name: &str, params: Vec) -> String { fn sign_event_or_func(name: &str, params: Vec, size: usize) -> String { let signature = format!("{}({})", name, params.join(",")); - get_partial_signature(signature.as_bytes(), size) + keccak::partial(signature.as_bytes(), size) } diff --git a/compiler/src/yul/mappers/assignments.rs b/compiler/src/yul/mappers/assignments.rs index 578980f2cf..36be3ba994 100644 --- a/compiler/src/yul/mappers/assignments.rs +++ b/compiler/src/yul/mappers/assignments.rs @@ -177,7 +177,7 @@ mod tests { assert_eq!( map(&harness.context, &harness.src), - "mstoren(add($foo, mul(4, 32)), 2, 32)" + "mstoren(add($foo, mul(4, 32)), 32, 2)" ) } } diff --git a/compiler/src/yul/mappers/contracts.rs b/compiler/src/yul/mappers/contracts.rs index 87551042a9..e06a4af88e 100644 --- a/compiler/src/yul/mappers/contracts.rs +++ b/compiler/src/yul/mappers/contracts.rs @@ -3,7 +3,7 @@ use crate::yul::constructor; use crate::yul::mappers::functions; use crate::yul::runtime; use fe_analyzer::Context; -use fe_common::utils::keccak::get_full_signature; +use fe_common::utils::keccak; use fe_parser::ast as fe; use fe_parser::span::Spanned; use yultsur::*; @@ -53,7 +53,7 @@ pub fn contract_def( .clone() .into_iter() .map(|val| yul::Data { - name: get_full_signature(val.as_bytes()), + name: keccak::full(val.as_bytes()), value: val, }) .collect::>() diff --git a/compiler/src/yul/mappers/declarations.rs b/compiler/src/yul/mappers/declarations.rs index 87f5a67844..357d052495 100644 --- a/compiler/src/yul/mappers/declarations.rs +++ b/compiler/src/yul/mappers/declarations.rs @@ -91,7 +91,7 @@ mod tests { assert_eq!( map(&harness.context, &harness.src), - "let $foo := alloc(200)" + "let $foo := alloc(320)" ); } } diff --git a/compiler/src/yul/mappers/expressions.rs b/compiler/src/yul/mappers/expressions.rs index 13d07e22fc..66357da51a 100644 --- a/compiler/src/yul/mappers/expressions.rs +++ b/compiler/src/yul/mappers/expressions.rs @@ -24,7 +24,7 @@ use fe_analyzer::{ Context, Location, }; -use fe_common::utils::keccak::get_full_signature; +use fe_common::utils::keccak; use fe_parser::ast as fe; use fe_parser::span::Spanned; use std::convert::TryFrom; @@ -339,7 +339,7 @@ fn expr_bool(exp: &Spanned) -> Result { fn expr_str(exp: &Spanned) -> Result { if let fe::Expr::Str(lines) = &exp.node { let content = lines.join(""); - let string_identifier = format!(r#""{}""#, get_full_signature(content.as_bytes())); + let string_identifier = format!(r#""{}""#, keccak::full(content.as_bytes())); let offset = expression! { dataoffset([literal_expression! { (string_identifier) }]) }; let size = expression! { datasize([literal_expression! { (string_identifier) }]) }; @@ -475,15 +475,18 @@ fn expr_attribute_self( } /// Converts a storage nonce into a pointer based on the keccak256 hash +/// +/// Pointers created here have the last byte set to zero. This is to ensure that +/// our byte pointer sits at the start of a word (32 | `ptr` ). pub fn nonce_to_ptr(nonce: usize) -> yul::Expression { - let ptr = get_full_signature(nonce.to_string().as_bytes()); + // set the last byte to `0x00` to ensure our pointer sits at the start of a word + let ptr = keccak::partial_right_padded(nonce.to_string().as_bytes(), 31); literal_expression! { (ptr) } } /// Converts a storage nonce into a pointer based on the keccak256 hash pub fn nonce_with_offset_to_ptr(nonce: usize, offset: usize) -> yul::Expression { - let ptr = get_full_signature(nonce.to_string().as_bytes()); - let ptr = literal_expression! { (ptr) }; + let ptr = nonce_to_ptr(nonce); let offset = literal_expression! { (offset) }; expression! { (add([ptr], [offset])) } } @@ -566,7 +569,7 @@ mod tests { let result = map(&harness.context, &harness.src); - assert_eq!(result, "sloadn(dualkeccak256(0, 3), 32)"); + assert_eq!(result, "bytes_sloadn(map_value_ptr(0, 3), 32)"); } #[test] @@ -620,7 +623,7 @@ mod tests { assert_eq!( result, - "scopym(dualkeccak256(0, mloadn(add($bar_array, mul($index, 20)), 20)), 160)" + "scopym(div(map_value_ptr(0, mloadn(add($bar_array, mul($index, 32)), 32)), 32), 256)" ); } diff --git a/compiler/src/yul/operations/data.rs b/compiler/src/yul/operations/data.rs index 3e6813ca07..fee1c79071 100644 --- a/compiler/src/yul/operations/data.rs +++ b/compiler/src/yul/operations/data.rs @@ -7,38 +7,44 @@ use fe_analyzer::namespace::types::{ }; use yultsur::*; -/// Loads a value from storage. -/// -/// The returned expression evaluates to a 256 bit value. +/// Loads a value of the given type from storage. pub fn sload(typ: T, sptr: yul::Expression) -> yul::Expression { let size = literal_expression! { (typ.size()) }; - expression! { sloadn([sptr], [size]) } + expression! { bytes_sloadn([sptr], [size]) } } -/// Stores a 256 bit value in storage. +/// Stores a value of the given type in storage. pub fn sstore(typ: T, sptr: yul::Expression, value: yul::Expression) -> yul::Statement { let size = literal_expression! { (typ.size()) }; - statement! { sstoren([sptr], [value], [size]) } + statement! { bytes_sstoren([sptr], [size], [value]) } +} + +/// Loads a value of the given type from memory. +pub fn mload(typ: T, mptr: yul::Expression) -> yul::Expression { + let size = literal_expression! { (typ.size()) }; + expression! { mloadn([mptr], [size]) } } -/// Stores a 256 bit value in memory. +/// Stores a value of the given type in memory. pub fn mstore(typ: T, mptr: yul::Expression, value: yul::Expression) -> yul::Statement { let size = literal_expression! { (typ.size()) }; - statement! { mstoren([mptr], [value], [size]) } + statement! { mstoren([mptr], [size], [value]) } } /// Copies a segment of memory into storage. pub fn mcopys(typ: T, sptr: yul::Expression, mptr: yul::Expression) -> yul::Statement { let size = literal_expression! { (typ.size()) }; - statement! { mcopys([mptr], [sptr], [size]) } + let word_ptr = expression! { div([sptr], 32) }; + statement! { mcopys([mptr], [word_ptr], [size]) } } /// Copies a segment of storage into memory. /// -/// The returned expression evaluates to a memory pointer. +/// Returns the address of the data in memory. pub fn scopym(typ: T, sptr: yul::Expression) -> yul::Expression { let size = literal_expression! { (typ.size()) }; - expression! { scopym([sptr], [size]) } + let word_ptr = expression! { div([sptr], 32) }; + expression! { scopym([word_ptr], [size]) } } /// Copies a segment of storage to another segment of storage. @@ -48,7 +54,9 @@ pub fn scopys( origin_ptr: yul::Expression, ) -> yul::Statement { let size = literal_expression! { (typ.size()) }; - statement! { scopys([origin_ptr], [dest_ptr], [size]) } + let origin_word = expression! { div([origin_ptr], 32) }; + let dest_word = expression! { div([dest_ptr], 32) }; + statement! { scopys([origin_word], [dest_word], [size]) } } /// Copies a segment of memory to another segment of memory. @@ -57,14 +65,6 @@ pub fn mcopym(typ: T, ptr: yul::Expression) -> yul::Expression { expression! { mcopym([ptr], [size]) } } -/// Loads a value in memory. -/// -/// The returned expression evaluates to a 256 bit value. -pub fn mload(typ: T, mptr: yul::Expression) -> yul::Expression { - let size = literal_expression! { (typ.size()) }; - expression! { mloadn([mptr], [size]) } -} - /// Logs an event. pub fn emit_event(event: Event, vals: Vec) -> yul::Statement { let mut topics = vec![literal_expression! { (event.topic) }]; @@ -108,7 +108,7 @@ pub fn sum(vals: Vec) -> yul::Expression { /// Hashes the storage nonce of a map with a key to determine the value's /// location in storage. pub fn keyed_map(map: yul::Expression, key: yul::Expression) -> yul::Expression { - expression! { dualkeccak256([map], [key]) } + expression! { map_value_ptr([map], [key]) } } /// Finds the location of an array element base on the element size, element diff --git a/compiler/src/yul/runtime/functions/data.rs b/compiler/src/yul/runtime/functions/data.rs index 256d6e12f4..4fb2e6a232 100644 --- a/compiler/src/yul/runtime/functions/data.rs +++ b/compiler/src/yul/runtime/functions/data.rs @@ -21,112 +21,121 @@ pub fn alloc() -> yul::Statement { } } -/// Stores a value in a newly allocated memory segment. -pub fn alloc_mstoren() -> yul::Statement { +/// Set the highest available pointer. +pub fn free() -> yul::Statement { function_definition! { - function alloc_mstoren(val, size) -> ptr { - (ptr := alloc(size)) - (mstoren(ptr, val, size)) + function free(ptr) { + (mstore(0x00, ptr)) } } } -/// Set the highest available pointer. -pub fn free() -> yul::Statement { +/// Set the given segment of the value (defined in bits) to zero. +pub fn set_zero() -> yul::Statement { function_definition! { - function free(ptr) { - (mstore(0x00, ptr)) + function set_zero(start_bit, end_bit, val) -> result { + (let left_shift_dist := sub(256, start_bit)) + (let right_shift_dist := end_bit) + // shift left then right to zero out the desired bits + (let left := shl(left_shift_dist, (shr(left_shift_dist, val)))) + // shift right then left to zero out the desired bits + (let right := shr(right_shift_dist, (shl(right_shift_dist, val)))) + // combine the left and right side + // the segment defined by `start_bit` and `end_bit` will be zero + (result := or(left, right)) } } } -/// Copy calldata to memory a newly allocated segment of memory. -pub fn ccopym() -> yul::Statement { +/// Rounds a 256 bit value up to the nearest multiple of 32. +pub fn ceil32() -> yul::Statement { function_definition! { - function ccopym(cptr, size) -> mptr { - (mptr := alloc(size)) - (calldatacopy(mptr, cptr, size)) + function ceil32(n) -> return_val { + (return_val := mul((div((add(n, 31)), 32)), 32)) } } } -/// Load a static string from data into a newly allocated segment of memory. -pub fn load_data_string() -> yul::Statement { +/// Copy calldata to a newly allocated segment of memory. +pub fn ccopym() -> yul::Statement { function_definition! { - function load_data_string(code_ptr, size) -> mptr { - (mptr := alloc(32)) - (mstore(mptr, size)) - (let content_ptr := alloc(size)) - (datacopy(content_ptr, code_ptr, size)) + function ccopym(cptr, size) -> mptr { + (mptr := alloc(size)) + (calldatacopy(mptr, cptr, size)) } } } /// Copy memory to a given segment of storage. +/// +/// The storage pointer addresses a word. pub fn mcopys() -> yul::Statement { function_definition! { function mcopys(mptr, sptr, size) { - (let offset := 0) - (for { } (lt((add(offset, 32)), size)) { } + (let mptr_offset := 0) + (let sptr_offset := 0) + (for { } (lt((add(mptr_offset, 32)), size)) { } { - (let _mptr := add(mptr, offset)) - (let _sptr := add(sptr, offset)) + (let _mptr := add(mptr, mptr_offset)) + (let _sptr := add(sptr, sptr_offset)) (sstore(_sptr, (mload(_mptr)))) - (offset := add(offset, 32)) + (mptr_offset := add(mptr_offset, 32)) + (sptr_offset := add(sptr_offset, 1)) }) - (let rem := sub(size, offset)) + (let rem := sub(size, mptr_offset)) (if (gt(rem, 0)) { - (let _mptr := add(mptr, offset)) - (let _sptr := add(sptr, offset)) - (sstoren(_sptr, (mloadn(_mptr, rem)), rem)) + (let _mptr := add(mptr, mptr_offset)) + (let _sptr := add(sptr, sptr_offset)) + (let zeroed_val := set_zero((mul(rem, 8)), 256, (mload(_mptr)))) + (sstore(_sptr, zeroed_val)) }) } } } /// Copy storage to a newly allocated segment of memory. +/// +/// The storage pointer addresses a word. pub fn scopym() -> yul::Statement { function_definition! { function scopym(sptr, size) -> mptr { (mptr := alloc(size)) - (let offset := 0) - (for { } (lt((add(offset, 32)), size)) { } + (let mptr_offset := 0) + (let sptr_offset := 0) + (for { } (lt((add(mptr_offset, 32)), size)) { } { - (let _mptr := add(mptr, offset)) - (let _sptr := add(sptr, offset)) + (let _mptr := add(mptr, mptr_offset)) + (let _sptr := add(sptr, sptr_offset)) (mstore(_mptr, (sload(_sptr)))) - (offset := add(offset, 32)) + (mptr_offset := add(mptr_offset, 32)) + (sptr_offset := add(sptr_offset, 1)) }) - (let rem := sub(size, offset)) + (let rem := sub(size, mptr_offset)) (if (gt(rem, 0)) { - (let _mptr := add(mptr, offset)) - (let _sptr := add(sptr, offset)) - (mstoren(_mptr, (sloadn(_sptr, rem)), rem)) + (let _mptr := add(mptr, mptr_offset)) + (let _sptr := add(sptr, sptr_offset)) + (mstoren(_mptr, rem, (sloadn(_sptr, 0, rem)))) }) } } } /// Copies a segment of storage to another segment of storage. +/// +/// The storage pointers address words. pub fn scopys() -> yul::Statement { function_definition! { function scopys(ptr1, ptr2, size) { + (let word_size := div((add(size, 31)), 32)) (let offset := 0) - (for { } (lt((add(offset, 32)), size)) { } + (for { } (lt((add(offset, 1)), size)) { } { (let _ptr1 := add(ptr1, offset)) (let _ptr2 := add(ptr2, offset)) (sstore(_ptr2, (sload(_ptr1)))) - (offset := add(offset, 32)) - }) - - (let rem := sub(size, offset)) - (if (gt(rem, 0)) { - (let _ptr1 := add(ptr1, offset)) - (let _ptr2 := add(ptr2, offset)) - (sstoren(_ptr2, (sloadn(_ptr1, rem)), rem)) + (offset := add(offset, 1)) }) } } @@ -150,13 +159,13 @@ pub fn mcopym() -> yul::Statement { (if (gt(rem, 0)) { (let _ptr1 := add(ptr1, offset)) (let _ptr2 := add(ptr2, offset)) - (mstoren(_ptr2, (mloadn(_ptr1, rem)), rem)) + (mstoren(_ptr2, rem, (mloadn(_ptr1, rem)))) }) } } } -/// Read a 256 bit value from memory and right-shift according to size. +/// Read a value of n bytes from memory at the given address. pub fn mloadn() -> yul::Statement { function_definition! { function mloadn(ptr, size) -> val { @@ -165,16 +174,25 @@ pub fn mloadn() -> yul::Statement { } } -/// Read a 256 bit value from storage and right-shift according to size. +/// Read a value of n bytes at the given word address and bytes offset. +/// +/// (offset + size) must be less that 32, otherwise it enters undefined +/// behavior. pub fn sloadn() -> yul::Statement { function_definition! { - function sloadn(ptr, size) -> val { - (val := shr((sub(256, (mul(8, size)))), (sload(ptr)))) + function sloadn(word_ptr, bytes_offset, bytes_size) -> val { + (let bits_offset := mul(bytes_offset, 8)) + (let bits_size := mul(bytes_size, 8)) + (let bits_padding := sub(256, bits_size)) + + (let word := sload(word_ptr)) + (let word_shl := shl(bits_offset, word)) + (val := shr(bits_padding, word_shl)) } } } -/// Read a 256 bit value from calldata and right-shift according to size. +/// Read a value of n bytes from calldata at the given address. pub fn cloadn() -> yul::Statement { function_definition! { function cloadn(ptr, size) -> val { @@ -186,7 +204,7 @@ pub fn cloadn() -> yul::Statement { /// Stores a value in memory, only modifying the given size (0 < size <= 32). pub fn mstoren() -> yul::Statement { function_definition! { - function mstoren(ptr, val, size) { + function mstoren(ptr, size, val) { (let size_bits := mul(8, size)) (let left := shl((sub(256, size_bits)), val)) (let right := shr(size_bits, (mload((add(ptr, size)))))) @@ -198,32 +216,75 @@ pub fn mstoren() -> yul::Statement { /// Stores a value in storage, only modifying the given size (0 < size <= 32). pub fn sstoren() -> yul::Statement { function_definition! { - function sstoren(ptr, val, size) { - (let size_bits := mul(8, size)) - (let left := shl((sub(256, size_bits)), val)) - (let right := shr(size_bits, (sload((add(ptr, size)))))) - (sstore(ptr, (or(left, right)))) + function sstoren(word_ptr, bytes_offset, bytes_size, val) { + (let bits_offset := mul(bytes_offset, 8)) + (let bits_size := mul(bytes_size, 8)) + + (let old_word := sload(word_ptr)) + // zero out the section to be modified + (let zeroed_word := set_zero( + bits_offset, + (add(bits_offset, bits_size)), + old_word + )) + // get the number of bits we need to shift to get the value to the correct offset + (let left_shift_dist := sub((sub(256, bits_size)), bits_offset)) + (let offset_val := shl(left_shift_dist, val)) + // use or to place the new value in the zeroed out section + (let new_word := or(zeroed_word, offset_val)) + (sstore(word_ptr, new_word)) } } } -/// Takes two 256 bit values and returns the keccak256 value of both. -pub fn dualkeccak256() -> yul::Statement { +/// Read a value of n bytes at the given byte address. +/// +/// The value must not span multiple words. +pub fn bytes_sloadn() -> yul::Statement { function_definition! { - function dualkeccak256(a, b) -> return_val { - (let ptr := avail()) - (mstore(ptr, a)) - (mstore((add(ptr, 32)), b)) - (return_val := keccak256(ptr, 64)) + function bytes_sloadn(sptr, size) -> val { + (let word_ptr := div(sptr, 32)) + (let bytes_offset := mod(sptr, 32)) + (val := sloadn(word_ptr, bytes_offset, 32)) } } } -/// Rounds a 256 bit value up to the nearest multiple of 32. -pub fn ceil32() -> yul::Statement { +/// Stores a value in storage at the given address, only modifying a segment of +/// the given size. +/// +/// The modified segment must not span multiple words. +pub fn bytes_sstoren() -> yul::Statement { function_definition! { - function ceil32(n) -> return_val { - (return_val := mul((div((add(n, 31)), 32)), 32)) + function bytes_sstoren(sptr, size, val) { + (let word_ptr := div(sptr, 32)) + (let bytes_offset := mod(sptr, 32)) + (sstoren(word_ptr, bytes_offset, 32, val)) + } + } +} + +/// Stores a value in a newly allocated memory segment. +pub fn alloc_mstoren() -> yul::Statement { + function_definition! { + function alloc_mstoren(val, size) -> ptr { + (ptr := alloc(size)) + (mstoren(ptr, size, val)) + } + } +} + +/// Derives the byte address of a value corresponding to a map key. +/// +/// The address is always divisible by 32, so it points to a word. +pub fn map_value_ptr() -> yul::Statement { + function_definition! { + function map_value_ptr(a, b) -> return_val { + (let ptr := avail()) + (mstore(ptr, a)) + (mstore((add(ptr, 32)), b)) + (let hash := keccak256(ptr, 64)) + (return_val := set_zero(248, 256, hash)) } } } @@ -240,3 +301,15 @@ pub fn ternary() -> yul::Statement { } } } + +/// Load a static string from data into a newly allocated segment of memory. +pub fn load_data_string() -> yul::Statement { + function_definition! { + function load_data_string(code_ptr, size) -> mptr { + (mptr := alloc(32)) + (mstore(mptr, size)) + (let content_ptr := alloc(size)) + (datacopy(content_ptr, code_ptr, size)) + } + } +} diff --git a/compiler/src/yul/runtime/functions/mod.rs b/compiler/src/yul/runtime/functions/mod.rs index 5f6a106f0b..ef3d3aa319 100644 --- a/compiler/src/yul/runtime/functions/mod.rs +++ b/compiler/src/yul/runtime/functions/mod.rs @@ -21,12 +21,15 @@ pub fn std() -> Vec { data::scopys(), data::mloadn(), data::sloadn(), + data::bytes_sloadn(), data::cloadn(), data::mstoren(), data::sstoren(), - data::dualkeccak256(), + data::bytes_sstoren(), + data::map_value_ptr(), data::ceil32(), data::ternary(), + data::set_zero(), abi::unpack(), abi::pack(AbiDecodeLocation::Calldata), abi::pack(AbiDecodeLocation::Memory), diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index 7fce67cc08..96ed753577 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; use std::iter; mod utils; -use fe_common::utils::keccak::get_keccak256; +use fe_common::utils::keccak; use utils::ToBeBytes; use utils::*; @@ -741,7 +741,7 @@ fn keccak() { U256::from(1).to_be_bytes().to_vec(), )], Some(ðabi::Token::Uint( - get_keccak256(&U256::from(1).to_be_bytes()).into(), + keccak::full_as_bytes(&U256::from(1).to_be_bytes()).into(), )), ); @@ -750,7 +750,7 @@ fn keccak() { "return_hash_from_u8", &[ethabi::Token::FixedBytes([1].into())], Some(ðabi::Token::Uint( - get_keccak256(&1u8.to_be_bytes()).into(), + keccak::full_as_bytes(&1u8.to_be_bytes()).into(), )), ); @@ -759,7 +759,7 @@ fn keccak() { "return_hash_from_u8", &[ethabi::Token::FixedBytes([0].into())], Some(ðabi::Token::Uint( - get_keccak256(&0u8.to_be_bytes()).into(), + keccak::full_as_bytes(&0u8.to_be_bytes()).into(), )), ); @@ -768,7 +768,7 @@ fn keccak() { "return_hash_from_foo", &[bytes_token("foo")], Some(ðabi::Token::Uint( - get_keccak256(&"foo".as_bytes()).into(), + keccak::full_as_bytes(&"foo".as_bytes()).into(), )), ); }); @@ -934,6 +934,9 @@ fn data_copying_stress() { 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_addrs = address_array_token(&["0", "1", "2"]); + let my_second_addr = address_token("1"); + harness.test_function( &mut executor, "mutate_and_return", @@ -969,6 +972,14 @@ fn data_copying_stress() { Some(&u256_array_token(&[42, 26, 0, 1, 255])), ); + harness.test_function(&mut executor, "set_my_addrs", &[my_addrs], None); + harness.test_function( + &mut executor, + "get_my_second_addr", + &[], + Some(&my_second_addr), + ); + harness.events_emitted( executor, &[ diff --git a/compiler/tests/evm_runtime.rs b/compiler/tests/evm_runtime.rs index 79c318e08f..efec857ee1 100644 --- a/compiler/tests/evm_runtime.rs +++ b/compiler/tests/evm_runtime.rs @@ -16,7 +16,7 @@ macro_rules! assert_eq { } #[test] -fn test_alloc_and_avail() { +fn test_runtime_alloc_and_avail() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, @@ -36,51 +36,87 @@ fn test_alloc_and_avail() { } #[test] -fn test_mcopys() { +fn test_runtime_mcopys() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, functions::std(), statements! { - (let a := 26) - (let b := 27) + (let a := 0x0111114211111111011111112342311101111112221151110111111111111111) + (let b := 0x0111111234111111011123411111111101112431111111110111111234411111) + (let c := 0x0111341111111111011111111123411101111123411111110111111234111111) - (mstore(42, a)) - (mstore(74, b)) - (mcopys(42, 100, 64)) + (let d := 0x0111112344111111011111145111111101111111111111110111111111111111) + (let e := 0x0111112344111111011111145111111101111111110000000000000000000000) - [assert_eq!(a, (sload(100)))] - [assert_eq!(b, (sload(132)))] + (mstore(100, a)) + (mstore(132, b)) + (mstore(164, c)) + (mstore(196, d)) + + (mcopys(100, 42, 117)) + + [assert_eq!(a, (sload(42)))] + [assert_eq!(b, (sload(43)))] + [assert_eq!(c, (sload(44)))] + [assert_eq!(e, (sload(45)))] + + (mcopys(100, 46, 128)) + + [assert_eq!(a, (sload(46)))] + [assert_eq!(b, (sload(47)))] + [assert_eq!(c, (sload(48)))] + [assert_eq!(d, (sload(49)))] }, ); }) } #[test] -fn test_scopym() { +fn test_runtime_scopym() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, functions::std(), statements! { - (let a := 26) - (let b := 27) + (let a := 0x0111114211111111011111112342311101111112221151110111111111111111) + (let b := 0x0111111234111111011123411111111101112431111111110111111234411111) + (let c := 0x0111341111111111011111111123411101111123411111110111111234111111) + + (let d := 0x0111112344111111011111145111111101111111111111110111111111111111) + (let e := 0x0111112344111111011111145111111101111111110000000000000000000000) (sstore(42, a)) - (sstore(74, b)) + (sstore(43, b)) + (sstore(44, c)) + (sstore(45, d)) - (let ptr1 := scopym(42, 64)) + (let ptr1 := scopym(42, 117)) (let ptr2 := add(ptr1, 32)) + (let ptr3 := add(ptr2, 32)) + (let ptr4 := add(ptr3, 32)) [assert_eq!(a, (mload(ptr1)))] [assert_eq!(b, (mload(ptr2)))] + [assert_eq!(c, (mload(ptr3)))] + [assert_eq!(e, (mload(ptr4)))] + + (let ptr5 := scopym(42, 128)) + (let ptr6 := add(ptr5, 32)) + (let ptr7 := add(ptr6, 32)) + (let ptr8 := add(ptr7, 32)) + + [assert_eq!(a, (mload(ptr5)))] + [assert_eq!(b, (mload(ptr6)))] + [assert_eq!(c, (mload(ptr7)))] + [assert_eq!(d, (mload(ptr8)))] }, ); }) } #[test] -fn test_mloadn() { +fn test_runtime_mloadn() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, @@ -99,29 +135,83 @@ fn test_mloadn() { } #[test] -fn test_sloadn() { +fn test_runtime_storage_sanity() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, - functions::std(), + vec![], statements! { - (let a := 0x4200000000000000000000000000000000000000000000000000000000420026) + (let a := 0x4200000000000000000000000000000000000000000000000000000000000026) + (let b := 0x9900000000000000000000000000000000000000000000000000000000000077) (sstore(0, a)) + (sstore(1, b)) + + [assert_eq!(a, (sload(0)))] + [assert_eq!(b, (sload(1)))] + }, + ); + }) +} - [assert_eq!(0x42, (sloadn(0, 1)))] - [assert_eq!(a, (sloadn(0, 32)))] - // these fail for some reason.. possible bug or misunderstanding of the EVM? - // TODO: figure out why this fails - // [assert_eq!(0x420026, (sloadn(29, 3)))] - // [assert_eq!(0x26, (sloadn(30, 2)))] - // [assert_eq!(0x26, (sloadn(31, 1)))] +#[test] +fn test_runtime_sloadn() { + with_executor(&|mut executor| { + test_runtime_functions( + &mut executor, + functions::std(), + statements! { + (let a := 0x4200000000000000000000000000000000000000000000000000000000000026) + (let b := 0x9900530000003900000000000000000000000000000000000000000000000077) + (sstore(1000, a)) + (sstore(1001, b)) + + [assert_eq!(a, (sloadn(1000, 0, 32)))] + [assert_eq!(b, (sloadn(1001, 0, 32)))] + + [assert_eq!(0x42, (sloadn(1000, 0, 1)))] + [assert_eq!(0x26, (sloadn(1000, 31, 1)))] + [assert_eq!(0x4200, (sloadn(1000, 0, 2)))] + + [assert_eq!(0x99, (sloadn(1001, 0, 1)))] + [assert_eq!(0x77, (sloadn(1001, 31, 1)))] + [assert_eq!(0x990053, (sloadn(1001, 0, 3)))] + [assert_eq!(0x5300000039, (sloadn(1001, 2, 5)))] }, ); }) } #[test] -fn test_ceil32() { +fn test_runtime_sstoren() { + with_executor(&|mut executor| { + test_runtime_functions( + &mut executor, + functions::std(), + statements! { + (let a := 0x0111111111111111011111111111111101111111111111110111111111111111) + // dashes indicate which bytes are to be replaced in this test + // 0----2 8----10 15----------------23 31--32 + (let b := 0x4201111111111111123411111111119999999998998999110111111111111126) + (sstore(1000, a)) + + (sstoren(1000, 0, 2, 0x4201)) + (sstoren(1000, 8, 2, 0x1234)) + (sstoren(1000, 15, 8, 0x9999999998998999)) + (sstoren(1000, 31, 1, 0x26)) + + [assert_eq!(b, (sload(1000)))] + + (let c := 0x4242424242424242424242424242424242424242424242424242424242424242) + (sstoren(1000, 0, 32, c)) + + [assert_eq!(c, (sload(1000)))] + }, + ); + }) +} + +#[test] +fn test_runtime_ceil32() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, @@ -135,7 +225,7 @@ fn test_ceil32() { } #[test] -fn test_ternary() { +fn test_runtime_ternary() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, @@ -152,7 +242,7 @@ fn test_ternary() { } #[test] -fn test_abi_unpack() { +fn test_runtime_abi_unpack() { with_executor(&|mut executor| { test_runtime_functions( &mut executor, @@ -193,3 +283,23 @@ fn test_keccak256() { ); }) } + +#[test] +fn test_runtime_set_zero() { + with_executor(&|mut executor| { + test_runtime_functions( + &mut executor, + functions::std(), + statements! { + (let a := 0x1111111111111111111111111111111111111111111111111111111111111111) + (let b := 0x1111110000000000000000000000000000000000000000000000001111111111) + (let c := 0x1111100000000000000000000000000000000000000000000000000000000000) + + [assert_eq!(0, (set_zero(0, 256, a)))] + [assert_eq!(0x11, (set_zero(0, 248, a)))] + [assert_eq!(b, (set_zero(24, 216, a)))] + [assert_eq!(c, (set_zero(20, 256, a)))] + }, + ); + }) +} diff --git a/compiler/tests/fixtures/data_copying_stress.fe b/compiler/tests/fixtures/data_copying_stress.fe index 43289b5217..5c18018501 100644 --- a/compiler/tests/fixtures/data_copying_stress.fe +++ b/compiler/tests/fixtures/data_copying_stress.fe @@ -7,6 +7,8 @@ contract Foo: my_nums: u256[5] + my_addrs: address[3] + event MyEvent: my_string: string42 my_u256: u256 @@ -70,3 +72,9 @@ contract Foo: def emit_my_event_internal(some_string: string42, some_u256: u256): emit MyEvent(some_string, some_u256) + + pub def set_my_addrs(my_addrs: address[3]): + self.my_addrs = my_addrs + + pub def get_my_second_addr() -> address: + return self.my_addrs[1] \ No newline at end of file diff --git a/newsfragments/251.internal.md b/newsfragments/251.internal.md new file mode 100644 index 0000000000..d0fcd67bbb --- /dev/null +++ b/newsfragments/251.internal.md @@ -0,0 +1 @@ +Values are stored more efficiently in storage. \ No newline at end of file