diff --git a/.changes/added/982.md b/.changes/added/982.md new file mode 100644 index 0000000000..22c610c071 --- /dev/null +++ b/.changes/added/982.md @@ -0,0 +1 @@ +New storage opcodes `SCLR`, `SRDD`, `SRDI`, `SWRD`, `SWRI`, `SUPD`, `SUPI`, `SPLD` and `SPCP` that allow working with variably-sized storage slots. \ No newline at end of file diff --git a/.changes/changed/982.md b/.changes/changed/982.md new file mode 100644 index 0000000000..d64d6ea2bb --- /dev/null +++ b/.changes/changed/982.md @@ -0,0 +1 @@ +`SRW` instruction now allows and offset argument. All existing storage operations are internally changed to function with variably sized slots, maintaining full backwards compatibility. \ No newline at end of file diff --git a/fuel-asm/src/lib.rs b/fuel-asm/src/lib.rs index f09e74b895..438cd91398 100644 --- a/fuel-asm/src/lib.rs +++ b/fuel-asm/src/lib.rs @@ -1,5 +1,6 @@ //! FuelVM instruction and opcodes representation. +#![recursion_limit = "256"] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "std", doc = include_str!("../README.md"))] @@ -198,7 +199,7 @@ impl_instructions! { "Clear a series of slots from contract storage." 0x37 SCWQ scwq [key_ptr: RegId status: RegId lenq: RegId] "Load a word from contract storage." - 0x38 SRW srw [dst: RegId status: RegId key_ptr: RegId] + 0x38 SRW srw [dst: RegId status: RegId key_ptr: RegId imm: Imm06] "Load a series of 32 byte slots from contract storage." 0x39 SRWQ srwq [dst_ptr: RegId status: RegId key_ptr: RegId lenq: RegId] "Store a word in contract storage." @@ -362,6 +363,25 @@ impl_instructions! { 0xbc ECOP ecop [dst_ptr: RegId curve_id: RegId operation_type: RegId points_ptr: RegId] "Given some curve, performs a pairing on groups of points" 0xbe EPAR epar [success: RegId curve_id: RegId number_elements: RegId points_ptr: RegId] + + "Clear a storage slot" + 0xc0 SCLR sclr [key_ptr: RegId count: RegId] + "Read storage slot (register length)" + 0xc1 SRDD srdd [dst_ptr: RegId key_ptr: RegId offset: RegId len: RegId] + "Read storage slot (immediate length)" + 0xc2 SRDI srdi [dst_ptr: RegId key_ptr: RegId offset: RegId len: Imm06] + "Write to a storage slot (full overwrite) (register length)" + 0xc3 SWRD swrd [key_ptr: RegId value_ptr: RegId len: RegId] + "Write to a storage slot (full overwrite) (immedidate length)" + 0xc4 SWRI swri [key_ptr: RegId value_ptr: RegId len: Imm12] + "Update a storage slot (read+write) (register length)" + 0xc5 SUPD supd [key_ptr: RegId value_ptr: RegId offset: RegId len: RegId] + "Update a storage slot (read+write) (immedidate length)" + 0xc6 SUPI supi [key_ptr: RegId value_ptr: RegId offset: RegId len: Imm06] + "Storage preload" + 0xc7 SPLD spld [len_dst: RegId key_ptr: RegId] + "Copy from preloaded storage slot" + 0xc8 SPCP spcp [dst: RegId offset: RegId len_reg: RegId len_imm: Imm06] } impl Instruction { @@ -1010,8 +1030,9 @@ fn check_predicate_allowed() { if let Ok(repr) = Opcode::try_from(byte) { let should_allow = match repr { BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LOG | LOGD - | MINT | RETD | RVRT | SMO | SCWQ | SRW | SRWQ | SWW | SWWQ | TIME - | TR | TRO => false, + | MINT | RETD | RVRT | SMO | SCWQ | SRW | SRWQ | SWW | SWWQ | SCLR + | SRDD | SRDI | SWRD | SWRI | SUPD | SUPI | SPLD | SPCP | TIME | TR + | TRO => false, _ => true, }; assert_eq!(should_allow, repr.is_predicate_allowed()); diff --git a/fuel-asm/src/panic_reason.rs b/fuel-asm/src/panic_reason.rs index 22fe4a776c..5918a7939d 100644 --- a/fuel-asm/src/panic_reason.rs +++ b/fuel-asm/src/panic_reason.rs @@ -170,6 +170,8 @@ enum_from! { CanNotGetGasPriceInPredicate = 0x40, /// The owner of the transaction is unknown OwnerIsUnknown = 0x41, + /// Storage operation out of bounds or exceeds maximum allowed size + StorageOutOfBounds = 0x42, } } diff --git a/fuel-tx/src/transaction/consensus_parameters.rs b/fuel-tx/src/transaction/consensus_parameters.rs index 64b73bce26..9ee862d4e1 100644 --- a/fuel-tx/src/transaction/consensus_parameters.rs +++ b/fuel-tx/src/transaction/consensus_parameters.rs @@ -818,39 +818,66 @@ impl Default for TxParametersV1 { )] pub enum ScriptParameters { V1(ScriptParametersV1), + V2(ScriptParametersV2), } impl ScriptParameters { #[cfg(feature = "test-helpers")] /// Default parameters just for testing. - pub const DEFAULT: Self = Self::V1(ScriptParametersV1::DEFAULT); + pub const DEFAULT: Self = Self::V2(ScriptParametersV2::DEFAULT); /// Replace the max script length with the given argument + #[cfg(feature = "test-helpers")] pub const fn with_max_script_length(self, max_script_length: u64) -> Self { match self { Self::V1(mut params) => { params.max_script_length = max_script_length; Self::V1(params) } + Self::V2(mut params) => { + params.max_script_length = max_script_length; + Self::V2(params) + } } } /// Replace the max script data length with the given argument + #[cfg(feature = "test-helpers")] pub const fn with_max_script_data_length(self, max_script_data_length: u64) -> Self { match self { Self::V1(mut params) => { params.max_script_data_length = max_script_data_length; Self::V1(params) } + Self::V2(mut params) => { + params.max_script_data_length = max_script_data_length; + Self::V2(params) + } + } + } + + /// Replace the max + #[cfg(feature = "test-helpers")] + pub const fn with_max_storage_slot_length( + self, + max_storage_slot_length: u64, + ) -> Self { + match self { + Self::V1(_) => { + panic!("ScriptParametersV1 does not support max_storage_slot_length"); + } + Self::V2(mut params) => { + params.max_storage_slot_length = max_storage_slot_length; + Self::V2(params) + } } } -} -impl ScriptParameters { /// Get the maximum script length pub const fn max_script_length(&self) -> u64 { match self { Self::V1(params) => params.max_script_length, + Self::V2(params) => params.max_script_length, } } @@ -858,13 +885,16 @@ impl ScriptParameters { pub const fn max_script_data_length(&self) -> u64 { match self { Self::V1(params) => params.max_script_data_length, + Self::V2(params) => params.max_script_data_length, } } -} -impl From for ScriptParameters { - fn from(params: ScriptParametersV1) -> Self { - Self::V1(params) + /// Get the maximum script data length + pub const fn max_storage_slot_length(&self) -> u64 { + match self { + Self::V1(_) => 32, // Legacy slots are 32 bytes + Self::V2(params) => params.max_storage_slot_length, + } } } @@ -885,6 +915,12 @@ pub struct ScriptParametersV1 { pub max_script_data_length: u64, } +impl From for ScriptParameters { + fn from(params: ScriptParametersV1) -> Self { + Self::V1(params) + } +} + #[cfg(feature = "test-helpers")] impl ScriptParametersV1 { /// Default parameters just for testing. @@ -901,6 +937,43 @@ impl Default for ScriptParametersV1 { } } +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, +)] +pub struct ScriptParametersV2 { + /// Maximum length of script, in instructions. + pub max_script_length: u64, + /// Maximum length of script data, in bytes. + pub max_script_data_length: u64, + /// Maximum length of a storage slot value, in bytes. + /// Note that if this is set to less than 32, the legacy storage operations will + /// still allow using 32-byte slots. + pub max_storage_slot_length: u64, +} + +impl From for ScriptParameters { + fn from(params: ScriptParametersV2) -> Self { + Self::V2(params) + } +} + +#[cfg(feature = "test-helpers")] +impl ScriptParametersV2 { + /// Default parameters just for testing. + pub const DEFAULT: Self = Self { + max_script_length: 1024 * 1024, + max_script_data_length: 1024 * 1024, + max_storage_slot_length: 1024 * 1024, + }; +} + +#[cfg(feature = "test-helpers")] +impl Default for ScriptParametersV2 { + fn default() -> Self { + Self::DEFAULT + } +} + /// Versioned contract parameters. #[derive( Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, diff --git a/fuel-tx/src/transaction/consensus_parameters/gas.rs b/fuel-tx/src/transaction/consensus_parameters/gas.rs index 82e7172d2c..cb18d74ac7 100644 --- a/fuel-tx/src/transaction/consensus_parameters/gas.rs +++ b/fuel-tx/src/transaction/consensus_parameters/gas.rs @@ -84,6 +84,8 @@ pub enum GasCostsValues { V5(GasCostsValuesV5), /// Version 6 of the gas costs. V6(GasCostsValuesV6), + /// Version 7 of the gas costs. + V7(GasCostsValuesV7), } /// Gas cost for this instruction is not defined for this version. @@ -105,6 +107,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.add, GasCostsValues::V5(v5) => v5.add, GasCostsValues::V6(v6) => v6.add, + GasCostsValues::V7(v7) => v7.add, } } @@ -116,6 +119,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.addi, GasCostsValues::V5(v5) => v5.addi, GasCostsValues::V6(v6) => v6.addi, + GasCostsValues::V7(v7) => v7.addi, } } @@ -127,6 +131,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.and, GasCostsValues::V5(v5) => v5.and, GasCostsValues::V6(v6) => v6.and, + GasCostsValues::V7(v7) => v7.and, } } @@ -138,6 +143,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.andi, GasCostsValues::V5(v5) => v5.andi, GasCostsValues::V6(v6) => v6.andi, + GasCostsValues::V7(v7) => v7.andi, } } @@ -149,6 +155,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.bal, GasCostsValues::V5(v5) => v5.bal, GasCostsValues::V6(v6) => v6.bal, + GasCostsValues::V7(v7) => v7.bal, } } @@ -160,6 +167,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.bhei, GasCostsValues::V5(v5) => v5.bhei, GasCostsValues::V6(v6) => v6.bhei, + GasCostsValues::V7(v7) => v7.bhei, } } @@ -171,6 +179,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.bhsh, GasCostsValues::V5(v5) => v5.bhsh, GasCostsValues::V6(v6) => v6.bhsh, + GasCostsValues::V7(v7) => v7.bhsh, } } @@ -182,6 +191,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.burn, GasCostsValues::V5(v5) => v5.burn, GasCostsValues::V6(v6) => v6.burn, + GasCostsValues::V7(v7) => v7.burn, } } @@ -193,6 +203,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.cb, GasCostsValues::V5(v5) => v5.cb, GasCostsValues::V6(v6) => v6.cb, + GasCostsValues::V7(v7) => v7.cb, } } @@ -204,6 +215,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.cfsi, GasCostsValues::V5(v5) => v5.cfsi, GasCostsValues::V6(v6) => v6.cfsi, + GasCostsValues::V7(v7) => v7.cfsi, } } @@ -215,6 +227,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.div, GasCostsValues::V5(v5) => v5.div, GasCostsValues::V6(v6) => v6.div, + GasCostsValues::V7(v7) => v7.div, } } @@ -226,6 +239,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.divi, GasCostsValues::V5(v5) => v5.divi, GasCostsValues::V6(v6) => v6.divi, + GasCostsValues::V7(v7) => v7.divi, } } @@ -237,6 +251,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.eck1, GasCostsValues::V5(v5) => v5.eck1, GasCostsValues::V6(v6) => v6.eck1, + GasCostsValues::V7(v7) => v7.eck1, } } @@ -248,6 +263,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ecr1, GasCostsValues::V5(v5) => v5.ecr1, GasCostsValues::V6(v6) => v6.ecr1, + GasCostsValues::V7(v7) => v7.ecr1, } } @@ -259,6 +275,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.eq, GasCostsValues::V5(v5) => v5.eq, GasCostsValues::V6(v6) => v6.eq, + GasCostsValues::V7(v7) => v7.eq, } } @@ -270,6 +287,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.exp, GasCostsValues::V5(v5) => v5.exp, GasCostsValues::V6(v6) => v6.exp, + GasCostsValues::V7(v7) => v7.exp, } } @@ -281,6 +299,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.expi, GasCostsValues::V5(v5) => v5.expi, GasCostsValues::V6(v6) => v6.expi, + GasCostsValues::V7(v7) => v7.expi, } } @@ -292,6 +311,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.flag, GasCostsValues::V5(v5) => v5.flag, GasCostsValues::V6(v6) => v6.flag, + GasCostsValues::V7(v7) => v7.flag, } } @@ -303,6 +323,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.gm, GasCostsValues::V5(v5) => v5.gm, GasCostsValues::V6(v6) => v6.gm, + GasCostsValues::V7(v7) => v7.gm, } } @@ -314,6 +335,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.gt, GasCostsValues::V5(v5) => v5.gt, GasCostsValues::V6(v6) => v6.gt, + GasCostsValues::V7(v7) => v7.gt, } } @@ -325,6 +347,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.gtf, GasCostsValues::V5(v5) => v5.gtf, GasCostsValues::V6(v6) => v6.gtf, + GasCostsValues::V7(v7) => v7.gtf, } } @@ -336,6 +359,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ji, GasCostsValues::V5(v5) => v5.ji, GasCostsValues::V6(v6) => v6.ji, + GasCostsValues::V7(v7) => v7.ji, } } @@ -347,6 +371,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jmp, GasCostsValues::V5(v5) => v5.jmp, GasCostsValues::V6(v6) => v6.jmp, + GasCostsValues::V7(v7) => v7.jmp, } } @@ -358,6 +383,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jne, GasCostsValues::V5(v5) => v5.jne, GasCostsValues::V6(v6) => v6.jne, + GasCostsValues::V7(v7) => v7.jne, } } @@ -369,6 +395,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jnei, GasCostsValues::V5(v5) => v5.jnei, GasCostsValues::V6(v6) => v6.jnei, + GasCostsValues::V7(v7) => v7.jnei, } } @@ -380,6 +407,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jnzi, GasCostsValues::V5(v5) => v5.jnzi, GasCostsValues::V6(v6) => v6.jnzi, + GasCostsValues::V7(v7) => v7.jnzi, } } @@ -391,6 +419,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jmpf, GasCostsValues::V5(v5) => v5.jmpf, GasCostsValues::V6(v6) => v6.jmpf, + GasCostsValues::V7(v7) => v7.jmpf, } } @@ -402,6 +431,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jmpb, GasCostsValues::V5(v5) => v5.jmpb, GasCostsValues::V6(v6) => v6.jmpb, + GasCostsValues::V7(v7) => v7.jmpb, } } @@ -413,6 +443,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jnzf, GasCostsValues::V5(v5) => v5.jnzf, GasCostsValues::V6(v6) => v6.jnzf, + GasCostsValues::V7(v7) => v7.jnzf, } } @@ -424,6 +455,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jnzb, GasCostsValues::V5(v5) => v5.jnzb, GasCostsValues::V6(v6) => v6.jnzb, + GasCostsValues::V7(v7) => v7.jnzb, } } @@ -435,6 +467,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jnef, GasCostsValues::V5(v5) => v5.jnef, GasCostsValues::V6(v6) => v6.jnef, + GasCostsValues::V7(v7) => v7.jnef, } } @@ -446,6 +479,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.jneb, GasCostsValues::V5(v5) => v5.jneb, GasCostsValues::V6(v6) => v6.jneb, + GasCostsValues::V7(v7) => v7.jneb, } } @@ -457,6 +491,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.lb, GasCostsValues::V5(v5) => v5.lb, GasCostsValues::V6(v6) => v6.lb, + GasCostsValues::V7(v7) => v7.lb, } } @@ -468,6 +503,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.log, GasCostsValues::V5(v5) => v5.log, GasCostsValues::V6(v6) => v6.log, + GasCostsValues::V7(v7) => v7.log, } } @@ -479,6 +515,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.lt, GasCostsValues::V5(v5) => v5.lt, GasCostsValues::V6(v6) => v6.lt, + GasCostsValues::V7(v7) => v7.lt, } } @@ -490,6 +527,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.lw, GasCostsValues::V5(v5) => v5.lw, GasCostsValues::V6(v6) => v6.lw, + GasCostsValues::V7(v7) => v7.lw, } } @@ -501,6 +539,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mint, GasCostsValues::V5(v5) => v5.mint, GasCostsValues::V6(v6) => v6.mint, + GasCostsValues::V7(v7) => v7.mint, } } @@ -512,6 +551,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mlog, GasCostsValues::V5(v5) => v5.mlog, GasCostsValues::V6(v6) => v6.mlog, + GasCostsValues::V7(v7) => v7.mlog, } } @@ -523,6 +563,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mod_op, GasCostsValues::V5(v5) => v5.mod_op, GasCostsValues::V6(v6) => v6.mod_op, + GasCostsValues::V7(v7) => v7.mod_op, } } @@ -534,6 +575,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.modi, GasCostsValues::V5(v5) => v5.modi, GasCostsValues::V6(v6) => v6.modi, + GasCostsValues::V7(v7) => v7.modi, } } @@ -545,6 +587,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.move_op, GasCostsValues::V5(v5) => v5.move_op, GasCostsValues::V6(v6) => v6.move_op, + GasCostsValues::V7(v7) => v7.move_op, } } @@ -556,6 +599,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.movi, GasCostsValues::V5(v5) => v5.movi, GasCostsValues::V6(v6) => v6.movi, + GasCostsValues::V7(v7) => v7.movi, } } @@ -567,6 +611,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mroo, GasCostsValues::V5(v5) => v5.mroo, GasCostsValues::V6(v6) => v6.mroo, + GasCostsValues::V7(v7) => v7.mroo, } } @@ -578,6 +623,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mul, GasCostsValues::V5(v5) => v5.mul, GasCostsValues::V6(v6) => v6.mul, + GasCostsValues::V7(v7) => v7.mul, } } @@ -589,6 +635,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.muli, GasCostsValues::V5(v5) => v5.muli, GasCostsValues::V6(v6) => v6.muli, + GasCostsValues::V7(v7) => v7.muli, } } @@ -600,6 +647,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mldv, GasCostsValues::V5(v5) => v5.mldv, GasCostsValues::V6(v6) => v6.mldv, + GasCostsValues::V7(v7) => v7.mldv, } } @@ -611,6 +659,7 @@ impl GasCostsValues { GasCostsValues::V4(_) => Err(GasCostNotDefined), GasCostsValues::V5(_) => Err(GasCostNotDefined), GasCostsValues::V6(v6) => Ok(v6.niop), + GasCostsValues::V7(v7) => Ok(v7.niop), } } @@ -622,6 +671,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.noop, GasCostsValues::V5(v5) => v5.noop, GasCostsValues::V6(v6) => v6.noop, + GasCostsValues::V7(v7) => v7.noop, } } @@ -633,6 +683,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.not, GasCostsValues::V5(v5) => v5.not, GasCostsValues::V6(v6) => v6.not, + GasCostsValues::V7(v7) => v7.not, } } @@ -644,6 +695,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.or, GasCostsValues::V5(v5) => v5.or, GasCostsValues::V6(v6) => v6.or, + GasCostsValues::V7(v7) => v7.or, } } @@ -655,6 +707,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ori, GasCostsValues::V5(v5) => v5.ori, GasCostsValues::V6(v6) => v6.ori, + GasCostsValues::V7(v7) => v7.ori, } } @@ -666,6 +719,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.poph, GasCostsValues::V5(v5) => v5.poph, GasCostsValues::V6(v6) => v6.poph, + GasCostsValues::V7(v7) => v7.poph, } } @@ -677,6 +731,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.popl, GasCostsValues::V5(v5) => v5.popl, GasCostsValues::V6(v6) => v6.popl, + GasCostsValues::V7(v7) => v7.popl, } } @@ -688,6 +743,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.pshh, GasCostsValues::V5(v5) => v5.pshh, GasCostsValues::V6(v6) => v6.pshh, + GasCostsValues::V7(v7) => v7.pshh, } } @@ -699,6 +755,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.pshl, GasCostsValues::V5(v5) => v5.pshl, GasCostsValues::V6(v6) => v6.pshl, + GasCostsValues::V7(v7) => v7.pshl, } } @@ -710,6 +767,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ret, GasCostsValues::V5(v5) => v5.ret, GasCostsValues::V6(v6) => v6.ret, + GasCostsValues::V7(v7) => v7.ret, } } @@ -721,6 +779,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.rvrt, GasCostsValues::V5(v5) => v5.rvrt, GasCostsValues::V6(v6) => v6.rvrt, + GasCostsValues::V7(v7) => v7.rvrt, } } @@ -732,6 +791,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.sb, GasCostsValues::V5(v5) => v5.sb, GasCostsValues::V6(v6) => v6.sb, + GasCostsValues::V7(v7) => v7.sb, } } @@ -743,6 +803,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.sll, GasCostsValues::V5(v5) => v5.sll, GasCostsValues::V6(v6) => v6.sll, + GasCostsValues::V7(v7) => v7.sll, } } @@ -754,6 +815,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.slli, GasCostsValues::V5(v5) => v5.slli, GasCostsValues::V6(v6) => v6.slli, + GasCostsValues::V7(v7) => v7.slli, } } @@ -765,6 +827,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.srl, GasCostsValues::V5(v5) => v5.srl, GasCostsValues::V6(v6) => v6.srl, + GasCostsValues::V7(v7) => v7.srl, } } @@ -776,6 +839,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.srli, GasCostsValues::V5(v5) => v5.srli, GasCostsValues::V6(v6) => v6.srli, + GasCostsValues::V7(v7) => v7.srli, } } @@ -787,6 +851,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.srw, GasCostsValues::V5(v5) => v5.srw, GasCostsValues::V6(v6) => v6.srw, + GasCostsValues::V7(v7) => v7.srw, } } @@ -798,6 +863,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.sub, GasCostsValues::V5(v5) => v5.sub, GasCostsValues::V6(v6) => v6.sub, + GasCostsValues::V7(v7) => v7.sub, } } @@ -809,6 +875,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.subi, GasCostsValues::V5(v5) => v5.subi, GasCostsValues::V6(v6) => v6.subi, + GasCostsValues::V7(v7) => v7.subi, } } @@ -820,6 +887,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.sw, GasCostsValues::V5(v5) => v5.sw, GasCostsValues::V6(v6) => v6.sw, + GasCostsValues::V7(v7) => v7.sw, } } @@ -831,6 +899,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.sww, GasCostsValues::V5(v5) => v5.sww, GasCostsValues::V6(v6) => v6.sww, + GasCostsValues::V7(v7) => v7.sww, } } @@ -842,6 +911,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.time, GasCostsValues::V5(v5) => v5.time, GasCostsValues::V6(v6) => v6.time, + GasCostsValues::V7(v7) => v7.time, } } @@ -853,6 +923,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.tr, GasCostsValues::V5(v5) => v5.tr, GasCostsValues::V6(v6) => v6.tr, + GasCostsValues::V7(v7) => v7.tr, } } @@ -864,6 +935,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.tro, GasCostsValues::V5(v5) => v5.tro, GasCostsValues::V6(v6) => v6.tro, + GasCostsValues::V7(v7) => v7.tro, } } @@ -875,6 +947,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdcm, GasCostsValues::V5(v5) => v5.wdcm, GasCostsValues::V6(v6) => v6.wdcm, + GasCostsValues::V7(v7) => v7.wdcm, } } @@ -886,6 +959,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqcm, GasCostsValues::V5(v5) => v5.wqcm, GasCostsValues::V6(v6) => v6.wqcm, + GasCostsValues::V7(v7) => v7.wqcm, } } @@ -897,6 +971,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdop, GasCostsValues::V5(v5) => v5.wdop, GasCostsValues::V6(v6) => v6.wdop, + GasCostsValues::V7(v7) => v7.wdop, } } @@ -908,6 +983,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqop, GasCostsValues::V5(v5) => v5.wqop, GasCostsValues::V6(v6) => v6.wqop, + GasCostsValues::V7(v7) => v7.wqop, } } @@ -919,6 +995,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdml, GasCostsValues::V5(v5) => v5.wdml, GasCostsValues::V6(v6) => v6.wdml, + GasCostsValues::V7(v7) => v7.wdml, } } @@ -930,6 +1007,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqml, GasCostsValues::V5(v5) => v5.wqml, GasCostsValues::V6(v6) => v6.wqml, + GasCostsValues::V7(v7) => v7.wqml, } } @@ -941,6 +1019,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wddv, GasCostsValues::V5(v5) => v5.wddv, GasCostsValues::V6(v6) => v6.wddv, + GasCostsValues::V7(v7) => v7.wddv, } } @@ -952,6 +1031,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqdv, GasCostsValues::V5(v5) => v5.wqdv, GasCostsValues::V6(v6) => v6.wqdv, + GasCostsValues::V7(v7) => v7.wqdv, } } @@ -963,6 +1043,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdmd, GasCostsValues::V5(v5) => v5.wdmd, GasCostsValues::V6(v6) => v6.wdmd, + GasCostsValues::V7(v7) => v7.wdmd, } } @@ -974,6 +1055,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqmd, GasCostsValues::V5(v5) => v5.wqmd, GasCostsValues::V6(v6) => v6.wqmd, + GasCostsValues::V7(v7) => v7.wqmd, } } @@ -985,6 +1067,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdam, GasCostsValues::V5(v5) => v5.wdam, GasCostsValues::V6(v6) => v6.wdam, + GasCostsValues::V7(v7) => v7.wdam, } } @@ -996,6 +1079,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqam, GasCostsValues::V5(v5) => v5.wqam, GasCostsValues::V6(v6) => v6.wqam, + GasCostsValues::V7(v7) => v7.wqam, } } @@ -1007,6 +1091,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wdmm, GasCostsValues::V5(v5) => v5.wdmm, GasCostsValues::V6(v6) => v6.wdmm, + GasCostsValues::V7(v7) => v7.wdmm, } } @@ -1018,6 +1103,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.wqmm, GasCostsValues::V5(v5) => v5.wqmm, GasCostsValues::V6(v6) => v6.wqmm, + GasCostsValues::V7(v7) => v7.wqmm, } } @@ -1029,6 +1115,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.xor, GasCostsValues::V5(v5) => v5.xor, GasCostsValues::V6(v6) => v6.xor, + GasCostsValues::V7(v7) => v7.xor, } } @@ -1040,6 +1127,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.xori, GasCostsValues::V5(v5) => v5.xori, GasCostsValues::V6(v6) => v6.xori, + GasCostsValues::V7(v7) => v7.xori, } } @@ -1051,6 +1139,7 @@ impl GasCostsValues { GasCostsValues::V4(_) => Err(GasCostNotDefined), GasCostsValues::V5(v5) => Ok(v5.ecop), GasCostsValues::V6(v6) => Ok(v6.ecop), + GasCostsValues::V7(v7) => Ok(v7.ecop), } } @@ -1065,6 +1154,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.aloc, GasCostsValues::V5(v5) => v5.aloc, GasCostsValues::V6(v6) => v6.aloc, + GasCostsValues::V7(v7) => v7.aloc, } } @@ -1082,6 +1172,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.cfe, GasCostsValues::V5(v5) => v5.cfe, GasCostsValues::V6(v6) => v6.cfe, + GasCostsValues::V7(v7) => v7.cfe, } } @@ -1099,6 +1190,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.cfei, GasCostsValues::V5(v5) => v5.cfei, GasCostsValues::V6(v6) => v6.cfei, + GasCostsValues::V7(v7) => v7.cfei, } } @@ -1110,6 +1202,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.call, GasCostsValues::V5(v5) => v5.call, GasCostsValues::V6(v6) => v6.call, + GasCostsValues::V7(v7) => v7.call, } } @@ -1121,6 +1214,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ccp, GasCostsValues::V5(v5) => v5.ccp, GasCostsValues::V6(v6) => v6.ccp, + GasCostsValues::V7(v7) => v7.ccp, } } @@ -1132,6 +1226,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.croo, GasCostsValues::V5(v5) => v5.croo, GasCostsValues::V6(v6) => v6.croo, + GasCostsValues::V7(v7) => v7.croo, } } @@ -1143,6 +1238,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.csiz, GasCostsValues::V5(v5) => v5.csiz, GasCostsValues::V6(v6) => v6.csiz, + GasCostsValues::V7(v7) => v7.csiz, } } @@ -1163,6 +1259,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ed19, GasCostsValues::V5(v5) => v5.ed19, GasCostsValues::V6(v6) => v6.ed19, + GasCostsValues::V7(v7) => v7.ed19, } } @@ -1174,6 +1271,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.k256, GasCostsValues::V5(v5) => v5.k256, GasCostsValues::V6(v6) => v6.k256, + GasCostsValues::V7(v7) => v7.k256, } } @@ -1185,6 +1283,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.ldc, GasCostsValues::V5(v5) => v5.ldc, GasCostsValues::V6(v6) => v6.ldc, + GasCostsValues::V7(v7) => v7.ldc, } } @@ -1196,6 +1295,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.logd, GasCostsValues::V5(v5) => v5.logd, GasCostsValues::V6(v6) => v6.logd, + GasCostsValues::V7(v7) => v7.logd, } } @@ -1207,6 +1307,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mcl, GasCostsValues::V5(v5) => v5.mcl, GasCostsValues::V6(v6) => v6.mcl, + GasCostsValues::V7(v7) => v7.mcl, } } @@ -1218,6 +1319,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mcli, GasCostsValues::V5(v5) => v5.mcli, GasCostsValues::V6(v6) => v6.mcli, + GasCostsValues::V7(v7) => v7.mcli, } } @@ -1229,6 +1331,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mcp, GasCostsValues::V5(v5) => v5.mcp, GasCostsValues::V6(v6) => v6.mcp, + GasCostsValues::V7(v7) => v7.mcp, } } @@ -1240,6 +1343,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.mcpi, GasCostsValues::V5(v5) => v5.mcpi, GasCostsValues::V6(v6) => v6.mcpi, + GasCostsValues::V7(v7) => v7.mcpi, } } @@ -1251,6 +1355,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.meq, GasCostsValues::V5(v5) => v5.meq, GasCostsValues::V6(v6) => v6.meq, + GasCostsValues::V7(v7) => v7.meq, } } @@ -1262,6 +1367,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.retd, GasCostsValues::V5(v5) => v5.retd, GasCostsValues::V6(v6) => v6.retd, + GasCostsValues::V7(v7) => v7.retd, } } @@ -1273,6 +1379,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.s256, GasCostsValues::V5(v5) => v5.s256, GasCostsValues::V6(v6) => v6.s256, + GasCostsValues::V7(v7) => v7.s256, } } @@ -1284,6 +1391,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.scwq, GasCostsValues::V5(v5) => v5.scwq, GasCostsValues::V6(v6) => v6.scwq, + GasCostsValues::V7(v7) => v7.scwq, } } @@ -1295,6 +1403,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.smo, GasCostsValues::V5(v5) => v5.smo, GasCostsValues::V6(v6) => v6.smo, + GasCostsValues::V7(v7) => v7.smo, } } @@ -1306,6 +1415,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.srwq, GasCostsValues::V5(v5) => v5.srwq, GasCostsValues::V6(v6) => v6.srwq, + GasCostsValues::V7(v7) => v7.srwq, } } @@ -1317,6 +1427,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.swwq, GasCostsValues::V5(v5) => v5.swwq, GasCostsValues::V6(v6) => v6.swwq, + GasCostsValues::V7(v7) => v7.swwq, } } @@ -1328,6 +1439,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => Ok(v4.bsiz), GasCostsValues::V5(v5) => Ok(v5.bsiz), GasCostsValues::V6(v6) => Ok(v6.bsiz), + GasCostsValues::V7(v7) => Ok(v7.bsiz), } } @@ -1339,6 +1451,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => Ok(v4.bldd), GasCostsValues::V5(v5) => Ok(v5.bldd), GasCostsValues::V6(v6) => Ok(v6.bldd), + GasCostsValues::V7(v7) => Ok(v7.bldd), } } @@ -1350,6 +1463,79 @@ impl GasCostsValues { GasCostsValues::V4(_v4) => Err(GasCostNotDefined), GasCostsValues::V5(v5) => Ok(v5.epar), GasCostsValues::V6(v6) => Ok(v6.epar), + GasCostsValues::V7(v7) => Ok(v7.epar), + } + } + + pub fn sclr(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.sclr), + } + } + + pub fn srdd(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.srdd), + } + } + + pub fn swrd(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.swrd), + } + } + + pub fn supd(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.supd), + } + } + + pub fn spld(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.spld), + } + } + + pub fn spcp(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(_v4) => Err(GasCostNotDefined), + GasCostsValues::V5(_v5) => Err(GasCostNotDefined), + GasCostsValues::V6(_v6) => Err(GasCostNotDefined), + GasCostsValues::V7(v7) => Ok(v7.spcp), } } @@ -1361,6 +1547,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.contract_root, GasCostsValues::V5(v5) => v5.contract_root, GasCostsValues::V6(v6) => v6.contract_root, + GasCostsValues::V7(v7) => v7.contract_root, } } @@ -1372,6 +1559,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.state_root, GasCostsValues::V5(v5) => v5.state_root, GasCostsValues::V6(v6) => v6.state_root, + GasCostsValues::V7(v7) => v7.state_root, } } @@ -1383,6 +1571,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.new_storage_per_byte, GasCostsValues::V5(v5) => v5.new_storage_per_byte, GasCostsValues::V6(v6) => v6.new_storage_per_byte, + GasCostsValues::V7(v7) => v7.new_storage_per_byte, } } @@ -1394,6 +1583,7 @@ impl GasCostsValues { GasCostsValues::V4(v4) => v4.vm_initialization, GasCostsValues::V5(v5) => v5.vm_initialization, GasCostsValues::V6(v6) => v6.vm_initialization, + GasCostsValues::V7(v7) => v7.vm_initialization, } } } @@ -2181,6 +2371,146 @@ pub struct GasCostsValuesV6 { pub vm_initialization: DependentCost, } +/// Gas costs for every op. +/// The difference with [`GasCostsValuesV6`]: +/// - New storage operations +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +#[serde(default = "GasCostsValuesV7::unit")] +pub struct GasCostsValuesV7 { + pub add: Word, + pub addi: Word, + pub and: Word, + pub andi: Word, + pub bal: Word, + pub bhei: Word, + pub bhsh: Word, + pub burn: Word, + pub cb: Word, + pub cfsi: Word, + pub div: Word, + pub divi: Word, + pub eck1: Word, + pub ecr1: Word, + pub eq: Word, + pub exp: Word, + pub expi: Word, + pub flag: Word, + pub gm: Word, + pub gt: Word, + pub gtf: Word, + pub ji: Word, + pub jmp: Word, + pub jne: Word, + pub jnei: Word, + pub jnzi: Word, + pub jmpf: Word, + pub jmpb: Word, + pub jnzf: Word, + pub jnzb: Word, + pub jnef: Word, + pub jneb: Word, + pub lb: Word, + pub log: Word, + pub lt: Word, + pub lw: Word, + pub mint: Word, + pub mlog: Word, + #[serde(rename = "mod")] + pub mod_op: Word, + pub modi: Word, + #[serde(rename = "move")] + pub move_op: Word, + pub movi: Word, + pub mroo: Word, + pub mul: Word, + pub muli: Word, + pub mldv: Word, + pub niop: Word, + pub noop: Word, + pub not: Word, + pub or: Word, + pub ori: Word, + pub poph: Word, + pub popl: Word, + pub pshh: Word, + pub pshl: Word, + #[serde(rename = "ret_contract")] + pub ret: Word, + #[serde(rename = "rvrt_contract")] + pub rvrt: Word, + pub sb: Word, + pub sll: Word, + pub slli: Word, + pub srl: Word, + pub srli: Word, + pub srw: Word, + pub sub: Word, + pub subi: Word, + pub sw: Word, + pub sww: Word, + pub time: Word, + pub tr: Word, + pub tro: Word, + pub wdcm: Word, + pub wqcm: Word, + pub wdop: Word, + pub wqop: Word, + pub wdml: Word, + pub wqml: Word, + pub wddv: Word, + pub wqdv: Word, + pub wdmd: Word, + pub wqmd: Word, + pub wdam: Word, + pub wqam: Word, + pub wdmm: Word, + pub wqmm: Word, + pub xor: Word, + pub xori: Word, + pub ecop: Word, + + // Dependent + pub aloc: DependentCost, + pub bsiz: DependentCost, + pub bldd: DependentCost, + pub cfe: DependentCost, + pub cfei: DependentCost, + pub call: DependentCost, + pub ccp: DependentCost, + pub croo: DependentCost, + pub csiz: DependentCost, + pub ed19: DependentCost, + pub k256: DependentCost, + pub ldc: DependentCost, + pub logd: DependentCost, + pub mcl: DependentCost, + pub mcli: DependentCost, + pub mcp: DependentCost, + pub mcpi: DependentCost, + pub meq: DependentCost, + #[serde(rename = "retd_contract")] + pub retd: DependentCost, + pub s256: DependentCost, + pub scwq: DependentCost, + pub smo: DependentCost, + pub srwq: DependentCost, + pub swwq: DependentCost, + pub epar: DependentCost, + pub sclr: DependentCost, + pub srdd: DependentCost, + pub swrd: DependentCost, + pub supd: DependentCost, + pub spld: DependentCost, + pub spcp: DependentCost, + + // Non-opcode costs + pub contract_root: DependentCost, + pub state_root: DependentCost, + pub new_storage_per_byte: Word, + pub vm_initialization: DependentCost, +} + /// Dependent cost is a cost that depends on the number of units. #[derive( Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, @@ -2225,12 +2555,12 @@ impl GasCosts { impl GasCostsValues { /// Create costs that are all set to zero. pub fn free() -> Self { - GasCostsValuesV6::free().into() + GasCostsValuesV7::free().into() } /// Create costs that are all set to one. pub fn unit() -> Self { - GasCostsValuesV6::unit().into() + GasCostsValuesV7::unit().into() } } @@ -3692,6 +4022,268 @@ impl GasCostsValuesV6 { } } +impl GasCostsValuesV7 { + /// Create costs that are all set to zero. + pub fn free() -> Self { + Self { + add: 0, + addi: 0, + and: 0, + andi: 0, + bal: 0, + bhei: 0, + bhsh: 0, + burn: 0, + cb: 0, + cfsi: 0, + div: 0, + divi: 0, + eck1: 0, + ecr1: 0, + eq: 0, + exp: 0, + expi: 0, + flag: 0, + gm: 0, + gt: 0, + gtf: 0, + ji: 0, + jmp: 0, + jne: 0, + jnei: 0, + jnzi: 0, + jmpf: 0, + jmpb: 0, + jnzf: 0, + jnzb: 0, + jnef: 0, + jneb: 0, + lb: 0, + log: 0, + lt: 0, + lw: 0, + mint: 0, + mlog: 0, + mod_op: 0, + modi: 0, + move_op: 0, + movi: 0, + mroo: 0, + mul: 0, + muli: 0, + mldv: 0, + niop: 0, + noop: 0, + not: 0, + or: 0, + ori: 0, + poph: 0, + popl: 0, + pshh: 0, + pshl: 0, + ret: 0, + rvrt: 0, + sb: 0, + sll: 0, + slli: 0, + srl: 0, + srli: 0, + srw: 0, + sub: 0, + subi: 0, + sw: 0, + sww: 0, + time: 0, + tr: 0, + tro: 0, + wdcm: 0, + wqcm: 0, + wdop: 0, + wqop: 0, + wdml: 0, + wqml: 0, + wddv: 0, + wqdv: 0, + wdmd: 0, + wqmd: 0, + wdam: 0, + wqam: 0, + wdmm: 0, + wqmm: 0, + xor: 0, + xori: 0, + ecop: 0, + aloc: DependentCost::free(), + bsiz: DependentCost::free(), + bldd: DependentCost::free(), + cfe: DependentCost::free(), + cfei: DependentCost::free(), + call: DependentCost::free(), + ccp: DependentCost::free(), + croo: DependentCost::free(), + csiz: DependentCost::free(), + ed19: DependentCost::free(), + k256: DependentCost::free(), + ldc: DependentCost::free(), + logd: DependentCost::free(), + mcl: DependentCost::free(), + mcli: DependentCost::free(), + mcp: DependentCost::free(), + mcpi: DependentCost::free(), + meq: DependentCost::free(), + retd: DependentCost::free(), + s256: DependentCost::free(), + scwq: DependentCost::free(), + smo: DependentCost::free(), + srwq: DependentCost::free(), + swwq: DependentCost::free(), + epar: DependentCost::free(), + sclr: DependentCost::free(), + srdd: DependentCost::free(), + swrd: DependentCost::free(), + supd: DependentCost::free(), + spld: DependentCost::free(), + spcp: DependentCost::free(), + + // Non-opcode costs + contract_root: DependentCost::free(), + state_root: DependentCost::free(), + new_storage_per_byte: 0, + vm_initialization: DependentCost::free(), + } + } + + /// Create costs that are all set to one. + pub fn unit() -> Self { + Self { + add: 1, + addi: 1, + and: 1, + andi: 1, + bal: 1, + bhei: 1, + bhsh: 1, + burn: 1, + cb: 1, + cfsi: 1, + div: 1, + divi: 1, + eck1: 1, + ecr1: 1, + eq: 1, + exp: 1, + expi: 1, + flag: 1, + gm: 1, + gt: 1, + gtf: 1, + ji: 1, + jmp: 1, + jne: 1, + jnei: 1, + jnzi: 1, + jmpf: 1, + jmpb: 1, + jnzf: 1, + jnzb: 1, + jnef: 1, + jneb: 1, + lb: 1, + log: 1, + lt: 1, + lw: 1, + mint: 1, + mlog: 1, + mod_op: 1, + modi: 1, + move_op: 1, + movi: 1, + mroo: 1, + mul: 1, + muli: 1, + mldv: 1, + niop: 1, + noop: 1, + not: 1, + or: 1, + ori: 1, + ret: 1, + poph: 1, + popl: 1, + pshh: 1, + pshl: 1, + rvrt: 1, + sb: 1, + sll: 1, + slli: 1, + srl: 1, + srli: 1, + srw: 1, + sub: 1, + subi: 1, + sw: 1, + sww: 1, + time: 1, + tr: 1, + tro: 1, + wdcm: 1, + wqcm: 1, + wdop: 1, + wqop: 1, + wdml: 1, + wqml: 1, + wddv: 1, + wqdv: 1, + wdmd: 1, + wqmd: 1, + wdam: 1, + wqam: 1, + wdmm: 1, + wqmm: 1, + xor: 1, + xori: 1, + ecop: 1, + aloc: DependentCost::unit(), + bsiz: DependentCost::unit(), + bldd: DependentCost::unit(), + cfe: DependentCost::unit(), + cfei: DependentCost::unit(), + call: DependentCost::unit(), + ccp: DependentCost::unit(), + croo: DependentCost::unit(), + csiz: DependentCost::unit(), + ed19: DependentCost::unit(), + k256: DependentCost::unit(), + ldc: DependentCost::unit(), + logd: DependentCost::unit(), + mcl: DependentCost::unit(), + mcli: DependentCost::unit(), + mcp: DependentCost::unit(), + mcpi: DependentCost::unit(), + meq: DependentCost::unit(), + retd: DependentCost::unit(), + s256: DependentCost::unit(), + scwq: DependentCost::unit(), + smo: DependentCost::unit(), + srwq: DependentCost::unit(), + swwq: DependentCost::unit(), + epar: DependentCost::unit(), + sclr: DependentCost::unit(), + srdd: DependentCost::unit(), + swrd: DependentCost::unit(), + supd: DependentCost::unit(), + spld: DependentCost::unit(), + spcp: DependentCost::unit(), + + // Non-opcode costs + contract_root: DependentCost::unit(), + state_root: DependentCost::unit(), + new_storage_per_byte: 1, + vm_initialization: DependentCost::unit(), + } + } +} + impl DependentCost { /// Create costs that make operations free. pub fn free() -> Self { @@ -3824,6 +4416,12 @@ impl From for GasCostsValues { } } +impl From for GasCostsValues { + fn from(i: GasCostsValuesV7) -> Self { + GasCostsValues::V7(i) + } +} + #[cfg(test)] mod tests { use crate::DependentCost; diff --git a/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs b/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs index 5e98d6d588..616df19069 100644 --- a/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs +++ b/fuel-tx/src/transaction/consensus_parameters/gas/default_gas_costs.rs @@ -5,7 +5,7 @@ pub const GIT: &str = "98341e564b75d1157e61d7d5f38612f6224a5b30"; /// Modified manually afterwards in https://github.com/FuelLabs/fuel-vm/pull/780 /// and in https://github.com/FuelLabs/fuel-vm/pull/922 pub fn default_gas_costs() -> GasCostsValues { - GasCostsValuesV6 { + GasCostsValuesV7 { add: 1, addi: 1, and: 1, @@ -185,6 +185,32 @@ pub fn default_gas_costs() -> GasCostsValues { base: 44, units_per_gas: 5, }, + + sclr: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + srdd: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + swrd: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + supd: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + spld: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + spcp: DependentCost::LightOperation { + base: 50, + units_per_gas: 5, + }, + bsiz: DependentCost::LightOperation { base: 17, units_per_gas: 790, diff --git a/fuel-tx/src/transaction/types/storage.rs b/fuel-tx/src/transaction/types/storage.rs index 13d9a8a184..b06ed1ac44 100644 --- a/fuel-tx/src/transaction/types/storage.rs +++ b/fuel-tx/src/transaction/types/storage.rs @@ -17,6 +17,8 @@ use rand::{ use core::cmp::Ordering; +/// Legacy storage slot representation. Only used by Create transaction when initializing +/// contract storage. #[derive( Debug, Default, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, )] diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index 2aae193589..2a24cc973b 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -409,6 +409,8 @@ pub struct CheckPredicateParams { pub contract_max_size: u64, /// Maximum length of the message data pub max_message_data_length: u64, + /// Maximum length of a storage slot + pub max_storage_slot_length: u64, /// Offset of the transaction data in the memory pub tx_offset: usize, /// Fee parameters @@ -440,6 +442,7 @@ impl From<&ConsensusParameters> for CheckPredicateParams { max_inputs: value.tx_params().max_inputs(), contract_max_size: value.contract_params().contract_max_size(), max_message_data_length: value.predicate_params().max_message_data_length(), + max_storage_slot_length: value.script_params().max_storage_slot_length(), tx_offset: value.tx_params().tx_offset(), fee_params: *(value.fee_params()), base_asset_id: *value.base_asset_id(), diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index f78b6b7887..f3a7e03ebc 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -74,6 +74,7 @@ mod receipts; mod debug; mod ecal; +mod storage; pub use balances::RuntimeBalances; pub use ecal::EcalHandler; @@ -152,6 +153,8 @@ pub struct InterpreterParams { pub tx_offset: usize, /// Maximum length of the message data pub max_message_data_length: u64, + /// Maximum length of a storage slot + pub max_storage_slot_length: u64, /// Chain ID pub chain_id: ChainId, /// Fee parameters @@ -171,6 +174,8 @@ impl Default for InterpreterParams { tx_offset: fuel_tx::TxParameters::DEFAULT.tx_offset(), max_message_data_length: fuel_tx::PredicateParameters::DEFAULT .max_message_data_length(), + max_storage_slot_length: fuel_tx::ScriptParameters::DEFAULT + .max_storage_slot_length(), chain_id: ChainId::default(), fee_params: FeeParameters::default(), base_asset_id: Default::default(), @@ -189,6 +194,7 @@ impl InterpreterParams { contract_max_size: params.contract_max_size, tx_offset: params.tx_offset, max_message_data_length: params.max_message_data_length, + max_storage_slot_length: params.max_storage_slot_length, chain_id: params.chain_id, fee_params: params.fee_params, base_asset_id: params.base_asset_id, diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 969e671827..f8eca787d5 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -41,7 +41,6 @@ use crate::{ BlobData, ContractsAssetsStorage, ContractsRawCode, - ContractsStateData, InterpreterStorage, }, verification::Verifier, @@ -327,6 +326,7 @@ where ra: RegId, rb: RegId, c: Word, + offset: Imm06, ) -> IoResult<(), S::DataError> { let (SystemRegisters { fp, pc, .. }, mut w) = split_registers(&mut self.registers); @@ -348,6 +348,7 @@ where context, fp: fp.as_ref(), pc, + offset, }, result, got_result, @@ -1075,6 +1076,7 @@ pub(crate) struct StateReadWordCtx<'vm, S> { pub context: &'vm Context, pub fp: Reg<'vm, FP>, pub pc: RegMut<'vm, PC>, + pub offset: Imm06, } pub(crate) fn state_read_word( @@ -1084,7 +1086,7 @@ pub(crate) fn state_read_word( context, fp, pc, - .. + offset, }: StateReadWordCtx, result: &mut Word, got_result: &mut Word, @@ -1093,16 +1095,24 @@ pub(crate) fn state_read_word( let key = Bytes32::new(memory.read_bytes(c)?); let contract = internal_contract(context, fp, memory)?; - let value = storage + let value: Option = storage .contract_state(&contract, &key) .map_err(RuntimeError::Storage)? .map(|bytes| { - Word::from_be_bytes( - bytes.as_ref().as_ref()[..8] - .try_into() - .expect("8 bytes can be converted to a Word"), - ) - }); + let offset = offset.to_u8() as usize; + let offset_bytes = offset.saturating_mul(WORD_SIZE); + let end_bytes = offset_bytes.saturating_add(WORD_SIZE); + + let data = bytes.as_ref().as_ref(); + if (data.len() as u64) < (end_bytes as u64) { + return Err(PanicReason::StorageOutOfBounds); + } + + let mut buf = [0u8; WORD_SIZE]; + buf.copy_from_slice(&data[offset_bytes..end_bytes]); + Ok(Word::from_be_bytes(buf)) + }) + .transpose()?; *result = value.unwrap_or(0); *got_result = value.is_some() as Word; @@ -1283,18 +1293,22 @@ fn state_read_qword( let dst = memory.write(ownership_registers, destination_pointer, slots_len)?; let mut all_set = true; - let result: Vec = storage + let mut result: Vec = Vec::with_capacity(slots_len); + + for slot in storage .contract_state_range(&contract_id, &origin_key, num_slots) .map_err(RuntimeError::Storage)? - .into_iter() - .flat_map(|bytes| match bytes { - Some(bytes) => bytes.into_owned(), - None => { - all_set = false; - ContractsStateData::default() + { + if let Some(bytes) = slot { + if bytes.0.len() != Bytes32::LEN { + return Err(PanicReason::StorageOutOfBounds.into()); } - }) - .collect(); + result.extend(bytes.into_owned()); + } else { + all_set = false; + result.extend([0; 32]); + } + } *result_register = all_set as Word; diff --git a/fuel-vm/src/interpreter/blockchain/test.rs b/fuel-vm/src/interpreter/blockchain/test.rs index 8ce93e9461..1f67d05e3c 100644 --- a/fuel-vm/src/interpreter/blockchain/test.rs +++ b/fuel-vm/src/interpreter/blockchain/test.rs @@ -49,19 +49,20 @@ impl OwnershipRegisters { } } -#[test_case(false, 0, None, 32 => Ok((0, 0)); "Nothing set")] -#[test_case(false, 0, 29, 32 => Ok((29, 1)); "29 set")] -#[test_case(false, 0, 0, 32 => Ok((0, 1)); "zero set")] -#[test_case(true, 0, None, 32 => Err(RuntimeError::Recoverable(PanicReason::ExpectedInternalContext)); "Can't read state from external context")] -#[test_case(false, 1, 29, 32 => Ok((0, 0)); "Wrong contract id")] -#[test_case(false, 0, 29, 33 => Ok((0, 0)); "Wrong key")] -#[test_case(true, 0, None, Word::MAX => Err(RuntimeError::Recoverable(PanicReason::MemoryOverflow)); "Overflowing key")] -#[test_case(true, 0, None, VM_MAX_RAM => Err(RuntimeError::Recoverable(PanicReason::MemoryOverflow)); "Overflowing key ram")] +#[test_case(false, 0, None, 32, 0 => Ok((0, 0)); "Nothing set")] +#[test_case(false, 0, 29, 32, 0 => Ok((29, 1)); "29 set")] +#[test_case(false, 0, 0, 32, 0 => Ok((0, 1)); "zero set")] +#[test_case(true, 0, None, 32, 0 => Err(RuntimeError::Recoverable(PanicReason::ExpectedInternalContext)); "Can't read state from external context")] +#[test_case(false, 1, 29, 32, 0 => Ok((0, 0)); "Wrong contract id")] +#[test_case(false, 0, 29, 33, 0 => Ok((0, 0)); "Wrong key")] +#[test_case(true, 0, None, Word::MAX, 0 => Err(RuntimeError::Recoverable(PanicReason::MemoryOverflow)); "Overflowing key")] +#[test_case(true, 0, None, VM_MAX_RAM, 0 => Err(RuntimeError::Recoverable(PanicReason::MemoryOverflow)); "Overflowing key ram")] fn test_state_read_word( external: bool, fp: Word, insert: impl Into>, key: Word, + offset: u8, ) -> Result<(Word, Word), RuntimeError> { let mut storage = MemoryStorage::default(); let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap(); @@ -107,6 +108,7 @@ fn test_state_read_word( context: &context, fp: Reg::new(&fp), pc: RegMut::new(&mut pc), + offset: Imm06::new_checked(offset).unwrap(), }; state_read_word(input, &mut result, &mut got_result, key)?; diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 2f1dbc3b2d..6d7a650bb4 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -135,7 +135,7 @@ where self, interpreter: &mut Interpreter, ) -> IoResult { - match self { + match dbg!(self) { Instruction::ADD(op) => op.execute(interpreter), Instruction::AND(op) => op.execute(interpreter), Instruction::DIV(op) => op.execute(interpreter), @@ -255,6 +255,15 @@ where Instruction::BLDD(op) => op.execute(interpreter), Instruction::ECOP(op) => op.execute(interpreter), Instruction::EPAR(op) => op.execute(interpreter), + Instruction::SCLR(op) => op.execute(interpreter), + Instruction::SRDD(op) => op.execute(interpreter), + Instruction::SRDI(op) => op.execute(interpreter), + Instruction::SWRD(op) => op.execute(interpreter), + Instruction::SWRI(op) => op.execute(interpreter), + Instruction::SUPD(op) => op.execute(interpreter), + Instruction::SUPI(op) => op.execute(interpreter), + Instruction::SPLD(op) => op.execute(interpreter), + Instruction::SPCP(op) => op.execute(interpreter), } } } diff --git a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs index 4bf4d3a7d9..48b5cdfdf4 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs @@ -259,6 +259,15 @@ fn writes_to_ra(opcode: Opcode) -> bool { Opcode::BLDD => false, Opcode::ECOP => false, Opcode::EPAR => true, + Opcode::SCLR => false, + Opcode::SRDD => false, + Opcode::SRDI => false, + Opcode::SWRD => false, + Opcode::SWRI => false, + Opcode::SUPD => false, + Opcode::SUPI => false, + Opcode::SPLD => true, + Opcode::SPCP => false, } } @@ -384,5 +393,14 @@ fn writes_to_rb(opcode: Opcode) -> bool { Opcode::BLDD => false, Opcode::ECOP => false, Opcode::EPAR => false, + Opcode::SCLR => false, + Opcode::SRDD => false, + Opcode::SRDI => false, + Opcode::SWRD => false, + Opcode::SWRI => false, + Opcode::SUPD => false, + Opcode::SUPI => false, + Opcode::SPLD => false, + Opcode::SPCP => false, } } diff --git a/fuel-vm/src/interpreter/executors/opcodes_impl.rs b/fuel-vm/src/interpreter/executors/opcodes_impl.rs index 098cf15c4d..116556dde4 100644 --- a/fuel-vm/src/interpreter/executors/opcodes_impl.rs +++ b/fuel-vm/src/interpreter/executors/opcodes_impl.rs @@ -1,6 +1,13 @@ use crate::{ - constraints::reg_key::ProgramRegistersSegment, - error::IoResult, + constraints::reg_key::{ + ProgramRegistersSegment, + SystemRegisters, + split_registers, + }, + error::{ + IoResult, + RuntimeError, + }, interpreter::{ EcalHandler, ExecutableTransaction, @@ -15,6 +22,7 @@ use crate::{ JumpArgs, JumpMode, }, + internal::inc_pc, }, prelude::InterpreterStorage, state::ExecuteState, @@ -32,6 +40,7 @@ use fuel_asm::{ }, wideint, }; +use fuel_tx::Bytes32; use fuel_types::Word; impl Execute for ADD @@ -2249,8 +2258,8 @@ where interpreter: &mut Interpreter, ) -> IoResult { interpreter.gas_charge(interpreter.gas_costs().srw())?; - let (a, b, c) = self.unpack(); - interpreter.state_read_word(a, b, interpreter.registers[c])?; + let (a, b, c, d) = self.unpack(); + interpreter.state_read_word(a, b, interpreter.registers[c], d)?; Ok(ExecuteState::Proceed) } } @@ -2685,3 +2694,302 @@ where Ok(ExecuteState::Proceed) } } + +impl Execute for fuel_asm::op::SCLR +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_key_ptr, r_num_slots) = self.unpack(); + interpreter.dependent_gas_charge( + interpreter.gas_costs().sclr().map_err(PanicReason::from)?, + interpreter.registers[r_num_slots], + )?; + let start_key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let num_slots = crate::convert::to_usize(interpreter.registers[r_num_slots]) + .ok_or(PanicReason::TooManySlots)?; + let contract_id = interpreter.internal_contract()?; + interpreter + .storage + .contract_state_remove_range_nostatus(&contract_id, &start_key, num_slots) + .map_err(RuntimeError::Storage)?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SRDD +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_buffer_ptr, r_key_ptr, r_offset, r_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.storage_read_to_memory( + key, + interpreter.registers[r_buffer_ptr], + interpreter.registers[r_offset], + interpreter.registers[r_len], + )?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().srdd().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SRDI +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_buffer_ptr, r_key_ptr, r_offset, imm_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.storage_read_to_memory( + key, + interpreter.registers[r_buffer_ptr], + interpreter.registers[r_offset], + imm_len.to_u8().into(), + )?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().srdd().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SWRD +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_key_ptr, r_value_ptr, r_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.registers[r_len]; + interpreter.dependent_gas_charge( + interpreter.gas_costs().swrd().map_err(PanicReason::from)?, + len, + )?; + interpreter.storage_write_from_memory( + key, + interpreter.registers[r_value_ptr], + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SWRI +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_key_ptr, r_value_ptr, imm_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len: u64 = imm_len.to_u16().into(); + interpreter.dependent_gas_charge( + interpreter.gas_costs().swrd().map_err(PanicReason::from)?, + len, + )?; + interpreter.storage_write_from_memory( + key, + interpreter.registers[r_value_ptr], + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SUPD +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_key_ptr, r_value_ptr, r_offset, r_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.storage_update_from_memory( + key, + interpreter.registers[r_value_ptr], + interpreter.registers[r_offset], + interpreter.registers[r_len], + )?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().supd().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SUPI +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_key_ptr, r_value_ptr, r_offset, imm_len) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.storage_update_from_memory( + key, + interpreter.registers[r_value_ptr], + interpreter.registers[r_offset], + imm_len.to_u8().into(), + )?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().supd().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SPLD +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_dst_len, r_key_ptr) = self.unpack(); + let key = Bytes32::from( + interpreter + .memory() + .read_bytes(interpreter.registers[r_key_ptr])?, + ); + let len = interpreter.storage_preload(key)?; + interpreter.write_user_register(r_dst_len, len)?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().spld().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} + +impl Execute for fuel_asm::op::SPCP +where + M: Memory, + S: InterpreterStorage, + Tx: ExecutableTransaction, + Ecal: EcalHandler, + V: Verifier, +{ + fn execute( + self, + interpreter: &mut Interpreter, + ) -> IoResult { + let (r_ptr, r_offset, r_len, imm_len) = self.unpack(); + let ptr = interpreter.registers[r_ptr]; + let offset = interpreter.registers[r_offset]; + let len = interpreter.registers[r_len].saturating_add(imm_len.to_u8() as u64); + let owner = interpreter.ownership_registers(); + interpreter + .memory_mut() + .memcopy_from_preload(ptr, offset, len, owner)?; + interpreter.dependent_gas_charge( + interpreter.gas_costs().spcp().map_err(PanicReason::from)?, + len, + )?; + let (SystemRegisters { pc, .. }, _) = split_registers(&mut interpreter.registers); + inc_pc(pc)?; + Ok(ExecuteState::Proceed) + } +} diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 89a89b0e2c..e9603ca3d6 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -99,7 +99,7 @@ where receipts: &mut self.receipts, frames: &mut self.frames, registers: &mut self.registers, - memory: self.memory.as_ref(), + memory: self.memory.as_mut(), context: &mut self.context, current_contract, }; @@ -157,7 +157,7 @@ where struct RetCtx<'vm> { frames: &'vm mut Vec, registers: &'vm mut [Word; VM_REGISTER_COUNT], - memory: &'vm MemoryInstance, + memory: &'vm mut MemoryInstance, receipts: &'vm mut ReceiptsCtx, context: &'vm mut Context, current_contract: Option, @@ -204,6 +204,9 @@ impl RetCtx<'_> { let fp = registers[RegId::FP]; set_frame_pointer(context, registers.fp_mut(), fp); + + // Clear storage preload area + self.memory.as_mut().storage_preload_mut().clear(); } self.receipts.push(receipt)?; @@ -600,6 +603,9 @@ impl PrepareCallCtx<'_, S, V> { *self.registers.system_registers.cgas = forward_gas_amount; *self.registers.system_registers.flag = 0; + // Clear storage preload area + self.memory.as_mut().storage_preload_mut().clear(); + let receipt = Receipt::call( id, *call.to(), diff --git a/fuel-vm/src/interpreter/internal.rs b/fuel-vm/src/interpreter/internal.rs index 36a2910fc9..d69811efd7 100644 --- a/fuel-vm/src/interpreter/internal.rs +++ b/fuel-vm/src/interpreter/internal.rs @@ -137,6 +137,25 @@ where .block_height() .ok_or(PanicReason::TransactionValidity) } + + /// A standard method to write to user registers, enforcing the rules around + /// which registers are writable. Writes to the zero register are ignored. + pub(crate) fn write_user_register( + &mut self, + reg: RegId, + val: Word, + ) -> SimpleResult<()> { + if reg == RegId::ZERO { + return Ok(()); + } + + if reg < RegId::WRITABLE { + return Err(PanicReason::ReservedRegisterNotWritable.into()); + } + + self.registers[reg] = val; + Ok(()) + } } pub(crate) fn clear_err(mut err: RegMut) { diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index 27d177a57e..c5d8ec117a 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -7,6 +7,7 @@ use super::{ use crate::{ constraints::reg_key::*, consts::*, + convert, error::SimpleResult, }; @@ -62,7 +63,7 @@ pub trait Memory: AsRef + AsMut {} impl Memory for M where M: AsRef + AsMut {} -/// The memory of the VM, represented as stack and heap. +/// The memory of the VM, represented as stack, heap, and storage preload area. #[derive(Clone, Eq)] pub struct MemoryInstance { /// Stack. Grows upwards. @@ -72,6 +73,8 @@ pub struct MemoryInstance { /// Lowest allowed heap address, i.e. hp register value. /// This is needed since we can allocate extra heap for performance reasons. hp: usize, + /// Storage preload area. + storage_preload: Vec, } impl Default for MemoryInstance { @@ -121,6 +124,7 @@ impl MemoryInstance { stack: Vec::new(), heap: Vec::new(), hp: MEM_SIZE, + storage_preload: Vec::new(), } } @@ -128,6 +132,7 @@ impl MemoryInstance { pub fn reset(&mut self) { self.stack.truncate(0); self.hp = MEM_SIZE; + self.storage_preload.truncate(0); } /// Offset of the heap section @@ -339,6 +344,45 @@ impl MemoryInstance { Ok(()) } + /// Copies from preload area to main memory, verifying ownership. + #[inline] + pub fn memcopy_from_preload( + &mut self, + dst: Word, + offset: Word, + length: Word, + owner: OwnershipRegisters, + ) -> Result<(), PanicReason> { + let dst_range = self.verify(dst, length)?; + owner.verify_ownership(&dst_range)?; + + let offset = convert::to_usize(offset).ok_or(PanicReason::MemoryOverflow)?; + let length = convert::to_usize(length).ok_or(PanicReason::MemoryOverflow)?; + + let end = offset.saturating_add(length); + if end > self.storage_preload.len() { + return Err(PanicReason::StorageOutOfBounds); + } + + let dst = if dst_range.end() <= self.stack.len() { + &mut self.stack[dst_range.usizes()] + } else if dst_range.start() >= self.heap_offset() { + #[allow(clippy::arithmetic_side_effects)] + // Safety: subtractions are checked above + let start = dst_range.start() - self.heap_offset(); + #[allow(clippy::arithmetic_side_effects)] + // Safety: subtractions are checked above + let end = dst_range.end() - self.heap_offset(); + &mut self.heap[start..end] + } else { + unreachable!("Range was verified to be valid") + }; + + dst.copy_from_slice(&self.storage_preload[offset..end]); + + Ok(()) + } + /// Copies the memory from `src` to `dst` verifying ownership. #[inline] #[track_caller] @@ -410,6 +454,16 @@ impl MemoryInstance { Ok(()) } + /// Storage preload/staging area access. + pub fn storage_preload(&self) -> &[u8] { + &self.storage_preload + } + + /// Mutable storage preload/staging area access. + pub fn storage_preload_mut(&mut self) -> &mut Vec { + &mut self.storage_preload + } + /// Memory access to the raw stack buffer. /// Note that for efficiency reasons this might not match sp value. #[cfg(any(test, feature = "test-helpers"))] diff --git a/fuel-vm/src/interpreter/storage.rs b/fuel-vm/src/interpreter/storage.rs new file mode 100644 index 0000000000..8adcb2c2cc --- /dev/null +++ b/fuel-vm/src/interpreter/storage.rs @@ -0,0 +1,156 @@ +use fuel_asm::RegId; +use fuel_tx::{ + Bytes32, + PanicReason, +}; + +use crate::{ + convert, + error::RuntimeError, + prelude::{ + Interpreter, + Memory, + }, + storage::InterpreterStorage, +}; + +impl Interpreter +where + M: Memory, + S: InterpreterStorage, +{ + pub(crate) fn verify_storage_size( + &self, + size: usize, + ) -> Result<(), RuntimeError> { + let max_size = self.interpreter_params.max_storage_slot_length; + if (size as u64) > max_size { + return Err(RuntimeError::Recoverable(PanicReason::StorageOutOfBounds)); + } + Ok(()) + } + + /// Returns length of the value, or 0 if the slot is not found. + pub(crate) fn storage_read_to_memory( + &mut self, + key: Bytes32, + dst_ptr: u64, + offset: u64, + len: u64, + ) -> Result> { + let offset = convert::to_usize(offset).ok_or(PanicReason::MemoryOverflow)?; + + let len = convert::to_usize(len).ok_or(PanicReason::MemoryOverflow)?; + + let contract_id = self.internal_contract()?; + let owner = self.ownership_registers(); + + let dst = self.memory.as_mut().write(owner, dst_ptr, len)?; + let value = self + .storage + .contract_state(&contract_id, &key) + .map_err(RuntimeError::Storage)?; + + let Some(value) = value else { + self.registers[RegId::ERR] = 1; + return Ok(0); + }; + + let value = value.as_ref().as_ref(); + + let end = offset.saturating_add(len); + if end > value.len() { + // attempting to read past the end of the stored value + return Err(RuntimeError::Recoverable(PanicReason::StorageOutOfBounds)); + } + + dst.copy_from_slice(&value[offset..end]); + + self.registers[RegId::ERR] = 0; + Ok(value.len() as u64) + } + + pub(crate) fn storage_write_from_memory( + &mut self, + key: Bytes32, + src_ptr: u64, + len: u64, + ) -> Result<(), RuntimeError> { + let contract_id = self.internal_contract()?; + let len = convert::to_usize(len).ok_or(PanicReason::MemoryOverflow)?; + self.verify_storage_size(len)?; + let src = self.memory.as_mut().read(src_ptr, len)?; + self.storage + .contract_state_insert(&contract_id, &key, src) + .map_err(RuntimeError::Storage) + } + + /// Storage read, subslice update and write back. + /// Returns the resulting slot length. + pub(crate) fn storage_update_from_memory( + &mut self, + key: Bytes32, + src_ptr: u64, + offset: u64, + write_len: u64, + ) -> Result> { + let contract_id = self.internal_contract()?; + let mut value = self + .storage + .contract_state(&contract_id, &key) + .map_err(RuntimeError::Storage)? + .map(|v| v.as_ref().0.clone().into_inner()) + .unwrap_or_default(); + + let offset = if offset == u64::MAX { + value.len() + } else { + convert::to_usize(offset).ok_or(PanicReason::MemoryOverflow)? + }; + + if offset > value.len() { + return Err(RuntimeError::Recoverable(PanicReason::StorageOutOfBounds)); + } + + let write_len = + convert::to_usize(write_len).ok_or(PanicReason::MemoryOverflow)?; + let len_after = offset.saturating_add(write_len); + + self.verify_storage_size(len_after)?; + + if len_after > value.len() { + value.resize(len_after, 0); + } + + value[offset..len_after] + .copy_from_slice(self.memory.as_mut().read(src_ptr, write_len)?); + + self.storage + .contract_state_insert(&contract_id, &key, &value) + .map_err(RuntimeError::Storage)?; + Ok(len_after as u64) + } + + /// Preloads the storage slot identified by `key` into a special memory area, + /// returning its size. Returns length of the slot, or 0 if the slot is not found. + pub(crate) fn storage_preload( + &mut self, + key: Bytes32, + ) -> Result> { + let contract_id = self.internal_contract()?; + let value = self + .storage + .contract_state(&contract_id, &key) + .map_err(RuntimeError::Storage)?; + + let Some(value) = value else { + self.registers[RegId::ERR] = 1; + return Ok(0); + }; + + let dst = self.memory.as_mut().storage_preload_mut(); + *dst = value.as_ref().as_ref().to_vec(); + self.registers[RegId::ERR] = 0; + Ok(dst.len() as u64) + } +} diff --git a/fuel-vm/src/storage/contracts_state.rs b/fuel-vm/src/storage/contracts_state.rs index 0266f42c76..566524f01b 100644 --- a/fuel-vm/src/storage/contracts_state.rs +++ b/fuel-vm/src/storage/contracts_state.rs @@ -7,10 +7,7 @@ use fuel_types::{ ContractId, }; -use alloc::{ - vec, - vec::Vec, -}; +use alloc::vec::Vec; use educe::Educe; use fuel_types::bytes::Bytes; @@ -51,13 +48,6 @@ double_key!( #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ContractsStateData(pub Bytes); -// TODO: Remove fixed size default when adding support for dynamic storage -impl Default for ContractsStateData { - fn default() -> Self { - Self(vec![0u8; 32].into()) - } -} - impl From> for ContractsStateData { fn from(c: Vec) -> Self { Self(c.into()) diff --git a/fuel-vm/src/storage/interpreter.rs b/fuel-vm/src/storage/interpreter.rs index 2e6991cf91..1997717188 100644 --- a/fuel-vm/src/storage/interpreter.rs +++ b/fuel-vm/src/storage/interpreter.rs @@ -246,6 +246,20 @@ pub trait InterpreterStorage: start_key: &Bytes32, range: usize, ) -> Result, Self::DataError>; + + /// Remove a range of key-values from contract storage. + /// Unlike `contract_state_remove_range`, this method does not return + /// information about whether the keys were previously set or not. + fn contract_state_remove_range_nostatus( + &mut self, + contract: &ContractId, + start_key: &Bytes32, + range: usize, + ) -> Result<(), Self::DataError> { + // Default impl that just calls the possibly less efficient version + self.contract_state_remove_range(contract, start_key, range) + .map(|_| ()) + } } /// Storage operations for contract assets. diff --git a/fuel-vm/src/storage/memory.rs b/fuel-vm/src/storage/memory.rs index 607f71347b..6c62038296 100644 --- a/fuel-vm/src/storage/memory.rs +++ b/fuel-vm/src/storage/memory.rs @@ -138,18 +138,6 @@ impl MemoryStorage { self.memory.contract_state.iter() } - /// Fetch a mapping from the contract state. - pub fn contract_state( - &self, - contract: &ContractId, - key: &Bytes32, - ) -> Cow<'_, ContractsStateData> { - self.storage::() - .get(&(contract, key).into()) - .expect("Infallible") - .unwrap_or(Cow::Owned(ContractsStateData::default())) - } - /// Set the transacted state to the memory state. pub fn commit(&mut self) { self.transacted = self.memory.clone(); @@ -822,13 +810,13 @@ mod tests { ] } - #[test_case(&[&[0u8; 32]], &[0u8; 32], 1 => vec![Some(Default::default())])] + #[test_case(&[&[0u8; 32]], &[0u8; 32], 1 => vec![Some([0; 32].to_vec().into())])] #[test_case(&[&[0u8; 32]], &[0u8; 32], 0 => Vec::>::with_capacity(0))] #[test_case(&[], &[0u8; 32], 1 => vec![None])] #[test_case(&[], &[1u8; 32], 1 => vec![None])] #[test_case(&[&[0u8; 32]], &key(1), 2 => vec![None, None])] - #[test_case(&[&key(1), &key(3)], &[0u8; 32], 4 => vec![None, Some(Default::default()), None, Some(Default::default())])] - #[test_case(&[&[0u8; 32], &key(1)], &[0u8; 32], 1 => vec![Some(Default::default())])] + #[test_case(&[&key(1), &key(3)], &[0u8; 32], 4 => vec![None, Some([0; 32].to_vec().into()), None, Some([0; 32].to_vec().into())])] + #[test_case(&[&[0u8; 32], &key(1)], &[0u8; 32], 1 => vec![Some([0; 32].to_vec().into())])] fn test_contract_state_range( store: &[&[u8; 32]], start: &[u8; 32], @@ -838,7 +826,7 @@ mod tests { for k in store { mem.memory.contract_state.insert( (&ContractId::default(), &(**k).into()).into(), - Default::default(), + [0; 32].to_vec().into(), ); } mem.contract_state_range(&ContractId::default(), &(*start).into(), range) diff --git a/fuel-vm/src/tests/blockchain.rs b/fuel-vm/src/tests/blockchain.rs index 25c045d539..e5de2fbc76 100644 --- a/fuel-vm/src/tests/blockchain.rs +++ b/fuel-vm/src/tests/blockchain.rs @@ -140,7 +140,7 @@ fn state_read_write() { let routine_add_word_to_state = vec![ op::jnei(0x10, 0x30, 13), // (0, b) Add word to state op::lw(0x20, 0x11, 4), // r[0x20] := m[b+32, 8] - op::srw(0x21, SET_STATUS_REG, 0x11), // r[0x21] := s[m[b, 32], 8] + op::srw(0x21, SET_STATUS_REG, 0x11, 0), // r[0x21] := s[m[b, 32], 8] op::add(0x20, 0x20, 0x21), // r[0x20] += r[0x21] op::sww(0x11, SET_STATUS_REG, 0x20), // s[m[b,32]] := r[0x20] op::log(0x20, 0x21, 0x00, 0x00), @@ -231,8 +231,9 @@ fn state_read_write() { // Assert the initial state of `key` is empty let state = test_context .get_storage() - .contract_state(&contract_id, &key); - assert_eq!(ContractsStateData::default(), state.into_owned()); + .contract_state(&contract_id, &key) + .unwrap(); + assert!(state.is_none(), "Expected empty initial state for key"); let result = test_context .start_script(script.clone(), script_data) @@ -245,7 +246,9 @@ fn state_read_write() { let receipts = result.receipts(); let state = test_context .get_storage() - .contract_state(&contract_id, &key); + .contract_state(&contract_id, &key) + .unwrap() + .expect("Missing slot"); // Assert the state of `key` is mutated to `val` assert_eq!( @@ -317,7 +320,9 @@ fn state_read_write() { let data = ContractsStateData::from(bytes.as_ref().to_vec()); let state = test_context .get_storage() - .contract_state(&contract_id, &key); + .contract_state(&contract_id, &key) + .unwrap() + .expect("Missing slot"); assert_eq!(data, state.into_owned()); } @@ -1305,7 +1310,7 @@ fn sww_sets_status() { #[rustfmt::skip] let program = vec![ op::sww(0x30, SET_STATUS_REG, RegId::ZERO), - op::srw(0x31, SET_STATUS_REG + 1, RegId::ZERO), + op::srw(0x31, SET_STATUS_REG + 1, RegId::ZERO, 0), op::log(SET_STATUS_REG, SET_STATUS_REG + 1, 0x00, 0x00), op::ret(RegId::ONE), ]; @@ -1319,7 +1324,7 @@ fn scwq_clears_status() { let program = vec![ op::sww(0x30, SET_STATUS_REG, RegId::ZERO), op::scwq(0x30, SET_STATUS_REG + 1, RegId::ONE), - op::srw(0x30, SET_STATUS_REG + 2, RegId::ZERO), + op::srw(0x30, SET_STATUS_REG + 2, RegId::ZERO, 0), op::log(SET_STATUS_REG, SET_STATUS_REG + 1, SET_STATUS_REG + 2, 0x00), op::ret(RegId::ONE), ]; @@ -1351,9 +1356,9 @@ fn scwq_clears_status_for_range() { fn srw_reads_status() { let program = vec![ op::sww(0x30, SET_STATUS_REG, RegId::ZERO), - op::srw(0x30, SET_STATUS_REG + 1, RegId::ZERO), - op::srw(0x30, SET_STATUS_REG + 2, RegId::ZERO), - op::srw(0x30, SET_STATUS_REG + 3, RegId::ONE), + op::srw(0x30, SET_STATUS_REG + 1, RegId::ZERO, 0), + op::srw(0x30, SET_STATUS_REG + 2, RegId::ZERO, 0), + op::srw(0x30, SET_STATUS_REG + 3, RegId::ONE, 0), op::log( SET_STATUS_REG, SET_STATUS_REG + 1, @@ -1374,7 +1379,7 @@ fn srwq_reads_status() { op::addi(0x31, RegId::HP, 0x5), op::sww(0x31, SET_STATUS_REG, RegId::ZERO), op::srwq(0x31, SET_STATUS_REG + 1, 0x31, RegId::ONE), - op::srw(0x31, SET_STATUS_REG + 2, 0x31), + op::srw(0x31, SET_STATUS_REG + 2, 0x31, 0), op::log(SET_STATUS_REG, SET_STATUS_REG + 1, SET_STATUS_REG + 2, 0x00), op::ret(RegId::ONE), ]; @@ -1408,9 +1413,9 @@ fn swwq_sets_status() { let program = vec![ op::aloc(0x10), op::addi(0x31, RegId::HP, 0x5), - op::srw(0x31, SET_STATUS_REG, 0x31), + op::srw(0x31, SET_STATUS_REG, 0x31, 0), op::swwq(0x31, SET_STATUS_REG + 1, 0x31, RegId::ONE), - op::srw(0x31, SET_STATUS_REG + 2, 0x31), + op::srw(0x31, SET_STATUS_REG + 2, 0x31, 0), op::log(SET_STATUS_REG, SET_STATUS_REG + 1, SET_STATUS_REG + 2, 0x00), op::ret(RegId::ONE), ]; @@ -1474,10 +1479,11 @@ fn check_receipts_for_program_call( script_data.extend(val.to_be_bytes()); // Assert the initial state of `key` is empty - let state = test_context + let slot = test_context .get_storage() - .contract_state(&contract_id, &key); - assert_eq!(ContractsStateData::default(), state.into_owned()); + .contract_state(&contract_id, &key) + .unwrap(); + assert!(slot.is_none(), "Expected empty initial state for key"); let result = test_context .start_script(script, script_data) @@ -1523,7 +1529,7 @@ fn state_r_word_b_plus_32_over() { let state_read_word = vec![ op::not(reg_a, RegId::ZERO), op::subi(reg_a, reg_a, 31 as Immediate12), - op::srw(reg_a, SET_STATUS_REG, reg_a), + op::srw(reg_a, SET_STATUS_REG, reg_a, 0), ]; check_expected_reason_for_instructions(state_read_word, MemoryOverflow); @@ -1538,7 +1544,7 @@ fn state_r_word_b_over_max_ram() { let state_read_word = vec![ op::slli(reg_a, RegId::ONE, MAX_MEM_SHL), op::subi(reg_a, reg_a, 31 as Immediate12), - op::srw(reg_a, SET_STATUS_REG, reg_a), + op::srw(reg_a, SET_STATUS_REG, reg_a, 0), ]; check_expected_reason_for_instructions(state_read_word, MemoryOverflow); diff --git a/fuel-vm/src/tests/flow.rs b/fuel-vm/src/tests/flow.rs index 56b1f70739..9e3a4c7187 100644 --- a/fuel-vm/src/tests/flow.rs +++ b/fuel-vm/src/tests/flow.rs @@ -406,11 +406,11 @@ fn revert() { #[rustfmt::skip] let routine_add_word_to_state = vec![ - op::jnei(0x10, 0x30, 13), // (0, b) Add word to state - op::lw(0x20, 0x11, 4), // r[0x20] := m[b+32, 8] - op::srw(0x21, SET_STATUS_REG, 0x11), // r[0x21] := s[m[b, 32], 8] - op::add(0x20, 0x20, 0x21), // r[0x20] += r[0x21] - op::sww(0x11, SET_STATUS_REG, 0x20), // s[m[b,32]] := r[0x20] + op::jnei(0x10, 0x30, 13), // (0, b) Add word to state + op::lw(0x20, 0x11, 4), // r[0x20] := m[b+32, 8] + op::srw(0x21, SET_STATUS_REG, 0x11, 0), // r[0x21] := s[m[b, 32], 8] + op::add(0x20, 0x20, 0x21), // r[0x20] += r[0x21] + op::sww(0x11, SET_STATUS_REG, 0x20), // s[m[b,32]] := r[0x20] op::log(0x20, 0x21, 0x00, 0x00), op::ret(RegId::ONE), ]; @@ -463,7 +463,9 @@ fn revert() { let receipts = result.receipts(); let state = test_context .get_storage() - .contract_state(&contract_id, &key); + .contract_state(&contract_id, &key) + .unwrap() + .expect("missing slot"); // Assert the state of `key` is mutated to `val` assert_eq!( @@ -503,7 +505,9 @@ fn revert() { .execute(); let state = test_context .get_storage() - .contract_state(&contract_id, &key); + .contract_state(&contract_id, &key) + .unwrap() + .expect("missing slot"); assert_eq!( &val.to_be_bytes()[..], diff --git a/fuel-vm/src/tests/mod.rs b/fuel-vm/src/tests/mod.rs index 9e4cd86aa4..50f895cf63 100644 --- a/fuel-vm/src/tests/mod.rs +++ b/fuel-vm/src/tests/mod.rs @@ -33,6 +33,7 @@ mod outputs; mod predicate; mod receipts; mod spec; +mod storage; mod upgrade; mod upload; mod validation; diff --git a/fuel-vm/src/tests/storage.rs b/fuel-vm/src/tests/storage.rs new file mode 100644 index 0000000000..f94887de3d --- /dev/null +++ b/fuel-vm/src/tests/storage.rs @@ -0,0 +1,1343 @@ +use core::{ + array, + panic, +}; + +use crate::{ + consts::VM_MAX_RAM, + prelude::*, + script_with_data_offset, + tests::test_helpers::{ + assert_panics, + assert_success, + set_full_word, + }, + util::test_helpers::TestBuilder, +}; +use alloc::{ + vec, + vec::Vec, +}; +use fuel_asm::{ + Imm06, + Imm12, + RegId, + op, +}; +use fuel_tx::ConsensusParameters; +use fuel_types::{ + bytes::WORD_SIZE, + canonical::Serialize, +}; + +/// Helper to deploy and call a contract once. +fn call_contract_once(program: Vec) -> Vec { + let mut test_context = TestBuilder::new(2322u64); + + let contract_id = test_context.setup_contract(program, None, None).contract_id; + + let (script_call, _) = script_with_data_offset!( + data_offset, + vec![ + op::movi(0x10, data_offset as Immediate18), + op::call(0x10, RegId::ZERO, 0x10, RegId::CGAS), + op::ret(RegId::ONE), + ], + test_context.get_tx_params().tx_offset() + ); + let script_call_data = Call::new(contract_id, 0, 0).to_bytes(); + + let result = test_context + .start_script(script_call.clone(), script_call_data) + .script_gas_limit(1_000_000) + .contract_input(contract_id) + .fee_input() + .contract_output(&contract_id) + .variable_output(AssetId::zeroed()) + .execute(); + + result.receipts().to_vec() +} + +#[test] +fn srwq_can_read_slots_when_created_with_dynamic_opcodes() { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let receipts = call_contract_once(vec![ + // Allocate buffers + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, 64), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + // Store dummy zeroes data to both slots + op::swri(SLOT_KEY, BUFFER, 32), + op::sb(SLOT_KEY, RegId::ONE, 31), + op::swri(SLOT_KEY, BUFFER, 32), + op::sb(SLOT_KEY, RegId::ZERO, 31), + // Attempt read using SRWQ, should panic + op::movi(0x10, 2), + op::srwq(BUFFER, DISCARD, SLOT_KEY, 0x10), + op::logd(RegId::ZERO, RegId::ZERO, BUFFER, 0x15), + // Done + op::ret(RegId::ONE), + ]); + + assert_success(&receipts); + + for r in receipts { + let Receipt::LogData { data, .. } = r else { + continue; + }; + let data = data.as_ref().unwrap(); + let expected = [0u8; 64]; + assert_eq!(&**data, &expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[test] +fn srwq_allows_reading_zero_slots_even_if_the_first_would_have_wrong_size() { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let receipts = call_contract_once(vec![ + // Allocate buffers + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, 64), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + // Store dummy data to the slot + op::swri(SLOT_KEY, BUFFER, 43), + // Attempt read using SRWQ, should succeed + op::srwq(BUFFER, DISCARD, SLOT_KEY, RegId::ZERO), + // Done + op::ret(RegId::ONE), + ]); + assert_success(&receipts); +} + +#[test] +fn srwq_panics_when_combined_slots_sum_to_multiple_of_32() { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + const LEN_FIRST: usize = 20; + const LEN_SECOND: usize = 64 - LEN_FIRST; + + let receipts = call_contract_once(vec![ + // Allocate buffers + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, 64), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + // Store dummy data to both slots + op::swri(SLOT_KEY, BUFFER, LEN_FIRST as _), + op::sb(SLOT_KEY, RegId::ONE, 31), + op::swri(SLOT_KEY, BUFFER, LEN_SECOND as _), + op::sb(SLOT_KEY, RegId::ZERO, 31), + // Attempt read using SRWQ, should panic + op::movi(0x10, 2), + op::srwq(BUFFER, DISCARD, SLOT_KEY, 0x10), + // Done + op::ret(RegId::ONE), + ]); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[test] +fn sww_writes_32_bytes() { + const DISCARD: RegId = RegId::new(0x39); + + let receipts = call_contract_once(vec![ + // Allocate a buffer + op::movi(0x15, 64), + op::aloc(0x15), + // Store some data + op::movi(0x10, 0x01), + op::sww(RegId::HP, DISCARD, 0x10), + op::addi(0x11, RegId::HP, 32), + op::sb(0x11, RegId::ONE, 31), + op::movi(0x10, 0x02), + op::sww(0x11, DISCARD, 0x10), + // Load it back in 32 byte groups + op::movi(0x10, 0x02), + op::srwq(RegId::HP, DISCARD, RegId::HP, 0x10), + // Log it + op::logd(RegId::ZERO, RegId::ZERO, RegId::HP, 0x15), + // Done + op::ret(RegId::ONE), + ]); + + assert_success(&receipts); + + for r in receipts { + let Receipt::LogData { data, .. } = r else { + continue; + }; + let data = data.as_ref().unwrap(); + let mut expected = [0u8; 64]; + expected[7] = 1; + expected[32 + 7] = 2; + assert_eq!(&**data, &expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[test] +fn srw_offset_works() { + const DISCARD: RegId = RegId::new(0x39); + + // Construct a program that writes 1024 bytes to storage, + // then reads two bytes at different offsets using `srw` and logs them. + let mut program = vec![ + // Allocate slot key (all zeroes) + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(0x14, RegId::HP), + // Allocate a buffer for the data + op::movi(0x15, 1024), + op::aloc(0x15), + ]; + // Make up some data and write it to the storage + for offset in 0..(1024 / WORD_SIZE) { + program.extend([ + op::movi(0x10, (offset % 256) as u8 as _), + op::sw(RegId::HP, 0x10, offset as _), + ]); + } + program.push(op::swri(0x14, RegId::HP, 1024)); + // Log test cases + for offset in 0..=Imm06::MAX.to_u8() { + program.extend([ + op::srw(0x10, DISCARD, 0x14, offset), + op::log(0x10, RegId::ZERO, RegId::ZERO, RegId::ZERO), + ]); + } + // Done + program.push(op::ret(RegId::ONE)); + + let receipts = call_contract_once(program); + + assert_success(&receipts); + + let logged_values: Vec = receipts + .iter() + .filter_map(|r| match r { + Receipt::Log { ra, .. } => Some(*ra), + _ => None, + }) + .collect(); + + assert_eq!(logged_values.len(), Imm06::MAX.to_u8() as usize + 1); + + for i in 0..=Imm06::MAX.to_u8() { + assert_eq!(logged_values[i as usize], i as u64); + } +} + +enum MemoryOverflowCase { + /// Offset is exactly one byte past the end of memory. + OffByOne, + /// Offset is completely outside of memory. + Outside, + /// Offset calculation overflows. + Overflow, +} + +enum StorageOverflowCase { + /// length alone (with offset zero) exceeds slot limit + Length, + /// offset alone (with length zero) exceeds slot limit + Offset, + /// offset + length exceeds slot limit + OffsetPlusLength, +} + +#[rstest::rstest] +fn storage_op_storage_key_read_past_boudnds_panics( + #[values( + op::sclr(0x10, RegId::ONE), + op::srdd(RegId::HP, 0x10, RegId::ZERO, RegId::ZERO), + op::srdi(RegId::HP, 0x10, RegId::ZERO, 0), + op::swrd(0x10, RegId::HP, RegId::ZERO), + op::swri(0x10, RegId::HP, 0), + op::supd(0x10, RegId::HP, RegId::ZERO, RegId::ZERO), + op::supi(0x10, RegId::HP, RegId::ZERO, 0), + op::spld(0x11, 0x10) + )] + instr: Instruction, + #[values( + MemoryOverflowCase::OffByOne, + MemoryOverflowCase::Outside, + MemoryOverflowCase::Overflow + )] + case: MemoryOverflowCase, +) { + let mut program = vec![ + // Allocate slot key (all zeroes) + op::movi(0x15, 32), + op::aloc(0x15), + ]; + // Set up the case + program.push(match case { + MemoryOverflowCase::OffByOne => op::addi(0x10, RegId::HP, 1), + MemoryOverflowCase::Outside => op::addi(0x10, RegId::HP, 32), + MemoryOverflowCase::Overflow => op::not(0x10, RegId::ZERO), + }); + // Perform the storage operation + program.push(instr); + // The instruction above should panic, so we never reach this + program.push(op::ret(RegId::ONE)); + + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +#[rstest::rstest] +fn sclr_clears_correct_number_of_slots( + #[values(0, 1, 2, 3, 100)] num_create: u8, + #[values(0, 1, 2, 3, 100)] num_clear: u8, +) { + const DISCARD: RegId = RegId::new(0x39); + + let mut program = vec![ + // Allocate slot key buffer (all zeroes) + op::movi(0x15, 32), + op::aloc(0x15), + ]; + // Create slots + for i in 0..num_create { + program.extend([ + op::movi(0x10, i as _), + op::sb(RegId::HP, 0x10, 31), + op::sww(RegId::HP, DISCARD, RegId::ONE), + ]); + } + // Clear slots + program.extend([ + op::mcli(RegId::HP, 32), + op::movi(0x10, num_clear as _), + op::sclr(RegId::HP, 0x10), + ]); + // Log the first 256 slots + for i in 0..256u32 { + program.extend([ + op::movi(0x10, i as _), + op::sb(RegId::HP, 0x10, 31), + op::spld(0x10, RegId::HP), + op::log(0x10, RegId::ERR, RegId::ZERO, RegId::ZERO), + ]); + } + // Done + program.push(op::ret(RegId::ONE)); + + let receipts = call_contract_once(program); + assert_success(&receipts); + + let slots_after: Vec = receipts + .iter() + .filter_map(|r| match r { + Receipt::Log { ra, rb, .. } => Some(match rb { + 0 => { + // $err clear, so this is an occupied slot + assert_eq!(*ra, 32, "All created slots should have length of 32"); + true + } + 1 => { + // $err set, so this is a cleared slot + assert_eq!(*ra, 0, "Cleared slots should have length of 0"); + false + } + _ => unreachable!("Unexpected $err value in Log receipt"), + }), + _ => None, + }) + .collect(); + + let mut expected = vec![false; 256]; + expected[..(num_create as usize)].fill(true); + expected[..(num_clear as usize)].fill(false); + + assert_eq!(slots_after, expected); +} + +/// Allocates and initalizes a 256 byte array with elements from 0 to 255. +fn create_example_buffer() -> Vec { + let mut ops = vec![op::movi(0x39, 256), op::aloc(0x39)]; + for i in 0..256u32 { + ops.push(op::movi(0x39, i as u8 as _)); + ops.push(op::sb(RegId::HP, 0x39, i as _)); + } + ops +} + +#[rstest::rstest] +fn srdd_srdi_reads_slot_contents( + #[values(0, 1, 2, 63, 100)] offset: u8, + #[values(0, 1, 2, 63, 100)] len: u8, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(create_example_buffer()); + program.extend([ + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 256), + op::mcli(RegId::HP, 256), + op::movi(0x10, offset as _), + op::movi(0x11, len as _), + ]); + + // Invoke the storage read instruction + program.push(if imm { + if len > Imm06::MAX.to_u8() { + return; // skip inconstructible test case + } + op::srdi(BUFFER, SLOT_KEY, 0x10, len) + } else { + op::srdd(BUFFER, SLOT_KEY, 0x10, 0x11) + }); + + // Log results + program.extend([ + op::move_(0x11, RegId::ERR), + op::movi(0x10, 256), + op::logd(0x11, RegId::ZERO, BUFFER, 0x10), + op::ret(RegId::ONE), + ]); + let receipts = call_contract_once(program); + assert_success(&receipts); + + let example_data: [u8; 256] = array::from_fn(|i| i as u8); + let mut expected = [0u8; 256]; + expected[..(len as usize)].copy_from_slice( + &example_data[(offset as usize)..(offset as usize + len as usize)], + ); + + for r in receipts { + let Receipt::LogData { ra, data, .. } = r else { + continue; + }; + assert_eq!(ra, 0, "$err should be cleared when read is successful"); + let data = data.as_ref().unwrap(); + assert_eq!(**data, expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn srdd_srdi_reading_nonexistent_slot_sets_err( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + + // Allocate slot key (all zeroes) + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + + // Invoke the storage read instruction + program.push(if imm { + op::srdi(RegId::HP, SLOT_KEY, RegId::ZERO, 0) + } else { + op::srdd(RegId::HP, SLOT_KEY, RegId::ZERO, RegId::ZERO) + }); + + // Log results + program.extend([ + op::log(RegId::ERR, RegId::ZERO, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]); + let receipts = call_contract_once(program); + assert_success(&receipts); + + for r in receipts { + let Receipt::Log { ra, .. } = r else { + continue; + }; + assert_eq!(ra, 1, "$err should set when reading nonexistent slot"); + return; + } + + panic!("Missing Log receipt"); +} + +#[rstest::rstest] +fn srdd_srdi_read_past_the_end_panics( + #[values( + StorageOverflowCase::Length, + StorageOverflowCase::Offset, + StorageOverflowCase::OffsetPlusLength + )] + case: StorageOverflowCase, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + let (len, offset): (u16, u16) = match case { + StorageOverflowCase::Length => (257, 0), + StorageOverflowCase::Offset => (0, 257), + StorageOverflowCase::OffsetPlusLength => (128, 129), + }; + + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(create_example_buffer()); + program.extend([ + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 256), + op::mcli(RegId::HP, 256), + op::movi(0x10, offset as _), + op::movi(0x11, len as _), + ]); + + // Invoke the storage read instruction + program.push(if imm { + if len > Imm06::MAX.to_u8() as _ { + return; // skip inconstructible test case + } + op::srdi(BUFFER, SLOT_KEY, 0x10, len as _) + } else { + op::srdd(BUFFER, SLOT_KEY, 0x10, 0x11) + }); + + // Log results + program.extend([ + op::movi(0x10, 256), + op::logd(RegId::ZERO, RegId::ZERO, BUFFER, 0x10), + op::ret(RegId::ONE), + ]); + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[rstest::rstest] +fn srdd_srdi_dst_buffer_outside_memory_panics( + #[values( + MemoryOverflowCase::OffByOne, + MemoryOverflowCase::Outside, + MemoryOverflowCase::Overflow + )] + case: MemoryOverflowCase, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(create_example_buffer()); + program.extend([ + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 256), + op::mcli(RegId::HP, 256), + ]); + program.extend(set_full_word(0x11, VM_MAX_RAM)); + program.push(match case { + MemoryOverflowCase::OffByOne => op::addi(0x10, 0x11, 0), + MemoryOverflowCase::Outside => op::addi(0x10, 0x11, 1), + MemoryOverflowCase::Overflow => op::not(0x10, RegId::ZERO), + }); + + program.push(if imm { + op::srdi(0x10, SLOT_KEY, RegId::ZERO, 1) + } else { + op::srdd(0x10, SLOT_KEY, RegId::ZERO, RegId::ONE) + }); + + // Unreachable + program.push(op::ret(RegId::ONE)); + + // Check + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +#[rstest::rstest] +fn swrd_swri_writes_storage_slot( + #[values(0, 1, 2, 63, 100)] len: usize, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x10, len as _), + op::aloc(0x10), + ]; + + program.push(if imm { + if len > Imm12::MAX.to_u16() as _ { + return; // skip inconstructible test case + } + op::swri(SLOT_KEY, 0x10, len as _) + } else { + op::swrd(SLOT_KEY, 0x10, 0x10) + }); + + // Read slot len and log results + program.extend([ + op::spld(0x10, SLOT_KEY), + op::log(0x10, RegId::ERR, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert_eq!(ra, len as u64, "Logged length should match written length"); + assert_eq!(rb, 0, "$err should be clear since slot exists"); + return; + } + + panic!("Missing Log receipt"); +} + +#[rstest::rstest] +fn swrd_swri_src_buffer_outside_memory_panics( + #[values( + MemoryOverflowCase::OffByOne, + MemoryOverflowCase::Outside, + MemoryOverflowCase::Overflow + )] + case: MemoryOverflowCase, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(set_full_word(0x11, VM_MAX_RAM)); + program.push(match case { + MemoryOverflowCase::OffByOne => op::addi(0x10, 0x11, 0), + MemoryOverflowCase::Outside => op::addi(0x10, 0x11, 1), + MemoryOverflowCase::Overflow => op::not(0x10, RegId::ZERO), + }); + + program.push(if imm { + op::swri(SLOT_KEY, 0x10, 1) + } else { + op::swrd(SLOT_KEY, 0x10, RegId::ONE) + }); + + // Unreachable + program.push(op::ret(RegId::ONE)); + + // Check + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +/// Note: swri cannot exceed max limit due to immediate size constraint, +/// unless the limit is unreasonably small, so we wont bother testing it here +#[rstest::rstest] +fn swrd_exceeding_slot_max_length_panics() { + const SLOT_KEY: RegId = RegId::new(0x38); + + let limit = ConsensusParameters::default() + .script_params() + .max_storage_slot_length(); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(set_full_word(0x11, limit + 1)); + program.extend([ + op::aloc(0x11), + op::swrd(SLOT_KEY, RegId::HP, 0x11), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[rstest::rstest] +fn supd_supi_exceeding_slot_max_length_panics( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + + let limit = ConsensusParameters::default() + .script_params() + .max_storage_slot_length(); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(set_full_word(0x11, limit)); + program.extend([ + op::aloc(0x11), + op::swrd(SLOT_KEY, RegId::HP, 0x11), + // Log here to prove that setup succeeded + op::log(RegId::ONE, RegId::ZERO, RegId::ZERO, RegId::ZERO), + op::not(0x10, RegId::ZERO), + ]); + + // Write beyond limit by extending by 1 byte + program.push(if imm { + op::supi(SLOT_KEY, RegId::HP, 0x10, 1) + } else { + op::supd(SLOT_KEY, RegId::HP, 0x10, RegId::ONE) + }); + + // Unreachable + program.push(op::ret(RegId::ONE)); + + // Check + let receipts = call_contract_once(program); + assert!( + receipts + .iter() + .any(|r| matches!(r, Receipt::Log { ra, .. } if *ra == 1)), + "Setup failed" + ); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[rstest::rstest] +fn supd_supi_treat_nonexisting_slot_as_empty( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::move_(BUFFER, RegId::HP), + ]; + + program.push(if imm { + op::supi(SLOT_KEY, BUFFER, RegId::ZERO, 32) + } else { + op::supd(SLOT_KEY, BUFFER, RegId::ZERO, 0x15) + }); + + // Log results + program.extend([ + op::spld(0x10, SLOT_KEY), + op::aloc(0x10), + op::srdd(RegId::HP, SLOT_KEY, RegId::ZERO, 0x10), + op::logd(RegId::ERR, RegId::ZERO, RegId::HP, 0x10), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + assert_success(&receipts); + + for r in receipts { + let Receipt::LogData { ra, data, .. } = r else { + continue; + }; + assert_eq!(ra, 0, "$err should be clear when read is successful"); + let data = data.as_ref().unwrap(); + let expected = [0u8; 32]; + assert_eq!(&**data, &expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn supd_supi_write_subrange_works( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + const W_LEN: u8 = 8; + const W_OFFSET: u8 = 4; + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + // Prepare slot with some initial data (zeroes) + op::swri(SLOT_KEY, SLOT_KEY, 32), + // Prepare data to be written (8 bytes of all bits set) + op::movi(0x15, 8), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + op::not(0x10, RegId::ZERO), + op::sw(BUFFER, 0x10, 0), + // Prepare subrange write + op::movi(0x10, W_OFFSET as _), + op::movi(0x11, W_LEN as _), + ]; + + program.push(if imm { + op::supi(SLOT_KEY, BUFFER, 0x10, W_LEN as _) + } else { + op::supd(SLOT_KEY, BUFFER, 0x10, 0x11) + }); + + // Log results + program.extend([ + op::spld(0x10, SLOT_KEY), + op::aloc(0x10), + op::srdd(RegId::HP, SLOT_KEY, RegId::ZERO, 0x10), + op::logd(RegId::ERR, RegId::ZERO, RegId::HP, 0x10), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + assert_success(&receipts); + + for r in receipts { + let Receipt::LogData { ra, data, .. } = r else { + continue; + }; + assert_eq!(ra, 0, "$err should be clear when read is successful"); + let data = data.as_ref().unwrap(); + let mut expected = [0u8; 32]; + expected[W_OFFSET as usize..(W_OFFSET as usize + W_LEN as usize)].fill(0xff); + assert_eq!(&**data, &expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn supd_supi_writing_past_end_extends_value( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + const W_LEN: u8 = 8; + const W_OFFSET: u8 = 4; + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::move_(BUFFER, RegId::HP), + // Prepare slot with some initial data + op::swri(SLOT_KEY, BUFFER, 8), + op::movi(0x10, W_OFFSET as _), + op::movi(0x11, W_LEN as _), + ]; + + program.push(if imm { + op::supi(SLOT_KEY, BUFFER, 0x10, W_LEN as _) + } else { + op::supd(SLOT_KEY, BUFFER, 0x10, 0x11) + }); + + // Log results + program.extend([ + op::spld(0x10, SLOT_KEY), + op::log(0x10, RegId::ERR, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + assert_success(&receipts); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert_eq!(ra, (W_OFFSET + W_LEN) as u64, "Final length incorrect"); + assert_eq!(rb, 0, "$err should be clear when read is successful"); + return; + } + + panic!("Missing Log receipt"); +} + +#[rstest::rstest] +fn supd_supi_extending_using_u64_MAX_works( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + const REPETITIONS: usize = 8; // 8 * 8 = 64 bytes + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, 8), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + op::sw(BUFFER, RegId::ONE, 0), + op::not(0x10, RegId::ZERO), + op::movi(0x11, 8), + ]; + + for _ in 0..REPETITIONS { + program.push(if imm { + op::supi(SLOT_KEY, BUFFER, 0x10, 8) + } else { + op::supd(SLOT_KEY, BUFFER, 0x10, 0x11) + }); + } + // Log results + program.extend([ + op::spld(0x10, SLOT_KEY), + op::aloc(0x10), + op::srdd(RegId::HP, SLOT_KEY, RegId::ZERO, 0x10), + op::logd(RegId::ERR, RegId::ZERO, RegId::HP, 0x10), + op::ret(RegId::ONE), + ]); + + // Check + let receipts = call_contract_once(program); + assert_success(&receipts); + + for r in receipts { + let Receipt::LogData { ra, data, .. } = r else { + continue; + }; + assert_eq!(ra, 0, "$err should be clear when read is successful"); + let data = data.as_ref().unwrap(); + let expected = 1u64.to_be_bytes().repeat(8); + assert_eq!(&**data, &expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn supd_supi_offset_past_existing_value_panics( + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::move_(BUFFER, RegId::HP), + op::movi(0x11, 33), + ]; + + program.push(if imm { + op::supi(SLOT_KEY, BUFFER, 0x11, 1) + } else { + op::supd(SLOT_KEY, BUFFER, 0x11, RegId::ONE) + }); + + // Unreachable + program.push(op::ret(RegId::ONE)); + + // Check + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[rstest::rstest] +fn supd_supi_src_buffer_outside_memory_panics( + #[values( + MemoryOverflowCase::OffByOne, + MemoryOverflowCase::Outside, + MemoryOverflowCase::Overflow + )] + case: MemoryOverflowCase, + #[values(true, false)] imm: bool, // use immediate instruction variant +) { + const SLOT_KEY: RegId = RegId::new(0x38); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(set_full_word(0x11, VM_MAX_RAM)); + program.push(match case { + MemoryOverflowCase::OffByOne => op::addi(0x10, 0x11, 0), + MemoryOverflowCase::Outside => op::addi(0x10, 0x11, 1), + MemoryOverflowCase::Overflow => op::not(0x10, RegId::ZERO), + }); + + program.push(if imm { + op::supi(SLOT_KEY, 0x10, RegId::ZERO, 1) + } else { + op::supd(SLOT_KEY, 0x10, RegId::ZERO, RegId::ONE) + }); + + // Unreachable + program.push(op::ret(RegId::ONE)); + + // Check + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +#[rstest::rstest] +fn spld_returns_correct_len(#[values(0, 1, 2, 3, 4, 5, 100)] len: u8) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let receipts = call_contract_once(vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, len as _), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, len as _), + op::spld(0x10, SLOT_KEY), + op::log(0x10, RegId::ERR, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]); + assert_success(&receipts); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert_eq!(ra, len as u64, "Logged length should match slot length"); + assert_eq!(rb, 0, "$err should clear when reading existing slot"); + return; + } + + panic!("Missing Log receipt"); +} + +#[test] +fn spld_reports_nonexisting_slots_in_err() { + let receipts = call_contract_once(vec![ + op::movi(0x10, 1234), // dummy value to make sure it's overwritten + op::spld(0x10, RegId::FP), // nonexistent slot + op::log(0x10, RegId::ERR, RegId::ZERO, RegId::ZERO), + op::ret(RegId::ONE), + ]); + assert_success(&receipts); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert_eq!(ra, 0, "Logged length should be zero for nonexistent slot"); + assert_eq!(rb, 1, "$err should set when reading nonexistent slot"); + return; + } + + panic!("Missing Log receipt"); +} + +#[rstest::rstest] +fn spcp_copies_whole_value(#[values(0, 1, 2, 63, 100)] len: u8) { + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(create_example_buffer()); + program.extend([ + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, len as _), + op::spld(0x11, SLOT_KEY), + op::mcli(BUFFER, 256), + op::spcp(BUFFER, RegId::ZERO, 0x11, 0), + op::movi(0x10, 256), + op::logd(RegId::ZERO, RegId::ZERO, BUFFER, 0x10), + op::ret(RegId::ONE), + ]); + + let receipts = call_contract_once(program); + assert_success(&receipts); + + let example_data: [u8; 256] = array::from_fn(|i| i as u8); + let mut expected = [0u8; 256]; + expected[..(len as usize)].copy_from_slice(&example_data[..len as usize]); + + for r in receipts { + let Receipt::LogData { data, .. } = r else { + continue; + }; + let data = data.as_ref().unwrap(); + assert_eq!(**data, expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn spcp_copies_correct_subslice( + #[values(0, 1, 2, 63, 100)] offset: u8, + #[values(0, 1, 2, 63, 100)] len: u8, +) { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + ]; + program.extend(create_example_buffer()); + program.extend([ + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 256), + op::spld(DISCARD, SLOT_KEY), + op::mcli(RegId::HP, 256), + op::movi(0x10, offset as _), + op::movi(0x11, len as _), + op::spcp(BUFFER, 0x10, 0x11, 0), + op::movi(0x10, 256), + op::logd(RegId::ZERO, RegId::ZERO, BUFFER, 0x10), + op::ret(RegId::ONE), + ]); + + let receipts = call_contract_once(program); + assert_success(&receipts); + + let example_data: [u8; 256] = array::from_fn(|i| i as u8); + let mut expected = [0u8; 256]; + expected[..(len as usize)].copy_from_slice( + &example_data[(offset as usize)..(offset as usize + len as usize)], + ); + + for r in receipts { + let Receipt::LogData { data, .. } = r else { + continue; + }; + let data = data.as_ref().unwrap(); + assert_eq!(**data, expected); + return; + } + + panic!("Missing LogData receipt"); +} + +#[rstest::rstest] +fn spcp_panics_if_preloaded_data_range_is_invalid( + #[values( + StorageOverflowCase::Length, + StorageOverflowCase::Offset, + StorageOverflowCase::OffsetPlusLength + )] + case: StorageOverflowCase, +) { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let (offset, len): (u16, u16) = match case { + StorageOverflowCase::Length => (0, 33), + StorageOverflowCase::Offset => (33, 0), + StorageOverflowCase::OffsetPlusLength => (16, 17), + }; + + let receipts = call_contract_once(vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::movi(0x15, 64), + op::aloc(0x15), + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 32), + op::spld(DISCARD, SLOT_KEY), + op::movi(0x10, offset as _), + op::movi(0x11, len as _), + op::spcp(BUFFER, 0x10, 0x11, 0), + op::ret(RegId::ONE), + ]); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); +} + +#[rstest::rstest] +fn spcp_panics_if_dst_range_is_invalid( + #[values( + MemoryOverflowCase::OffByOne, + MemoryOverflowCase::Outside, + MemoryOverflowCase::Overflow + )] + case: MemoryOverflowCase, +) { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let mut program = set_full_word(0x20, VM_MAX_RAM); + program.push(match case { + MemoryOverflowCase::OffByOne => op::addi(0x10, 0x20, 0), + MemoryOverflowCase::Outside => op::addi(0x10, 0x20, 1), + MemoryOverflowCase::Overflow => op::not(0x10, RegId::ZERO), + }); + program.extend([ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 32), + op::spld(DISCARD, SLOT_KEY), + op::spcp(0x10, RegId::ZERO, RegId::ONE, 0), + op::ret(RegId::ONE), + ]); + + let receipts = call_contract_once(program); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +#[test] +fn spcp_panics_if_length_field_sum_overflows() { + const DISCARD: RegId = RegId::new(0x39); + const SLOT_KEY: RegId = RegId::new(0x38); + const BUFFER: RegId = RegId::new(0x37); + + let receipts = call_contract_once(vec![ + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + op::move_(BUFFER, RegId::HP), + op::swri(SLOT_KEY, BUFFER, 32), + op::spld(DISCARD, SLOT_KEY), + op::not(0x10, RegId::ZERO), + op::spcp(BUFFER, RegId::ZERO, 0x10, 1), + op::ret(RegId::ONE), + ]); + assert_panics(&receipts, PanicReason::MemoryOverflow); +} + +#[test] +fn preload_is_cleared_on_contract_call() { + const SLOT_KEY: RegId = RegId::new(0x38); + const IS_NESTED: RegId = RegId::new(0x36); + + let receipts = call_contract_once(vec![ + // If in nested context, attempt to copy from the preloaded data + // which should now be empty and thus cause a panic. + // Just before panicing instruction, log something to make sure we + // got here. + op::jnef(IS_NESTED, RegId::ONE, RegId::ZERO, 5), + op::movi(0x15, 32), + op::aloc(0x15), + op::log(RegId::ONE, RegId::ZERO, RegId::ZERO, RegId::ZERO), + op::spcp(RegId::HP, RegId::ZERO, RegId::ONE, 0), + op::ret(RegId::ONE), + // Allocate zeroed slot key + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + // Write dummy value + op::swri(SLOT_KEY, SLOT_KEY, 32), + // Call this contract recursively + op::movi(IS_NESTED, 1), + op::gtf_args(0x10, RegId::ZERO, GTFArgs::ScriptData), + op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS), + // Unreachable + op::divi(RegId::ZERO, RegId::ZERO, 0), + ]); + dbg!(&receipts); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert!(ra == 1 && rb == 0, "Should have reached nested context"); + return; + } + + panic!("Missing Log receipt"); +} + +#[rstest::rstest] +fn preload_is_cleared_on_contract_return( + #[values(true, false)] return_data: bool, // test retd instead of ret instruction +) { + const SLOT_KEY: RegId = RegId::new(0x38); + const IS_NESTED: RegId = RegId::new(0x36); + + let receipts = call_contract_once(vec![ + // Allocate zeroed slot key + op::movi(0x15, 32), + op::aloc(0x15), + op::move_(SLOT_KEY, RegId::HP), + // If in nested context, write the a dummy value and return immediately + op::jnef(IS_NESTED, RegId::ONE, RegId::ZERO, 2), + op::swri(SLOT_KEY, SLOT_KEY, 32), + if return_data { + op::retd(RegId::ZERO, RegId::ONE) + } else { + op::ret(RegId::ONE) + }, + // Call this contract recursively + op::movi(IS_NESTED, 1), + op::gtf_args(0x10, RegId::ZERO, GTFArgs::ScriptData), + op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS), + // Now in the outer context, try to copy from preloaded data + // which should be empty and thus cause a panic. + // Just before panicing instruction, log something to make sure we + // got here. + op::log(RegId::ONE, RegId::ZERO, RegId::ZERO, RegId::ZERO), + op::spcp(RegId::HP, RegId::ZERO, RegId::ONE, 0), + // Unreachable + op::divi(RegId::ZERO, RegId::ZERO, 0), + ]); + dbg!(&receipts); + assert_panics(&receipts, PanicReason::StorageOutOfBounds); + + for r in receipts { + let Receipt::Log { ra, rb, .. } = r else { + continue; + }; + assert!(ra == 1 && rb == 0, "Should have reached nested context"); + return; + } + + panic!("Missing Log receipt"); +}