From 8421507fde6094cc4a29eee19a70bf8991eb6113 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Sun, 26 Jun 2022 22:18:34 -0400 Subject: [PATCH] Enable storage initializers and emit a storage initialization JSON (#2078) * Enable storage initializers and dump out a JSON file * Move the new functions into a separate storage module * Use StorageSlot directly from fuel-tx * forc deploy now uses the storage slots * add some tests * lint, change initializers -> slots, and fixing some tests * enhance a comment * Revert unneeded changes to sway-types * add a failing test * Fix failing test * renaming some functions * Test the storage slots JSON in e2e tests and add forc json-storage-slots command * ignore *_output.json * forc documenter changes * Remove forc json-storage-slots and stop relying on forc json-abi * Enhance some comments * Remove unnecessary oracle --- .gitignore | 1 + Cargo.lock | 3 + forc-pkg/Cargo.toml | 1 + forc-pkg/src/pkg.rs | 8 + forc/src/cli/commands/build.rs | 4 + forc/src/cli/commands/deploy.rs | 4 + forc/src/cli/commands/run.rs | 5 + forc/src/ops/forc_build.rs | 30 ++- forc/src/ops/forc_deploy.rs | 15 +- forc/src/ops/forc_run.rs | 1 + sway-core/Cargo.toml | 1 + sway-core/src/convert_parse_tree.rs | 5 +- sway-core/src/ir_generation.rs | 5 +- sway-core/src/ir_generation/const_eval.rs | 22 +- sway-core/src/ir_generation/convert.rs | 17 -- sway-core/src/ir_generation/function.rs | 30 +-- sway-core/src/ir_generation/storage.rs | 234 ++++++++++++++++++ sway-core/src/lib.rs | 19 +- .../src/parse_tree/declaration/storage.rs | 3 +- .../ast_node/declaration/storage.rs | 67 ++++- .../src/semantic_analysis/ast_node/mod.rs | 20 +- sway-core/src/semantic_analysis/program.rs | 62 ++++- test/Cargo.toml | 1 + test/src/e2e_vm_tests/harness.rs | 86 +++++-- test/src/e2e_vm_tests/mod.rs | 26 +- .../nonconst_storage_init/Forc.lock | 9 + .../nonconst_storage_init/Forc.toml | 8 + .../nonconst_storage_init/src/main.sw | 19 ++ .../nonconst_storage_init/test.toml | 9 + .../storage_access_caller/src/main.sw | 50 +++- .../test_abis/storage_access_abi/src/main.sw | 2 + .../json_storage_slots_oracle.json | 1 + .../abi_with_tuples_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../array_of_structs_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../auth_testing_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../balance_test_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../test_contracts/basic_storage/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../context_testing_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../get_storage_key_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../increment_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../test_contracts/issue_1512_repro/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../test_contracts/multiple_impl/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../nested_struct_args_contract/test.toml | 1 + .../json_abi_oracle.json | 59 +++++ .../json_storage_slots_oracle.json | 106 ++++++++ .../storage_access_contract/src/main.sw | 181 ++++++-------- .../storage_access_contract/test.toml | 1 + .../json_storage_slots_oracle.json | 1 + .../test_fuel_coin_contract/test.toml | 1 + 59 files changed, 940 insertions(+), 198 deletions(-) create mode 100644 sway-core/src/ir_generation/storage.rs create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_storage_slots_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/json_storage_slots_oracle.json diff --git a/.gitignore b/.gitignore index 3facbca8035..d8e9a544778 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables **/*/target/ **/*/json_abi_output.json +**/*/json_storage_slots_output.json target # These are backup files generated by rustfmt diff --git a/Cargo.lock b/Cargo.lock index 773a3a1ae5f..9eb56543a8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ version = "0.16.2" dependencies = [ "anyhow", "forc-util", + "fuel-tx", "fuels-types", "git2", "petgraph", @@ -3595,6 +3596,7 @@ dependencies = [ "either", "fuel-asm", "fuel-crypto", + "fuel-tx", "fuel-types", "fuel-vm", "fuels-types", @@ -3792,6 +3794,7 @@ dependencies = [ "anyhow", "filecheck", "forc", + "forc-pkg", "forc-util", "fuel-asm", "fuel-tx", diff --git a/forc-pkg/Cargo.toml b/forc-pkg/Cargo.toml index 41d0e824f7c..281138c06fd 100644 --- a/forc-pkg/Cargo.toml +++ b/forc-pkg/Cargo.toml @@ -11,6 +11,7 @@ description = "Building, locking, fetching and updating Sway projects as Forc pa [dependencies] anyhow = "1" forc-util = { version = "0.16.2", path = "../forc-util" } +fuel-tx = "0.12" fuels-types = "0.12" git2 = { version = "0.14", features = ["vendored-libgit2", "vendored-openssl"] } petgraph = { version = "0.6", features = ["serde-1"] } diff --git a/forc-pkg/src/pkg.rs b/forc-pkg/src/pkg.rs index b14e54cee11..503a378a316 100644 --- a/forc-pkg/src/pkg.rs +++ b/forc-pkg/src/pkg.rs @@ -7,6 +7,7 @@ use forc_util::{ find_file_name, git_checkouts_directory, kebab_to_snake_case, print_on_failure, print_on_success, print_on_success_library, println_yellow_err, }; +use fuel_tx::StorageSlot; use fuels_types::JsonABI; use petgraph::{ self, @@ -45,6 +46,7 @@ pub struct PinnedId(u64); /// The result of successfully compiling a package. pub struct Compiled { pub json_abi: JsonABI, + pub storage_slots: Vec, pub bytecode: Vec, pub tree_type: TreeType, } @@ -1503,6 +1505,7 @@ pub fn compile( warnings, } => { let json_abi = time_expr!("generate JSON ABI", typed_program.kind.generate_json_abi()); + let storage_slots = typed_program.storage_slots.clone(); let tree_type = typed_program.kind.tree_type(); match tree_type { // If we're compiling a library, we don't need to compile any further. @@ -1513,6 +1516,7 @@ pub fn compile( let lib_namespace = typed_program.root.namespace.clone(); let compiled = Compiled { json_abi, + storage_slots, bytecode, tree_type, }; @@ -1535,6 +1539,7 @@ pub fn compile( let bytecode = bytes; let compiled = Compiled { json_abi, + storage_slots, bytecode, tree_type, }; @@ -1567,6 +1572,7 @@ pub fn build( let mut namespace_map = Default::default(); let mut source_map = SourceMap::new(); let mut json_abi = vec![]; + let mut storage_slots = vec![]; let mut bytecode = vec![]; let mut tree_type = None; for &node in &plan.compilation_order { @@ -1581,6 +1587,7 @@ pub fn build( namespace_map.insert(node, namespace.into()); } json_abi.extend(compiled.json_abi); + storage_slots.extend(compiled.storage_slots); bytecode = compiled.bytecode; tree_type = Some(compiled.tree_type); source_map.insert_dependency(path.clone()); @@ -1590,6 +1597,7 @@ pub fn build( let compiled = Compiled { bytecode, json_abi, + storage_slots, tree_type, }; Ok((compiled, source_map)) diff --git a/forc/src/cli/commands/build.rs b/forc/src/cli/commands/build.rs index 4612f816a16..6209bb45790 100644 --- a/forc/src/cli/commands/build.rs +++ b/forc/src/cli/commands/build.rs @@ -57,6 +57,10 @@ pub struct Command { /// output will be "minified", i.e. all on one line without whitespace. #[clap(long)] pub minify_json_abi: bool, + /// By default the JSON for initial storage slots is formatted for human readability. By using + /// this option JSON output will be "minified", i.e. all on one line without whitespace. + #[clap(long)] + pub minify_json_storage_slots: bool, /// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it /// needs to be updated, Forc will exit with an error #[clap(long)] diff --git a/forc/src/cli/commands/deploy.rs b/forc/src/cli/commands/deploy.rs index 19957822bfb..d778da1df23 100644 --- a/forc/src/cli/commands/deploy.rs +++ b/forc/src/cli/commands/deploy.rs @@ -45,6 +45,10 @@ pub struct Command { /// output will be "minified", i.e. all on one line without whitespace. #[clap(long)] pub minify_json_abi: bool, + /// By default the JSON for initial storage slots is formatted for human readability. By using + /// this option JSON output will be "minified", i.e. all on one line without whitespace. + #[clap(long)] + pub minify_json_storage_slots: bool, /// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it /// needs to be updated, Forc will exit with an error #[clap(long)] diff --git a/forc/src/cli/commands/run.rs b/forc/src/cli/commands/run.rs index 1067d11deea..bc70505f980 100644 --- a/forc/src/cli/commands/run.rs +++ b/forc/src/cli/commands/run.rs @@ -83,6 +83,11 @@ pub struct Command { #[clap(long)] pub minify_json_abi: bool, + /// By default the JSON for initial storage slots is formatted for human readability. By using + /// this option JSON output will be "minified", i.e. all on one line without whitespace. + #[clap(long)] + pub minify_json_storage_slots: bool, + /// Set the transaction byte price. Defaults to 0. #[clap(long)] pub byte_price: Option, diff --git a/forc/src/ops/forc_build.rs b/forc/src/ops/forc_build.rs index 7838d1e28b2..00f9da19158 100644 --- a/forc/src/ops/forc_build.rs +++ b/forc/src/ops/forc_build.rs @@ -25,6 +25,7 @@ pub fn build(command: BuildCommand) -> Result { silent_mode, output_directory, minify_json_abi, + minify_json_storage_slots, locked, build_profile, release, @@ -116,14 +117,21 @@ pub fn build(command: BuildCommand) -> Result { info!(" Bytecode size is {} bytes.", compiled.bytecode.len()); + // Additional ops required depending on the program type match compiled.tree_type { - TreeType::Script => { - // hash the bytecode for scripts and store the result in a file in the output directory - let bytecode_hash = format!("0x{}", fuel_crypto::Hasher::hash(&compiled.bytecode)); - let hash_file_name = format!("{}{}", &manifest.project.name, SWAY_BIN_HASH_SUFFIX); - let hash_path = output_dir.join(hash_file_name); - fs::write(hash_path, &bytecode_hash)?; - info!(" Script bytecode hash: {}", bytecode_hash); + TreeType::Contract => { + // For contracts, emit a JSON file with all the initialized storage slots. + let json_storage_slots_stem = format!("{}-storage_slots", manifest.project.name); + let json_storage_slots_path = output_dir + .join(&json_storage_slots_stem) + .with_extension("json"); + let file = File::create(json_storage_slots_path)?; + let res = if minify_json_storage_slots { + serde_json::to_writer(&file, &compiled.storage_slots) + } else { + serde_json::to_writer_pretty(&file, &compiled.storage_slots) + }; + res?; } TreeType::Predicate => { // get the root hash of the bytecode for predicates and store the result in a file in the output directory @@ -133,6 +141,14 @@ pub fn build(command: BuildCommand) -> Result { fs::write(root_path, &root)?; info!(" Predicate root: {}", root); } + TreeType::Script => { + // hash the bytecode for scripts and store the result in a file in the output directory + let bytecode_hash = format!("0x{}", fuel_crypto::Hasher::hash(&compiled.bytecode)); + let hash_file_name = format!("{}{}", &manifest.project.name, SWAY_BIN_HASH_SUFFIX); + let hash_path = output_dir.join(hash_file_name); + fs::write(hash_path, &bytecode_hash)?; + info!(" Script bytecode hash: {}", bytecode_hash); + } _ => (), } diff --git a/forc/src/ops/forc_deploy.rs b/forc/src/ops/forc_deploy.rs index 0d14748341c..43ef8e7e4e9 100644 --- a/forc/src/ops/forc_deploy.rs +++ b/forc/src/ops/forc_deploy.rs @@ -6,7 +6,7 @@ use crate::{ use anyhow::{bail, Result}; use forc_pkg::ManifestFile; use fuel_gql_client::client::FuelClient; -use fuel_tx::{Output, Salt, Transaction}; +use fuel_tx::{Output, Salt, StorageSlot, Transaction}; use fuel_vm::prelude::*; use std::path::PathBuf; use sway_core::TreeType; @@ -33,6 +33,7 @@ pub async fn deploy(command: DeployCommand) -> Result { silent_mode, output_directory, minify_json_abi, + minify_json_storage_slots, locked, url, build_profile, @@ -51,6 +52,7 @@ pub async fn deploy(command: DeployCommand) -> Result { silent_mode, output_directory, minify_json_abi, + minify_json_storage_slots, locked, build_profile, release, @@ -62,6 +64,7 @@ pub async fn deploy(command: DeployCommand) -> Result { compiled.bytecode, Vec::::new(), Vec::::new(), + compiled.storage_slots, ); let node_url = match &manifest.network { @@ -89,6 +92,7 @@ fn create_contract_tx( compiled_contract: Vec, inputs: Vec, outputs: Vec, + storage_slots: Vec, ) -> (Transaction, fuel_tx::ContractId) { let gas_price = 0; let gas_limit = fuel_tx::default_parameters::MAX_GAS_PER_TX; @@ -99,11 +103,16 @@ fn create_contract_tx( let salt = Salt::new([0; 32]); let static_contracts = vec![]; - let storage_slots = vec![]; let contract = Contract::from(compiled_contract); let root = contract.root(); - let state_root = Contract::default_state_root(); + + // The VM currently requires that storage slots are sorted but this shouldn't be neessary. + // Downstream tooling should do the sorting themselves. + // Ref: https://github.com/FuelLabs/fuel-tx/issues/153 + let mut storage_slots = storage_slots; + storage_slots.sort(); + let state_root = Contract::initial_state_root(storage_slots.iter()); let id = contract.id(&salt, &root, &state_root); info!("Contract id: 0x{}", hex::encode(id)); let outputs = [ diff --git a/forc/src/ops/forc_run.rs b/forc/src/ops/forc_run.rs index d9af5413109..2e770a3fa8f 100644 --- a/forc/src/ops/forc_run.rs +++ b/forc/src/ops/forc_run.rs @@ -37,6 +37,7 @@ pub async fn run(command: RunCommand) -> Result> { silent_mode: command.silent_mode, output_directory: command.output_directory, minify_json_abi: command.minify_json_abi, + minify_json_storage_slots: command.minify_json_storage_slots, locked: command.locked, build_profile: None, release: false, diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 62372205260..9e1783e4114 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -15,6 +15,7 @@ dirs = "3.0" either = "1.6" fuel-asm = "0.5" fuel-crypto = "0.5" +fuel-tx = "0.12" fuel-types = "0.5" fuel-vm = "0.11" fuels-types = "0.12" diff --git a/sway-core/src/convert_parse_tree.rs b/sway-core/src/convert_parse_tree.rs index e653dd3fdab..2d6d621773f 100644 --- a/sway-core/src/convert_parse_tree.rs +++ b/sway-core/src/convert_parse_tree.rs @@ -1867,7 +1867,10 @@ fn storage_field_to_storage_field( let storage_field = StorageField { name: storage_field.name, type_info: ty_to_type_info(ec, storage_field.ty)?, - //initializer: expr_to_expression(storage_field.expr), + initializer: storage_field + .initializer + .map(|initializer| expr_to_expression(ec, initializer.1)) + .transpose()?, }; Ok(storage_field) } diff --git a/sway-core/src/ir_generation.rs b/sway-core/src/ir_generation.rs index a54be627950..b8254f1b612 100644 --- a/sway-core/src/ir_generation.rs +++ b/sway-core/src/ir_generation.rs @@ -1,9 +1,10 @@ mod compile; -mod const_eval; +pub mod const_eval; mod convert; mod function; mod lexical_map; mod purity; +pub mod storage; mod types; use crate::{ @@ -17,7 +18,7 @@ use sway_types::span::Span; pub(crate) use purity::PurityChecker; pub(crate) fn compile_program(program: TypedProgram) -> Result { - let TypedProgram { kind, root } = program; + let TypedProgram { kind, root, .. } = program; let mut ctx = Context::default(); match kind { diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index ca1c33dd871..97b9eeb834d 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -use super::types::*; +use super::{convert::convert_literal_to_constant, types::*}; use sway_ir::{ constant::{Constant, ConstantValue}, @@ -26,6 +26,20 @@ pub(super) fn compile_constant_expression( const_expr: &TypedExpression, ) -> Result { let span_id_idx = MetadataIndex::from_span(context, &const_expr.span); + + let constant_evaluated = compile_constant_expression_to_constant(context, module, const_expr)?; + Ok(Value::new_constant( + context, + constant_evaluated, + span_id_idx, + )) +} + +pub(crate) fn compile_constant_expression_to_constant( + context: &mut Context, + module: Module, + const_expr: &TypedExpression, +) -> Result { let err = match &const_expr.expression { // Special case functions because the span in `const_expr` is to the inlined function // definition, rather than the actual call site. @@ -39,8 +53,8 @@ pub(super) fn compile_constant_expression( }), }; let mut known_consts = MappedStack::::new(); - const_eval_typed_expr(context, module, &mut known_consts, const_expr) - .map_or(err, |c| Ok(Value::new_constant(context, c, span_id_idx))) + + const_eval_typed_expr(context, module, &mut known_consts, const_expr).map_or(err, Ok) } // A HashMap that can hold multiple values and @@ -97,7 +111,7 @@ fn const_eval_typed_expr( expr: &TypedExpression, ) -> Option { match &expr.expression { - TypedExpressionVariant::Literal(l) => Some(super::convert::convert_literal_to_constant(l)), + TypedExpressionVariant::Literal(l) => Some(convert_literal_to_constant(l)), TypedExpressionVariant::FunctionApplication { arguments, function_body, diff --git a/sway-core/src/ir_generation/convert.rs b/sway-core/src/ir_generation/convert.rs index e79ec305e3a..f29b981f90c 100644 --- a/sway-core/src/ir_generation/convert.rs +++ b/sway-core/src/ir_generation/convert.rs @@ -140,20 +140,3 @@ fn convert_resolved_type( TypeInfo::Storage { .. } => reject_type!("Storage"), }) } - -use uint::construct_uint; - -#[allow( -// These two warnings are generated by the `construct_uint!()` macro below. - clippy::assign_op_pattern, - clippy::ptr_offset_with_cast -)] -pub(super) fn add_to_b256(x: fuel_types::Bytes32, y: u64) -> fuel_types::Bytes32 { - construct_uint! { - struct U256(4); - } - let x = U256::from(*x); - let y = U256::from(y); - let res: [u8; 32] = (x + y).into(); - fuel_types::Bytes32::from(res) -} diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index 1c426543e26..a29455df875 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -1,3 +1,10 @@ +use super::{ + compile::compile_function, + convert::*, + lexical_map::LexicalMap, + storage::{add_to_b256, get_storage_key}, + types::*, +}; use crate::{ asm_generation::from_ir::ir_type_size_in_bytes, constants, @@ -6,10 +13,6 @@ use crate::{ semantic_analysis::*, type_engine::{insert_type, resolve_type, TypeId, TypeInfo}, }; - -use super::{compile::compile_function, convert::*, lexical_map::LexicalMap, types::*}; - -use fuel_crypto::Hasher; use sway_ir::{Context, *}; use sway_types::{ ident::Ident, @@ -1609,16 +1612,7 @@ impl FnCompiler { Ok(struct_val) } _ => { - // Calculate the storage location hash for the given field - let mut storage_slot_to_hash = format!( - "{}{}", - sway_utils::constants::STORAGE_DOMAIN_SEPARATOR, - ix.to_usize() - ); - for ix in &indices { - storage_slot_to_hash = format!("{}_{}", storage_slot_to_hash, ix); - } - let hashed_storage_slot = Hasher::hash(storage_slot_to_hash); + let storage_key = get_storage_key(ix, &indices); // New name for the key let mut key_name = format!("{}{}", "key_for_", ix.to_usize()); @@ -1638,7 +1632,7 @@ impl FnCompiler { // Const value for the key from the hash let const_key = convert_literal_to_value( context, - &Literal::B256(hashed_storage_slot.into()), + &Literal::B256(storage_key.into()), span_md_idx, ); @@ -1684,7 +1678,7 @@ impl FnCompiler { &indices, &mut key_ptr_val, &key_ptr, - &hashed_storage_slot, + &storage_key, r#type, rhs, span_md_idx, @@ -1804,7 +1798,7 @@ impl FnCompiler { indices: &[u64], key_ptr_val: &mut Value, key_ptr: &Pointer, - hashed_storage_slot: &fuel_types::Bytes32, + storage_key: &fuel_types::Bytes32, r#type: &Type, rhs: &Option, span_md_idx: Option, @@ -1862,7 +1856,7 @@ impl FnCompiler { // Const value for the key from the initial hash + array_index let const_key = convert_literal_to_value( context, - &Literal::B256(*add_to_b256(*hashed_storage_slot, array_index)), + &Literal::B256(*add_to_b256(*storage_key, array_index)), span_md_idx, ); diff --git a/sway-core/src/ir_generation/storage.rs b/sway-core/src/ir_generation/storage.rs new file mode 100644 index 00000000000..31621995bc6 --- /dev/null +++ b/sway-core/src/ir_generation/storage.rs @@ -0,0 +1,234 @@ +use crate::asm_generation::from_ir::ir_type_size_in_bytes; +use fuel_crypto::Hasher; +use fuel_tx::StorageSlot; +use fuel_types::{Bytes32, Bytes8}; +use sway_ir::{ + constant::{Constant, ConstantValue}, + context::Context, + irtype::{AggregateContent, Type}, +}; +use sway_types::state::StateIndex; + +/// Hands out storage keys using a state index and a list of subfield indices. +/// Basically returns sha256("storage____..") +/// +pub(super) fn get_storage_key(ix: &StateIndex, indices: &[T]) -> Bytes32 +where + T: std::fmt::Display, +{ + Hasher::hash(indices.iter().fold( + format!( + "{}{}", + sway_utils::constants::STORAGE_DOMAIN_SEPARATOR, + ix.to_usize() + ), + |acc, i| format!("{}_{}", acc, i), + )) +} + +use uint::construct_uint; + +#[allow( +// These two warnings are generated by the `construct_uint!()` macro below. + clippy::assign_op_pattern, + clippy::ptr_offset_with_cast +)] +pub(super) fn add_to_b256(x: fuel_types::Bytes32, y: u64) -> fuel_types::Bytes32 { + construct_uint! { + struct U256(4); + } + let x = U256::from(*x); + let y = U256::from(y); + let res: [u8; 32] = (x + y).into(); + fuel_types::Bytes32::from(res) +} + +/// Given a constant value `constant`, a type `ty`, a state index, and a vector of subfield +/// indices, serialize the constant into a vector of storage slots. The keys (slots) are +/// generated using the state index and the subfield indices which are recursively built. The +/// values are generated such that each subfield gets its own storage slot except for enums and +/// strings which are spread over successive storage slots (use `serialize_to_words` in this case). +/// +/// This behavior matches the behavior of how storage slots are assigned for storage reads and +/// writes (i.e. how `state_read_*` and `state_write_*` instructions are generated). +/// +pub fn serialize_to_storage_slots( + constant: &Constant, + context: &Context, + ix: &StateIndex, + ty: &Type, + indices: &[usize], +) -> Vec { + match (&ty, &constant.value) { + (_, ConstantValue::Undef) => vec![], + (Type::Unit, ConstantValue::Unit) => vec![StorageSlot::new( + get_storage_key(ix, indices), + Bytes32::new([0; 32]), + )], + (Type::Bool, ConstantValue::Bool(b)) => { + vec![StorageSlot::new( + get_storage_key(ix, indices), + Bytes32::new( + [0; 7] + .iter() + .cloned() + .chain([if *b { 0x01 } else { 0x00 }].iter().cloned()) + .chain([0; 24].iter().cloned()) + .collect::>() + .try_into() + .unwrap(), + ), + )] + } + (Type::Uint(_), ConstantValue::Uint(n)) => { + vec![StorageSlot::new( + get_storage_key(ix, indices), + Bytes32::new( + n.to_be_bytes() + .iter() + .cloned() + .chain([0; 24].iter().cloned()) + .collect::>() + .try_into() + .unwrap(), + ), + )] + } + (Type::B256, ConstantValue::B256(b)) => { + vec![StorageSlot::new( + get_storage_key(ix, indices), + Bytes32::new(*b), + )] + } + (Type::Array(_), ConstantValue::Array(_a)) => { + unimplemented!("Arrays in storage have not been implemented yet.") + } + (Type::Struct(aggregate), ConstantValue::Struct(vec)) => { + match &context.aggregates[aggregate.0] { + AggregateContent::FieldTypes(field_tys) => vec + .iter() + .zip(field_tys.iter()) + .enumerate() + .flat_map(|(i, (f, ty))| { + serialize_to_storage_slots( + f, + context, + ix, + ty, + &indices + .iter() + .cloned() + .chain(vec![i].iter().cloned()) + .collect::>(), + ) + }) + .collect(), + _ => unreachable!("Wrong content for struct."), + } + } + (Type::Union(_), _) | (Type::String(_), _) => { + // Serialize the constant data in words and add zero words until the number of words + // is a multiple of 4. This is useful because each storage slot is 4 words. + let mut packed = serialize_to_words(constant, context, ty); + packed.extend(vec![ + Bytes8::new([0; 8]); + ((packed.len() + 3) / 4) * 4 - packed.len() + ]); + + assert!(packed.len() % 4 == 0); + + // Return a list of `StorageSlot`s + // First get the keys then get the values + (0..(ir_type_size_in_bytes(context, ty) + 31) / 32) + .into_iter() + .map(|i| add_to_b256(get_storage_key(ix, indices), i)) + .zip((0..packed.len() / 4).into_iter().map(|i| { + Bytes32::new( + Vec::from_iter((0..4).into_iter().flat_map(|j| *packed[4 * i + j])) + .try_into() + .unwrap(), + ) + })) + .map(|(k, r)| StorageSlot::new(k, r)) + .collect() + } + _ => vec![], + } +} + +/// Given a constant value `constant` and a type `ty`, serialize the constant into a vector of +/// words and add left padding up to size of `ty`. +/// +pub fn serialize_to_words(constant: &Constant, context: &Context, ty: &Type) -> Vec { + match (&ty, &constant.value) { + (_, ConstantValue::Undef) => vec![], + (Type::Unit, ConstantValue::Unit) => vec![Bytes8::new([0; 8])], + (Type::Bool, ConstantValue::Bool(b)) => { + vec![Bytes8::new( + [0; 7] + .iter() + .cloned() + .chain([if *b { 0x01 } else { 0x00 }].iter().cloned()) + .collect::>() + .try_into() + .unwrap(), + )] + } + (Type::Uint(_), ConstantValue::Uint(n)) => { + vec![Bytes8::new(n.to_be_bytes())] + } + (Type::B256, ConstantValue::B256(b)) => Vec::from_iter( + (0..4) + .into_iter() + .map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap())), + ), + (Type::String(_), ConstantValue::String(s)) => { + // Turn the serialized words (Bytes8) into seriliazed storage slots (Bytes32) + // Pad to word alignment + let mut s = s.clone(); + s.extend(vec![0; ((s.len() + 3) / 4) * 4 - s.len()]); + + assert!(s.len() % 8 == 0); + + // Group into words + Vec::from_iter((0..s.len() / 8).into_iter().map(|i| { + Bytes8::new( + Vec::from_iter((0..8).into_iter().map(|j| s[8 * i + j])) + .try_into() + .unwrap(), + ) + })) + } + (Type::Array(_), ConstantValue::Array(_)) => { + unimplemented!("Arrays in storage have not been implemented yet.") + } + (Type::Struct(aggregate), ConstantValue::Struct(vec)) => { + match &context.aggregates[aggregate.0] { + AggregateContent::FieldTypes(field_tys) => vec + .iter() + .zip(field_tys.iter()) + .flat_map(|(f, ty)| serialize_to_words(f, context, ty)) + .collect(), + _ => unreachable!("Wrong content for struct."), + } + } + (Type::Union(_), _) => { + let value_size_in_words = ir_type_size_in_bytes(context, ty) / 8; + let constant_size_in_words = ir_type_size_in_bytes(context, &constant.ty) / 8; + assert!(value_size_in_words >= constant_size_in_words); + + // Add enough left padding to satisfy the actual size of the union + let padding_size_in_words = value_size_in_words - constant_size_in_words; + vec![Bytes8::new([0; 8]); padding_size_in_words as usize] + .iter() + .cloned() + .chain( + serialize_to_words(constant, context, &constant.ty) + .iter() + .cloned(), + ) + .collect() + } + _ => vec![], + } +} diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index b965cf771fa..ac12974c364 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -255,8 +255,25 @@ pub fn compile_to_ast( return CompileAstResult::Failure { errors, warnings }; } + // Check that all storage initializers can be evaluated at compile time. + let CompileResult { + value: typed_program_with_storage_slots_result, + warnings: new_warnings, + errors: new_errors, + } = typed_program.get_typed_program_with_initialized_storage_slots(); + warnings.extend(new_warnings); + errors.extend(new_errors); + let typed_program_with_storage_slots = match typed_program_with_storage_slots_result { + Some(typed_program_with_storage_slots) => typed_program_with_storage_slots, + None => { + errors = dedup_unsorted(errors); + warnings = dedup_unsorted(warnings); + return CompileAstResult::Failure { errors, warnings }; + } + }; + CompileAstResult::Success { - typed_program: Box::new(typed_program), + typed_program: Box::new(typed_program_with_storage_slots), warnings, } } diff --git a/sway-core/src/parse_tree/declaration/storage.rs b/sway-core/src/parse_tree/declaration/storage.rs index c452e33825e..a70b280e237 100644 --- a/sway-core/src/parse_tree/declaration/storage.rs +++ b/sway-core/src/parse_tree/declaration/storage.rs @@ -1,4 +1,4 @@ -use crate::type_engine::*; +use crate::{parse_tree::Expression, type_engine::*}; use sway_types::{ident::Ident, span::Span}; @@ -18,4 +18,5 @@ pub struct StorageDeclaration { pub struct StorageField { pub name: Ident, pub type_info: TypeInfo, + pub initializer: Option, } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/storage.rs b/sway-core/src/semantic_analysis/ast_node/declaration/storage.rs index a410c787f95..6aa55ea6d34 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/storage.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/storage.rs @@ -1,16 +1,19 @@ -use crate::semantic_analysis::{ - TypeCheckedStorageAccess, TypeCheckedStorageAccessDescriptor, TypedStructField, -}; -use crate::type_engine::look_up_type_id; use crate::{ error::*, - type_engine::{TypeId, TypeInfo}, + ir_generation::{ + const_eval::compile_constant_expression_to_constant, storage::serialize_to_storage_slots, + }, + semantic_analysis::{ + TypeCheckedStorageAccess, TypeCheckedStorageAccessDescriptor, TypedExpression, + TypedStructField, + }, + type_engine::{look_up_type_id, TypeId, TypeInfo}, Ident, }; -use sway_types::Spanned; -use sway_types::{state::StateIndex, Span}; - use derivative::Derivative; +use fuel_tx::StorageSlot; +use sway_ir::{Context, Kind, Module}; +use sway_types::{state::StateIndex, Span, Spanned}; #[derive(Clone, Debug, Derivative)] #[derivative(PartialEq, Eq)] @@ -134,6 +137,7 @@ impl TypedStorageDeclaration { ref name, type_id: ref r#type, ref span, + .. }| TypedStructField { name: name.clone(), type_id: *r#type, @@ -142,12 +146,30 @@ impl TypedStorageDeclaration { ) .collect() } + + pub(crate) fn get_initialized_storage_slots(&self) -> CompileResult> { + let mut errors = vec![]; + let storage_slots = self + .fields + .iter() + .enumerate() + .map(|(i, f)| f.get_initialized_storage_slots(&StateIndex::new(i))) + .filter_map(|s| s.map_err(|e| errors.push(e)).ok()) + .flatten() + .collect::>(); + + match errors.is_empty() { + true => ok(storage_slots, vec![], vec![]), + false => err(vec![], errors), + } + } } #[derive(Clone, Debug, Eq)] pub struct TypedStorageField { pub name: Ident, pub type_id: TypeId, + pub initializer: Option, pub(crate) span: Span, } @@ -156,16 +178,41 @@ pub struct TypedStorageField { // https://doc.rust-lang.org/std/collections/struct.HashMap.html impl PartialEq for TypedStorageField { fn eq(&self, other: &Self) -> bool { - self.name == other.name && look_up_type_id(self.type_id) == look_up_type_id(other.type_id) + self.name == other.name + && look_up_type_id(self.type_id) == look_up_type_id(other.type_id) + && self.initializer == other.initializer } } impl TypedStorageField { - pub fn new(name: Ident, r#type: TypeId, span: Span) -> Self { + pub fn new( + name: Ident, + r#type: TypeId, + initializer: Option, + span: Span, + ) -> Self { TypedStorageField { name, type_id: r#type, + initializer, span, } } + + pub fn get_initialized_storage_slots( + &self, + ix: &StateIndex, + ) -> Result, CompileError> { + let mut context = Context::default(); + let module = Module::new(&mut context, Kind::Contract); + match &self.initializer { + None => Ok(vec![]), + Some(initializer) => compile_constant_expression_to_constant( + &mut context, + module, + initializer, + ) + .map(|constant| serialize_to_storage_slots(&constant, &context, ix, &constant.ty, &[])), + } + } } diff --git a/sway-core/src/semantic_analysis/ast_node/mod.rs b/sway-core/src/semantic_analysis/ast_node/mod.rs index 117d583ff59..a067256fcbe 100644 --- a/sway-core/src/semantic_analysis/ast_node/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/mod.rs @@ -440,7 +440,12 @@ impl TypedAstNode { } Declaration::StorageDeclaration(StorageDeclaration { span, fields }) => { let mut fields_buf = Vec::with_capacity(fields.len()); - for StorageField { name, type_info } in fields { + for StorageField { + name, + type_info, + initializer, + } in fields + { let type_id = check!( ctx.namespace.resolve_type_without_self( insert_type(type_info), @@ -450,9 +455,22 @@ impl TypedAstNode { warnings, errors ); + + let mut ctx = ctx.by_ref().with_type_annotation(type_id); + let initializer = match initializer { + Some(initializer) => Some(check!( + TypedExpression::type_check(ctx.by_ref(), initializer), + return err(warnings, errors), + warnings, + errors, + )), + None => None, + }; + fields_buf.push(TypedStorageField::new( name, type_id, + initializer, span.clone(), )); } diff --git a/sway-core/src/semantic_analysis/program.rs b/sway-core/src/semantic_analysis/program.rs index 5bb32448d39..ed05292ac25 100644 --- a/sway-core/src/semantic_analysis/program.rs +++ b/sway-core/src/semantic_analysis/program.rs @@ -12,6 +12,7 @@ use crate::{ type_engine::*, types::ToJsonAbi, }; +use fuel_tx::StorageSlot; use fuels_types::JsonABI; use sway_types::{span::Span, Ident, Spanned}; @@ -19,6 +20,7 @@ use sway_types::{span::Span, Ident, Spanned}; pub struct TypedProgram { pub kind: TypedProgramKind, pub root: TypedModule, + pub storage_slots: Vec, } impl TypedProgram { @@ -37,7 +39,11 @@ impl TypedProgram { let mod_res = TypedModule::type_check(ctx, root); mod_res.flat_map(|root| { let kind_res = Self::validate_root(&root, kind, mod_span); - kind_res.map(|kind| Self { kind, root }) + kind_res.map(|kind| Self { + kind, + root, + storage_slots: vec![], + }) }) } @@ -221,6 +227,60 @@ impl TypedProgram { err(vec![], errors) } } + + pub fn get_typed_program_with_initialized_storage_slots(&self) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + match &self.kind { + TypedProgramKind::Contract { declarations, .. } => { + let storage_decl = declarations + .iter() + .find(|decl| matches!(decl, TypedDeclaration::StorageDeclaration(_))); + + // Expecting at most a single storage declaration + match storage_decl { + Some(TypedDeclaration::StorageDeclaration(decl)) => { + let mut storage_slots = check!( + decl.get_initialized_storage_slots(), + return err(warnings, errors), + warnings, + errors, + ); + // Sort the slots to standardize the output. Not strictly required by the + // spec. + storage_slots.sort(); + ok( + Self { + kind: self.kind.clone(), + root: self.root.clone(), + storage_slots, + }, + warnings, + errors, + ) + } + _ => ok( + Self { + kind: self.kind.clone(), + root: self.root.clone(), + storage_slots: vec![], + }, + warnings, + errors, + ), + } + } + _ => ok( + Self { + kind: self.kind.clone(), + root: self.root.clone(), + storage_slots: vec![], + }, + warnings, + errors, + ), + } + } } #[derive(Clone, Debug)] diff --git a/test/Cargo.toml b/test/Cargo.toml index bc583edbb4c..14058fdaa4d 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -9,6 +9,7 @@ publish = false anyhow = "1.0.41" filecheck = "0.5" forc = { path = "../forc", features = ["test"], default-features = false } +forc-pkg = { path = "../forc-pkg" } forc-util = { path = "../forc-util" } fuel-asm = "0.5" fuel-tx = "0.12" diff --git a/test/src/e2e_vm_tests/harness.rs b/test/src/e2e_vm_tests/harness.rs index 771d712e917..f807bc4e57e 100644 --- a/test/src/e2e_vm_tests/harness.rs +++ b/test/src/e2e_vm_tests/harness.rs @@ -1,12 +1,9 @@ use anyhow::{bail, Result}; -use forc::test::{ - forc_abi_json, forc_build, forc_deploy, forc_run, BuildCommand, DeployCommand, JsonAbiCommand, - RunCommand, -}; +use forc::test::{forc_build, forc_deploy, forc_run, BuildCommand, DeployCommand, RunCommand}; +use forc_pkg::Compiled; use fuel_tx::Transaction; use fuel_vm::interpreter::Interpreter; use fuel_vm::prelude::*; -use serde_json::Value; use std::fs; pub(crate) fn deploy_contract(file_name: &str, locked: bool) -> ContractId { @@ -67,7 +64,7 @@ pub(crate) fn runs_on_node( /// Very basic check that code does indeed run in the VM. /// `true` if it does, `false` if not. -pub(crate) fn runs_in_vm(file_name: &str, locked: bool) -> ProgramState { +pub(crate) fn runs_in_vm(file_name: &str, locked: bool) -> (ProgramState, Compiled) { let storage = MemoryStorage::default(); let script = compile_to_bytes(file_name, locked).unwrap(); @@ -84,7 +81,7 @@ pub(crate) fn runs_in_vm(file_name: &str, locked: bool) -> ProgramState { gas_limit, byte_price, maturity, - script, + script.bytecode.clone(), script_data, inputs, outputs, @@ -95,7 +92,7 @@ pub(crate) fn runs_in_vm(file_name: &str, locked: bool) -> ProgramState { .validate(block_height, &Default::default()) .unwrap(); let mut i = Interpreter::with_storage(storage, Default::default()); - *i.transact(tx_to_test).unwrap().state() + (*i.transact(tx_to_test).unwrap().state(), script) } /// Returns Err(()) if code _does_ compile, used for test cases where the source @@ -132,7 +129,7 @@ pub(crate) fn does_not_compile(file_name: &str, locked: bool) -> Result Result> { +pub(crate) fn compile_to_bytes(file_name: &str, locked: bool) -> Result { compile_to_bytes_verbose(file_name, locked, get_test_config_from_env()) } @@ -140,7 +137,7 @@ pub(crate) fn compile_to_bytes_verbose( file_name: &str, locked: bool, verbose: bool, -) -> Result> { +) -> Result { tracing::info!(" Compiling {}", file_name); let manifest_dir = env!("CARGO_MANIFEST_DIR"); forc_build::build(BuildCommand { @@ -152,11 +149,10 @@ pub(crate) fn compile_to_bytes_verbose( silent_mode: !verbose, ..Default::default() }) - .map(|compiled| compiled.bytecode) } -pub(crate) fn test_json_abi(file_name: &str) -> Result<()> { - let _compiled_res = compile_to_json_abi(file_name)?; +pub(crate) fn test_json_abi(file_name: &str, compiled: &Compiled) -> Result<()> { + emit_json_abi(file_name, compiled)?; let manifest_dir = env!("CARGO_MANIFEST_DIR"); let oracle_path = format!( "{}/src/e2e_vm_tests/test_programs/{}/{}", @@ -182,21 +178,59 @@ pub(crate) fn test_json_abi(file_name: &str) -> Result<()> { Ok(()) } -fn compile_to_json_abi(file_name: &str) -> Result { +fn emit_json_abi(file_name: &str, compiled: &Compiled) -> Result<()> { tracing::info!(" ABI gen {}", file_name); + let json_abi = serde_json::json!(compiled.json_abi); let manifest_dir = env!("CARGO_MANIFEST_DIR"); - forc_abi_json::build(JsonAbiCommand { - path: Some(format!( - "{}/src/e2e_vm_tests/test_programs/{}", - manifest_dir, file_name - )), - json_outfile: Some(format!( - "{}/src/e2e_vm_tests/test_programs/{}/{}", - manifest_dir, file_name, "json_abi_output.json" - )), - silent_mode: true, - ..Default::default() - }) + let file = std::fs::File::create(format!( + "{}/src/e2e_vm_tests/test_programs/{}/{}", + manifest_dir, file_name, "json_abi_output.json" + )) + .map_err(|e| e)?; + let res = serde_json::to_writer_pretty(&file, &json_abi); + res.map_err(|e| e)?; + Ok(()) +} + +pub(crate) fn test_json_storage_slots(file_name: &str, compiled: &Compiled) -> Result<()> { + emit_json_storage_slots(file_name, compiled)?; + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let oracle_path = format!( + "{}/src/e2e_vm_tests/test_programs/{}/{}", + manifest_dir, file_name, "json_storage_slots_oracle.json" + ); + let output_path = format!( + "{}/src/e2e_vm_tests/test_programs/{}/{}", + manifest_dir, file_name, "json_storage_slots_output.json" + ); + if fs::metadata(oracle_path.clone()).is_err() { + bail!("JSON storage slots oracle file does not exist for this test."); + } + if fs::metadata(output_path.clone()).is_err() { + bail!("JSON storage slots output file does not exist for this test."); + } + let oracle_contents = + fs::read_to_string(oracle_path).expect("Something went wrong reading the file."); + let output_contents = + fs::read_to_string(output_path).expect("Something went wrong reading the file."); + if oracle_contents != output_contents { + bail!("Mismatched storage slots JSON output."); + } + Ok(()) +} + +fn emit_json_storage_slots(file_name: &str, compiled: &Compiled) -> Result<()> { + tracing::info!(" storage slots JSON gen {}", file_name); + let json_storage_slots = serde_json::json!(compiled.storage_slots); + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let file = std::fs::File::create(format!( + "{}/src/e2e_vm_tests/test_programs/{}/{}", + manifest_dir, file_name, "json_storage_slots_output.json" + )) + .map_err(|e| e)?; + let res = serde_json::to_writer_pretty(&file, &json_storage_slots); + res.map_err(|e| e)?; + Ok(()) } fn get_test_config_from_env() -> bool { diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index 33d29333662..4df29596d7d 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -33,6 +33,7 @@ struct TestDescription { expected_result: Option, contract_paths: Vec, validate_abi: bool, + validate_storage_slots: bool, checker: filecheck::Checker, } @@ -55,6 +56,7 @@ pub fn run(locked: bool, filter_regex: Option) { expected_result, contract_paths, validate_abi, + validate_storage_slots, checker, } in configured_tests { @@ -79,15 +81,27 @@ pub fn run(locked: bool, filter_regex: Option) { ), }; - assert_eq!(crate::e2e_vm_tests::harness::runs_in_vm(&name, locked), res); + let result = crate::e2e_vm_tests::harness::runs_in_vm(&name, locked); + assert_eq!(result.0, res); if validate_abi { - assert!(crate::e2e_vm_tests::harness::test_json_abi(&name).is_ok()); + assert!(crate::e2e_vm_tests::harness::test_json_abi(&name, &result.1).is_ok()); } number_of_tests_executed += 1; } TestCategory::Compiles => { - assert!(crate::e2e_vm_tests::harness::compile_to_bytes(&name, locked).is_ok()); + let result = crate::e2e_vm_tests::harness::compile_to_bytes(&name, locked); + assert!(result.is_ok()); + let compiled = result.unwrap(); + if validate_abi { + assert!(crate::e2e_vm_tests::harness::test_json_abi(&name, &compiled).is_ok()); + } + if validate_storage_slots { + assert!(crate::e2e_vm_tests::harness::test_json_storage_slots( + &name, &compiled + ) + .is_ok()); + } number_of_tests_executed += 1; } @@ -273,6 +287,11 @@ fn parse_test_toml(path: &Path) -> Result { .map(|v| v.as_bool().unwrap_or(false)) .unwrap_or(false); + let validate_storage_slots = toml_content + .get("validate_storage_slots") + .map(|v| v.as_bool().unwrap_or(false)) + .unwrap_or(false); + // We need to adjust the path to start relative to `test_programs`. let name = path .iter() @@ -294,6 +313,7 @@ fn parse_test_toml(path: &Path) -> Result { expected_result, contract_paths, validate_abi, + validate_storage_slots, checker, }) } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.lock new file mode 100644 index 00000000000..a7f26464081 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.lock @@ -0,0 +1,9 @@ +[[package]] +name = 'core' +source = 'path+from-root-BC4E87DB22234D76' +dependencies = [] + +[[package]] +name = 'repeated_storage_field' +source = 'root' +dependencies = ['core'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.toml new file mode 100644 index 00000000000..38cf33b2e6f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "repeated_storage_field" + +[dependencies] +core = { path = "../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/src/main.sw new file mode 100644 index 00000000000..45059447327 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/src/main.sw @@ -0,0 +1,19 @@ +contract; + +use core::*; + +storage { + x: u64 = 5 + 5, + y: u64 = 5 + 5, +} + +abi Test { + fn foo(); +} + +impl Test for Contract { + fn foo() { + storage.x += 1; + storage.y += 1; + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/test.toml new file mode 100644 index 00000000000..590c5b515ca --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/nonconst_storage_init/test.toml @@ -0,0 +1,9 @@ +category = "fail" + +# check: x: u64 = 5 + 5 +# nextln: $()Could not evaluate initializer to a const declaration. +# nextln: y: u64 = 5 + 5 + +# check: x: u64 = 5 + 5 +# nextln: y: u64 = 5 + 5 +# nextln: $()Could not evaluate initializer to a const declaration. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw index 3b04f6fd4dc..b27bdd4318b 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw @@ -1,11 +1,55 @@ script; use storage_access_abi::*; -use std::{assert::assert, hash::sha256}; +use std::{assert::assert, hash::sha256, revert::revert}; fn main() -> bool { - let contract_id = 0x41b881e842026ed4f607156ddc0f98866944c4c67478ededb48932c578ddd52c; + let contract_id = 0x1c7c76380ef43c048596b4cda60eff2763d7c081b70d8662a3e1d18a9ee1dc2b; let caller = abi(StorageAccess, contract_id); + // Test initializers + assert(caller.get_x() == 64); + assert(caller.get_y() == 0x0101010101010101010101010101010101010101010101010101010101010101); + assert(caller.get_boolean() == true); + assert(caller.get_int8() == 8); + assert(caller.get_int16() == 16); + assert(caller.get_int32() == 32); + let s = caller.get_s(); + assert(s.x == 1); + assert(s.y == 2); + assert(s.z == 0x0000000000000000000000000000000000000000000000000000000000000003); + assert(s.t.x == 4); + assert(s.t.y == 5); + assert(s.t.z == 0x0000000000000000000000000000000000000000000000000000000000000006); + assert(s.t.boolean == true); + assert(s.t.int8 == 7); + assert(s.t.int16 == 8); + assert(s.t.int32 == 9); + let e = caller.get_e(); + match e { + E::B(t) => { + assert(t.x == 1); + assert(t.y == 2); + assert(t.z == 0x0000000000000000000000000000000000000000000000000000000000000003); + assert(t.boolean == true); + assert(t.int8 == 4); + assert(t.int16 == 5); + assert(t.int32 == 6); + } + _ => { + revert(0) + } + } + let e2 = caller.get_e2(); + match e2 { + E::A(val) => { + assert(val == 777); + } + _ => { + revert(0) + } + } + assert(sha256(caller.get_string()) == sha256("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")); + // Test 1 caller.set_x(1); assert(caller.get_x() == 1); @@ -142,6 +186,7 @@ fn main() -> bool { let e = caller.get_e(); match e { E::A(val) => assert(val == 42), _ => { + revert(0) } } @@ -158,6 +203,7 @@ fn main() -> bool { assert(val.int32 == t.int32); } _ => { + revert(0) } }; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/storage_access_abi/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/storage_access_abi/src/main.sw index c9bb33696ee..5fae960988d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/storage_access_abi/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_abis/storage_access_abi/src/main.sw @@ -105,6 +105,8 @@ abi StorageAccess { #[storage(read)] fn get_e() -> E; #[storage(read)] + fn get_e2() -> E; + #[storage(read)] fn get_string() -> str[40]; // Operations diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/abi_with_tuples_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/array_of_structs_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/auth_testing_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/balance_test_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/basic_storage/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/context_testing_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/get_storage_key_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/increment_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/issue_1512_repro/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/multiple_impl/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/nested_struct_args_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_abi_oracle.json index 032838bc581..e035b1b6003 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_abi_oracle.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_abi_oracle.json @@ -867,6 +867,65 @@ ], "type": "function" }, + { + "inputs": [], + "name": "get_e2", + "outputs": [ + { + "components": [ + { + "components": null, + "name": "A", + "type": "u64" + }, + { + "components": [ + { + "components": null, + "name": "x", + "type": "u64" + }, + { + "components": null, + "name": "y", + "type": "u64" + }, + { + "components": null, + "name": "z", + "type": "b256" + }, + { + "components": null, + "name": "boolean", + "type": "bool" + }, + { + "components": null, + "name": "int8", + "type": "u8" + }, + { + "components": null, + "name": "int16", + "type": "u16" + }, + { + "components": null, + "name": "int32", + "type": "u32" + } + ], + "name": "B", + "type": "struct T" + } + ], + "name": "", + "type": "enum E" + } + ], + "type": "function" + }, { "inputs": [], "name": "get_string", diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..08c003c88c8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/json_storage_slots_oracle.json @@ -0,0 +1,106 @@ +[ + { + "key": "02dac99c283f16bc91b74f6942db7f012699a2ad51272b15207b9cc14a70dbae", + "value": "0000000000000001000000000000000000000000000000000000000000000000" + }, + { + "key": "126435532f2d2faed6fdd08cea385e77766b42cebae52c892908ba163ffd9484", + "value": "0000000000000000000000000000000000000000000000000000000000000003" + }, + { + "key": "2e92e2a58ff87833010c4cb205f65aa14fa39f799ffc3809bd4a7014b131bc93", + "value": "0000000000000001000000000000000000000000000000000000000000000000" + }, + { + "key": "57bba8e7ea11ac802968230c7c3c09485b488cc84009e80055f938a2cebeb0e6", + "value": "0000000000000001000000000000000200000000000000000000000000000000" + }, + { + "key": "57bba8e7ea11ac802968230c7c3c09485b488cc84009e80055f938a2cebeb0e7", + "value": "0000000000000000000000000000000300000000000000010000000000000004" + }, + { + "key": "57bba8e7ea11ac802968230c7c3c09485b488cc84009e80055f938a2cebeb0e8", + "value": "0000000000000005000000000000000600000000000000000000000000000000" + }, + { + "key": "6294951dcb0a9111a517be5cf4785670ff4e166fb5ab9c33b17e6881b48e964f", + "value": "0000000000000008000000000000000000000000000000000000000000000000" + }, + { + "key": "678de5d61f6f4690357e7868bca285aa8faf5bc9d2126bd8e5006a3ee54f0003", + "value": "0000000000000005000000000000000000000000000000000000000000000000" + }, + { + "key": "71c50136ce909d575b4bd2b1505b9b166ace9d514e92b0e6f9a04abfea8e649d", + "value": "0000000000000002000000000000000000000000000000000000000000000000" + }, + { + "key": "7f91d1a929dce734e7f930bbb279ccfccdb5474227502ea8845815c74bd930a7", + "value": "0000000000000020000000000000000000000000000000000000000000000000" + }, + { + "key": "94b2b70d20da552763c7614981b2a4d984380d7ed4e54c01b28c914e79e44bd5", + "value": "0000000000000010000000000000000000000000000000000000000000000000" + }, + { + "key": "9d9209c41faa2ab13bef277e30c3d85b2b95a81816d51614e5816c182ac0a66e", + "value": "0000000000000001000000000000000000000000000000000000000000000000" + }, + { + "key": "ad7293bc17e2debf737147d46a68be4c2487150275ed02c8a59c58d1452e9052", + "value": "0000000000000009000000000000000000000000000000000000000000000000" + }, + { + "key": "af75026d46742957dd1a310d00030e0c7099f07760a0f18d84a541ebf8a0c244", + "value": "0000000000000000000000000000000000000000000000000000000000000006" + }, + { + "key": "b0cfa187f470ad0b95c1e20154630783abb9019127755afbe4fb5e2382888ba6", + "value": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "key": "b0cfa187f470ad0b95c1e20154630783abb9019127755afbe4fb5e2382888ba7", + "value": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "key": "b0cfa187f470ad0b95c1e20154630783abb9019127755afbe4fb5e2382888ba8", + "value": "0000000000000000000000000000030900000000000000000000000000000000" + }, + { + "key": "c28432da64bb717a7d85e34191fc1e50f031b9689c808b653ef3a8328a8e1002", + "value": "0000000000000004000000000000000000000000000000000000000000000000" + }, + { + "key": "c497c29a5cb465e7baae51571dfdcd1d47dae5fb4258de06d4f395487d441468", + "value": "0000000000000007000000000000000000000000000000000000000000000000" + }, + { + "key": "c5e69153be998bc6f957aeb6f8fd46a0e9c5bc2d3dff421a73e02f64a3012fbb", + "value": "4141414141414141414141414141414141414141414141414141414141414141" + }, + { + "key": "c5e69153be998bc6f957aeb6f8fd46a0e9c5bc2d3dff421a73e02f64a3012fbc", + "value": "4141414141414141000000000000000000000000000000000000000000000000" + }, + { + "key": "d55bcd857a8d6a72e6ba8a7aacbf56161e266c2418af5c06c9d1907bbca2624b", + "value": "0000000000000001000000000000000000000000000000000000000000000000" + }, + { + "key": "de9090cb50e71c2588c773487d1da7066d0c719849a7e58dc8b6397a25c567c0", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "key": "e945e3646bf99ebf2a70bf0c8151349dab2a54abac6450f0ee9fad896c308871", + "value": "0000000000000008000000000000000000000000000000000000000000000000" + }, + { + "key": "ea9d1ab55216336383fedadabe3c23a4df23267279ea294a547ca1006371746f", + "value": "0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "key": "f383b0ce51358be57daa3b725fe44acdb2d880604e367199080b4379c41bb6ed", + "value": "0000000000000040000000000000000000000000000000000000000000000000" + } +] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/src/main.sw index b507509cbbd..6b064d88e05 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/src/main.sw @@ -3,205 +3,184 @@ contract; use storage_access_abi::{E, S, StorageAccess, T}; storage { - x: u64, - y: b256, - s: S, - boolean: bool, - int8: u8, - int16: u16, - int32: u32, - e: E, - string: str[40], + x: u64 = 64, + y: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101, + s: S = S { + x: 1, + y: 2, + z: 0x0000000000000000000000000000000000000000000000000000000000000003, + t: T { + x: 4, + y: 5, + z: 0x0000000000000000000000000000000000000000000000000000000000000006, + boolean: true, + int8: 7, + int16: 8, + int32: 9, + }, + }, + boolean: bool = true, + int8: u8 = 8, + int16: u16 = 16, + int32: u32 = 32, + e: E = E::B(T { + x: 1, + y: 2, + z: 0x0000000000000000000000000000000000000000000000000000000000000003, + boolean: true, + int8: 4, + int16: 5, + int32: 6, + }, + ), e2: E = E::A(777), + string: str[40] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", } impl StorageAccess for Contract { // Setters - #[storage(write)] - fn set_x(x: u64) { + #[storage(write)]fn set_x(x: u64) { storage.x = x; } - #[storage(write)] - fn set_y(y: b256) { + #[storage(write)]fn set_y(y: b256) { storage.y = y; } - #[storage(write)] - fn set_s(s: S) { + #[storage(write)]fn set_s(s: S) { storage.s = s; } - #[storage(write)] - fn set_boolean(boolean: bool) { + #[storage(write)]fn set_boolean(boolean: bool) { storage.boolean = boolean; } - #[storage(write)] - fn set_int8(int8: u8) { + #[storage(write)]fn set_int8(int8: u8) { storage.int8 = int8; } - #[storage(write)] - fn set_int16(int16: u16) { + #[storage(write)]fn set_int16(int16: u16) { storage.int16 = int16; } - #[storage(write)] - fn set_int32(int32: u32) { + #[storage(write)]fn set_int32(int32: u32) { storage.int32 = int32; } - #[storage(write)] - fn set_s_dot_x(x: u64) { + #[storage(write)]fn set_s_dot_x(x: u64) { storage.s.x = x; } - #[storage(write)] - fn set_s_dot_y(y: u64) { + #[storage(write)]fn set_s_dot_y(y: u64) { storage.s.y = y; } - #[storage(write)] - fn set_s_dot_z(z: b256) { + #[storage(write)]fn set_s_dot_z(z: b256) { storage.s.z = z; } - #[storage(write)] - fn set_s_dot_t(t: T) { + #[storage(write)]fn set_s_dot_t(t: T) { storage.s.t = t; } - #[storage(write)] - fn set_s_dot_t_dot_x(x: u64) { + #[storage(write)]fn set_s_dot_t_dot_x(x: u64) { storage.s.t.x = x; } - #[storage(write)] - fn set_s_dot_t_dot_y(y: u64) { + #[storage(write)]fn set_s_dot_t_dot_y(y: u64) { storage.s.t.y = y; } - #[storage(write)] - fn set_s_dot_t_dot_z(z: b256) { + #[storage(write)]fn set_s_dot_t_dot_z(z: b256) { storage.s.t.z = z; } - #[storage(write)] - fn set_s_dot_t_dot_boolean(boolean: bool) { + #[storage(write)]fn set_s_dot_t_dot_boolean(boolean: bool) { storage.s.t.boolean = boolean; } - #[storage(write)] - fn set_s_dot_t_dot_int8(int8: u8) { + #[storage(write)]fn set_s_dot_t_dot_int8(int8: u8) { storage.s.t.int8 = int8; } - #[storage(write)] - fn set_s_dot_t_dot_int16(int16: u16) { + #[storage(write)]fn set_s_dot_t_dot_int16(int16: u16) { storage.s.t.int16 = int16; } - #[storage(write)] - fn set_s_dot_t_dot_int32(int32: u32) { + #[storage(write)]fn set_s_dot_t_dot_int32(int32: u32) { storage.s.t.int32 = int32; } - #[storage(write)] - fn set_e(e: E) { + #[storage(write)]fn set_e(e: E) { storage.e = e; } - #[storage(write)] - fn set_string(string: str[40]) { + #[storage(write)]fn set_string(string: str[40]) { storage.string = string; } // Getters - #[storage(read)] - fn get_x() -> u64 { + #[storage(read)]fn get_x() -> u64 { storage.x } - #[storage(read)] - fn get_y() -> b256 { + #[storage(read)]fn get_y() -> b256 { storage.y } - #[storage(read)] - fn get_s() -> S { + #[storage(read)]fn get_s() -> S { storage.s } - #[storage(read)] - fn get_boolean() -> bool { + #[storage(read)]fn get_boolean() -> bool { storage.boolean } - #[storage(read)] - fn get_int8() -> u8 { + #[storage(read)]fn get_int8() -> u8 { storage.int8 } - #[storage(read)] - fn get_int16() -> u16 { + #[storage(read)]fn get_int16() -> u16 { storage.int16 } - #[storage(read)] - fn get_int32() -> u32 { + #[storage(read)]fn get_int32() -> u32 { storage.int32 } - #[storage(read)] - fn get_s_dot_x() -> u64 { + #[storage(read)]fn get_s_dot_x() -> u64 { storage.s.x } - #[storage(read)] - fn get_s_dot_y() -> u64 { + #[storage(read)]fn get_s_dot_y() -> u64 { storage.s.y } - #[storage(read)] - fn get_s_dot_z() -> b256 { + #[storage(read)]fn get_s_dot_z() -> b256 { storage.s.z } - #[storage(read)] - fn get_s_dot_t() -> T { + #[storage(read)]fn get_s_dot_t() -> T { storage.s.t } - #[storage(read)] - fn get_s_dot_t_dot_x() -> u64 { + #[storage(read)]fn get_s_dot_t_dot_x() -> u64 { storage.s.t.x } - #[storage(read)] - fn get_s_dot_t_dot_y() -> u64 { + #[storage(read)]fn get_s_dot_t_dot_y() -> u64 { storage.s.t.y } - #[storage(read)] - fn get_s_dot_t_dot_z() -> b256 { + #[storage(read)]fn get_s_dot_t_dot_z() -> b256 { storage.s.t.z } - #[storage(read)] - fn get_s_dot_t_dot_boolean() -> bool { + #[storage(read)]fn get_s_dot_t_dot_boolean() -> bool { storage.s.t.boolean } - #[storage(read)] - fn get_s_dot_t_dot_int8() -> u8 { + #[storage(read)]fn get_s_dot_t_dot_int8() -> u8 { storage.s.t.int8 } - #[storage(read)] - fn get_s_dot_t_dot_int16() -> u16 { + #[storage(read)]fn get_s_dot_t_dot_int16() -> u16 { storage.s.t.int16 } - #[storage(read)] - fn get_s_dot_t_dot_int32() -> u32 { + #[storage(read)]fn get_s_dot_t_dot_int32() -> u32 { storage.s.t.int32 } - #[storage(read)] - fn get_e() -> E { + #[storage(read)]fn get_e() -> E { storage.e } - #[storage(read)] - fn get_string() -> str[40] { + #[storage(read)]fn get_e2() -> E { + storage.e2 + } + #[storage(read)]fn get_string() -> str[40] { storage.string } // Operations - #[storage(read, write)] - fn add_to_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn add_to_s_dot_t_dot_x(k: u64) { storage.s.t.x += k; } - #[storage(read, write)] - fn subtract_from_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn subtract_from_s_dot_t_dot_x(k: u64) { storage.s.t.x -= k; } - #[storage(read, write)] - fn multiply_by_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn multiply_by_s_dot_t_dot_x(k: u64) { storage.s.t.x *= k; } - #[storage(read, write)] - fn divide_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn divide_s_dot_t_dot_x(k: u64) { storage.s.t.x /= k; } - #[storage(read, write)] - fn shift_left_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn shift_left_s_dot_t_dot_x(k: u64) { storage.s.t.x <<= k; } - #[storage(read, write)] - fn shift_right_s_dot_t_dot_x(k: u64) { + #[storage(read, write)]fn shift_right_s_dot_t_dot_x(k: u64) { storage.s.t.x >>= k; } } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/storage_access_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/json_storage_slots_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/json_storage_slots_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/json_storage_slots_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/test.toml index 6691150d9c8..b9d57f4d5f1 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/test_contracts/test_fuel_coin_contract/test.toml @@ -1,2 +1,3 @@ category = "compile" validate_abi = true +validate_storage_slots = true