From d2c76e240afb004dee4f226d053d31d1d88d5a1c Mon Sep 17 00:00:00 2001 From: Hannes Karppila <2204863+Dentosal@users.noreply.github.com> Date: Fri, 26 Jul 2024 21:24:18 +0300 Subject: [PATCH] Blob tx and instructions (#780) * Add blob tx * Add some tests * Add opcodes for blob storage access * Freeze stack on DLDC instead of copying it over the data * Add internal context test cases * Add changelog entry * Remove bldc. Allow executable stack. * Merge RunResult helper type with AluResult * Move blob tx data to witness like other txs do * Fixes for fuel-core * Merge RunResult types after branch merge * Fix no_std imports * Add blob id validation rule * Add base_asset_id to blob::CheckedMetadata * Use LDC with mode argument instead of having separate blob instructions * Restore BSIZ and BLDD instructions * fmt * Update doc comment (PR feedback) * Improve test case names (pr feedback) * Apply PR review suggestions * Fix the serialization for the blob * Fix the serialization for the blob * Make clippy happy * Add offset and encoding tests * Add as_blob(_mut) for tx * fix clippy * Fix offset tests (were targeting wrong tx type) * Update fuel-tx/src/transaction/types/blob.rs Co-authored-by: Green Baneling * Address PR feedback * More PR comments * Disallow blob reupload * Make memory access determintistic across 32 and 64 bit platforms * Remove tests that were dependent on executable stack * Charge BLDD based on max(load_len, blob_size) * Add blob_id field getter test * clippy * Fix wrong test name * Address PR feedback * Remove redundant coin validity check clause --------- Co-authored-by: green --- CHANGELOG.md | 5 + fuel-asm/src/lib.rs | 9 +- fuel-asm/src/panic_reason.rs | 6 + fuel-tx/src/builder.rs | 16 + fuel-tx/src/lib.rs | 5 + fuel-tx/src/test_helper.rs | 46 +- fuel-tx/src/tests/offset.rs | 56 +- fuel-tx/src/tests/valid_cases/transaction.rs | 1 + .../src/tests/valid_cases/transaction/blob.rs | 663 ++++++++++++++++++ fuel-tx/src/transaction.rs | 83 +++ .../src/transaction/consensus_parameters.rs | 1 + .../transaction/consensus_parameters/gas.rs | 525 +++++++++++++- .../gas/default_gas_costs.rs | 11 +- fuel-tx/src/transaction/id.rs | 2 + fuel-tx/src/transaction/metadata.rs | 2 + fuel-tx/src/transaction/repr.rs | 2 + fuel-tx/src/transaction/types.rs | 7 + fuel-tx/src/transaction/types/blob.rs | 243 +++++++ fuel-tx/src/transaction/validity.rs | 2 + fuel-tx/src/transaction/validity/error.rs | 2 + fuel-types/src/array_types.rs | 1 + fuel-vm/src/checked_transaction.rs | 38 + fuel-vm/src/checked_transaction/types.rs | 77 ++ fuel-vm/src/interpreter.rs | 40 +- fuel-vm/src/interpreter/blob.rs | 98 +++ fuel-vm/src/interpreter/blockchain.rs | 136 +++- fuel-vm/src/interpreter/diff/storage.rs | 38 +- .../src/interpreter/executors/instruction.rs | 16 +- .../instruction/tests/reserved_registers.rs | 4 + fuel-vm/src/interpreter/executors/main.rs | 103 +++ fuel-vm/src/interpreter/gas.rs | 18 + fuel-vm/src/interpreter/initialization.rs | 1 - fuel-vm/src/interpreter/memory.rs | 24 +- fuel-vm/src/interpreter/memory/tests.rs | 2 +- fuel-vm/src/lib.rs | 1 + fuel-vm/src/memory_client.rs | 6 + fuel-vm/src/storage.rs | 5 + fuel-vm/src/storage/blob_data.rs | 92 +++ fuel-vm/src/storage/interpreter.rs | 5 + fuel-vm/src/storage/memory.rs | 110 ++- fuel-vm/src/storage/predicate.rs | 54 ++ fuel-vm/src/tests/alu.rs | 117 ++-- fuel-vm/src/tests/blob.rs | 389 ++++++++++ fuel-vm/src/tests/blockchain.rs | 477 ++++++++++++- fuel-vm/src/tests/coins.rs | 82 +-- fuel-vm/src/tests/encoding.rs | 36 +- fuel-vm/src/tests/mod.rs | 1 + fuel-vm/src/tests/test_helpers.rs | 71 +- fuel-vm/src/transactor.rs | 25 + fuel-vm/src/util.rs | 36 + 50 files changed, 3584 insertions(+), 206 deletions(-) create mode 100644 fuel-tx/src/tests/valid_cases/transaction/blob.rs create mode 100644 fuel-tx/src/transaction/types/blob.rs create mode 100644 fuel-vm/src/interpreter/blob.rs create mode 100644 fuel-vm/src/storage/blob_data.rs create mode 100644 fuel-vm/src/tests/blob.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1424e9d4ee..cb554a2cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +#### Breaking +- [#780](https://github.com/FuelLabs/fuel-vm/pull/780): Added `Blob` transaction, and `BSIZ` and `BLDD` instructions. Also allows `LDC` to load blobs. + ## [Version 0.55.0] ### Added diff --git a/fuel-asm/src/lib.rs b/fuel-asm/src/lib.rs index dbcd13b34b..3ce1014b69 100644 --- a/fuel-asm/src/lib.rs +++ b/fuel-asm/src/lib.rs @@ -183,7 +183,7 @@ impl_instructions! { "Get current block proposer's address." 0x31 CB cb [dst: RegId] "Load a contract's code as executable." - 0x32 LDC ldc [contract_id_addr: RegId offset: RegId len: RegId] + 0x32 LDC ldc [contract_id_addr: RegId offset: RegId len: RegId mode: Imm06] "Log an event." 0x33 LOG log [a: RegId b: RegId c: RegId d: RegId] "Log data." @@ -340,6 +340,11 @@ impl_instructions! { "Call external function" 0xb0 ECAL ecal [a: RegId b: RegId c: RegId d: RegId] + + "Get blob size" + 0xba BSIZ bsiz [dst: RegId blob_id_ptr: RegId] + "Load blob as data" + 0xbb BLDD bldd [dst_ptr: RegId blob_id_ptr: RegId offset: RegId len: RegId] } impl Instruction { @@ -988,7 +993,7 @@ fn check_predicate_allowed() { let should_allow = match repr { BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LDC | LOG | LOGD | MINT | RETD | RVRT | SMO | SCWQ | SRW | SRWQ | SWW | SWWQ - | TIME | TR | TRO | ECAL => false, + | TIME | TR | TRO | ECAL | BSIZ | BLDD => 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 e2640d2e40..ff57240a22 100644 --- a/fuel-asm/src/panic_reason.rs +++ b/fuel-asm/src/panic_reason.rs @@ -147,6 +147,12 @@ enum_from! { BytecodeAlreadyUploaded = 0x34, /// The part of the bytecode is not sequentially connected to the previous parts. ThePartIsNotSequentiallyConnected = 0x35, + /// The requested blob is not found. + BlobNotFound = 0x36, + /// The blob was already + BlobIdAlreadyUploaded = 0x37, + /// Active gas costs do not define the cost for this instruction. + GasCostNotDefined = 0x38, } } diff --git a/fuel-tx/src/builder.rs b/fuel-tx/src/builder.rs index 5c32318c8e..6adb846424 100644 --- a/fuel-tx/src/builder.rs +++ b/fuel-tx/src/builder.rs @@ -14,6 +14,8 @@ use crate::{ Executable, Script, }, + Blob, + BlobBody, ConsensusParameters, ContractParameters, CreateMetadata, @@ -210,6 +212,20 @@ impl TransactionBuilder { } } +impl TransactionBuilder { + pub fn blob(body: BlobBody) -> Self { + let tx = Blob { + body, + policies: Policies::new().with_max_fee(0), + inputs: Default::default(), + outputs: Default::default(), + witnesses: Default::default(), + metadata: None, + }; + Self::with_tx(tx) + } +} + impl TransactionBuilder { pub fn mint( block_height: BlockHeight, diff --git a/fuel-tx/src/lib.rs b/fuel-tx/src/lib.rs index 040673c394..8d3c987c5e 100644 --- a/fuel-tx/src/lib.rs +++ b/fuel-tx/src/lib.rs @@ -30,6 +30,7 @@ pub use fuel_asm::{ pub use fuel_types::{ Address, AssetId, + BlobId, Bytes32, Bytes4, Bytes64, @@ -83,6 +84,10 @@ pub use transaction::{ output::Output, output::OutputRepr, policies, + Blob, + BlobBody, + BlobIdExt, + BlobMetadata, Cacheable, Chargeable, ChargeableMetadata, diff --git a/fuel-tx/src/test_helper.rs b/fuel-tx/src/test_helper.rs index 1049fb6dcf..c3ae9cc16b 100644 --- a/fuel-tx/src/test_helper.rs +++ b/fuel-tx/src/test_helper.rs @@ -46,6 +46,9 @@ mod use_std { }; use crate::{ field, + Blob, + BlobBody, + BlobIdExt, Buildable, ConsensusParameters, Contract, @@ -68,7 +71,10 @@ mod use_std { Hasher, SecretKey, }; - use fuel_types::canonical::Deserialize; + use fuel_types::{ + canonical::Deserialize, + BlobId, + }; use rand::{ distributions::{ Distribution, @@ -134,6 +140,7 @@ mod use_std { Transaction::Mint(_) => (), Transaction::Upgrade(_) => (), Transaction::Upload(_) => (), + Transaction::Blob(_) => (), }) .unwrap_or(()); @@ -443,6 +450,32 @@ mod use_std { } } + impl TransactionFactory + where + R: Rng + CryptoRng, + { + pub fn transaction(&mut self) -> Blob { + self.transaction_with_keys().0 + } + + pub fn transaction_with_keys(&mut self) -> (Blob, Vec) { + let len = self.rng.gen_range(1..1024 * 1024); + + let mut bytecode = alloc::vec![0u8; len]; + self.rng.fill_bytes(bytecode.as_mut_slice()); + + let mut builder = TransactionBuilder::::blob(BlobBody { + id: BlobId::compute(&bytecode), + witness_index: 0, + }); + debug_assert_eq!(builder.witnesses().len(), 0); + builder.add_witness(bytecode.into()); + + let keys = self.fill_transaction(&mut builder); + (builder.finalize(), keys) + } + } + impl TransactionFactory where R: Rng + CryptoRng, @@ -506,6 +539,17 @@ mod use_std { } } + impl Iterator for TransactionFactory + where + R: Rng + CryptoRng, + { + type Item = (Blob, Vec); + + fn next(&mut self) -> Option<(Blob, Vec)> { + Some(self.transaction_with_keys()) + } + } + impl Iterator for TransactionFactory where R: Rng + CryptoRng, diff --git a/fuel-tx/src/tests/offset.rs b/fuel-tx/src/tests/offset.rs index 212850aa4e..9a53a8ecc5 100644 --- a/fuel-tx/src/tests/offset.rs +++ b/fuel-tx/src/tests/offset.rs @@ -5,6 +5,7 @@ use crate::{ field::{ + BlobId as BlobIdField, InputContract, Inputs, MintAmount, @@ -68,6 +69,14 @@ struct TestedFields { } fn chargeable_transaction_parts(tx: &Tx, bytes: &[u8], cases: &mut TestedFields) +where + Tx: Buildable, +{ + inputs_assert(tx, bytes, cases); + outputs_assert(tx, bytes, cases); +} + +fn inputs_assert(tx: &Tx, bytes: &[u8], cases: &mut TestedFields) where Tx: Buildable, { @@ -247,8 +256,6 @@ where } } }); - - outputs_assert(tx, bytes, cases); } fn outputs_assert(tx: &Tx, bytes: &[u8], cases: &mut TestedFields) { @@ -530,6 +537,51 @@ fn tx_offset_upload() { assert!(cases.output_contract_created_id); } +#[test] +fn tx_offset_blob() { + let mut cases = TestedFields::default(); + let number_cases = 100; + + // The seed will define how the transaction factory will generate a new transaction. + // Different seeds might implicate on how many of the cases we cover - since we + // assert coverage for all scenarios with the boolean variables above, we need to + // pick a seed that, with low number of cases, will cover everything. + TransactionFactory::<_, Blob>::from_seed(1295) + .take(number_cases) + .for_each(|(tx, _)| { + let bytes = tx.to_bytes(); + + // Blob id + let offs = tx.blob_id_offset(); + assert_eq!(bytes[offs..offs + BlobId::LEN], **tx.blob_id()); + + chargeable_transaction_parts(&tx, &bytes, &mut cases); + }); + + // Chargeable parts + assert!(cases.utxo_id); + assert!(cases.owner); + assert!(cases.asset_id); + assert!(cases.predicate_coin); + assert!(cases.predicate_message); + assert!(cases.predicate_data_coin); + assert!(cases.predicate_data_message); + assert!(cases.contract_balance_root); + assert!(cases.contract_state_root); + assert!(cases.contract_id); + assert!(cases.sender); + assert!(cases.recipient); + assert!(cases.message_data); + assert!(cases.message_predicate); + assert!(cases.message_predicate_data); + assert!(cases.output_to); + assert!(cases.output_asset_id); + assert!(cases.output_balance_root); + assert!(cases.output_contract_state_root); + assert!(cases.output_contract_created_state_root); + assert!(cases.output_contract_created_id); +} + #[test] fn tx_offset_mint() { let number_cases = 100; diff --git a/fuel-tx/src/tests/valid_cases/transaction.rs b/fuel-tx/src/tests/valid_cases/transaction.rs index 30bcb8c329..e3b8a636b3 100644 --- a/fuel-tx/src/tests/valid_cases/transaction.rs +++ b/fuel-tx/src/tests/valid_cases/transaction.rs @@ -1,6 +1,7 @@ #![allow(clippy::cast_possible_truncation)] #![allow(non_snake_case)] +mod blob; mod upgrade; mod upload; diff --git a/fuel-tx/src/tests/valid_cases/transaction/blob.rs b/fuel-tx/src/tests/valid_cases/transaction/blob.rs new file mode 100644 index 0000000000..a35edd1d3a --- /dev/null +++ b/fuel-tx/src/tests/valid_cases/transaction/blob.rs @@ -0,0 +1,663 @@ +#![allow(clippy::cast_possible_truncation)] +#![allow(non_snake_case)] + +use super::*; +use crate::field::Witnesses; +use fuel_asm::op; +use fuel_types::{ + BlobId, + BlockHeight, +}; + +// Creates a predicate that always is valid - returns `true`. +fn predicate() -> Vec { + vec![op::ret(1)].into_iter().collect::>() +} + +fn test_params() -> ConsensusParameters { + ConsensusParameters::default() +} + +fn valid_blob_transaction() -> TransactionBuilder { + let blob_data = vec![1; 100]; + let mut builder = TransactionBuilder::blob(BlobBody { + id: BlobId::compute(&blob_data), + witness_index: 0, + }); + builder.add_witness(blob_data.into()); + builder.max_fee_limit(0); + builder.add_input(Input::coin_predicate( + Default::default(), + Input::predicate_owner(predicate()), + Default::default(), + AssetId::BASE, + Default::default(), + Default::default(), + predicate(), + vec![], + )); + + builder +} + +#[test] +fn check__valid_blob_transaction_passes_check() { + // Given + let block_height: BlockHeight = 1000.into(); + let tx = valid_blob_transaction().finalize(); + + // When + let tx = tx.check(block_height, &test_params()); + + // Then + assert_eq!(tx, Ok(())); +} + +#[test] +fn check__fails_if_maturity_not_met() { + // Given + let block_height: BlockHeight = 1000.into(); + let failing_block_height = block_height.succ().unwrap(); + let tx = valid_blob_transaction() + .maturity(failing_block_height) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionMaturity), result); +} + +#[test] +fn check__fails_if_blob_id_doesnt_match_payload() { + // Given + let blob_data = vec![1; 100]; + let mut builder = TransactionBuilder::blob(BlobBody { + id: BlobId::from_bytes(&[0xf0; 32]).unwrap(), + witness_index: 0, + }); + builder.add_witness(blob_data.into()); + builder.max_fee_limit(0); + builder.add_input(Input::coin_predicate( + Default::default(), + Input::predicate_owner(predicate()), + Default::default(), + AssetId::BASE, + Default::default(), + Default::default(), + predicate(), + vec![], + )); + + let block_height: BlockHeight = 1000.into(); + let tx = builder.maturity(block_height).finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionBlobIdVerificationFailed), + result + ); +} + +#[test] +fn check__not_set_witness_limit_success() { + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction().finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert!(result.is_ok()); +} + +#[test] +fn check__set_witness_limit_for_empty_witness_success() { + // Given + let block_height = 1000.into(); + let subsection_size = valid_blob_transaction() + .finalize() + .witnesses() + .size_dynamic(); + let limit = subsection_size + Signature::LEN + vec![0u8; 0].size_static(); + let tx = valid_blob_transaction() + .witness_limit(limit as u64) + .add_witness(vec![0; Signature::LEN].into()) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Ok(()), result); +} + +#[test] +fn check__set_witness_limit_less_than_witness_data_size_fails() { + let block_height = 1000.into(); + let subsection_size = valid_blob_transaction() + .finalize() + .witnesses() + .size_dynamic(); + let limit = subsection_size + Signature::LEN + vec![0u8; 0].size_static(); + + // Given + let failing_limit = limit - 1; + let tx = valid_blob_transaction() + .witness_limit(failing_limit as u64) + .add_witness(vec![0; Signature::LEN].into()) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionWitnessLimitExceeded), result); +} + +#[test] +fn check__no_max_fee_fails() { + let block_height = 1000.into(); + let mut tx = valid_blob_transaction().add_random_fee_input().finalize(); + + // Given + tx.policies_mut().set(PolicyType::MaxFee, None); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionMaxFeeNotSet), result); +} + +#[test] +fn check__reached_max_inputs() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let mut builder = valid_blob_transaction(); + + while builder.outputs().len() < test_params().tx_params().max_outputs() as usize { + builder.add_output(Output::coin(rng.gen(), rng.gen(), AssetId::BASE)); + } + + // Given + let secrets: Vec = + (0..1 + test_params().tx_params().max_inputs() as usize - builder.inputs().len()) + .map(|_| SecretKey::random(rng)) + .collect(); + secrets.iter().for_each(|k| { + builder.add_unsigned_coin_input( + *k, + rng.gen(), + rng.gen(), + AssetId::BASE, + rng.gen(), + ); + }); + while builder.witnesses().len() < test_params().tx_params().max_witnesses() as usize { + builder.add_witness(generate_bytes(rng).into()); + } + let tx = builder.finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionInputsMax), result); +} + +#[test] +fn check__reached_max_outputs() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let mut builder = valid_blob_transaction(); + + let secrets: Vec = (0..test_params().tx_params().max_inputs() as usize + - builder.inputs().len()) + .map(|_| SecretKey::random(rng)) + .collect(); + secrets.iter().for_each(|k| { + builder.add_unsigned_coin_input( + *k, + rng.gen(), + rng.gen(), + AssetId::BASE, + rng.gen(), + ); + }); + while builder.witnesses().len() < test_params().tx_params().max_witnesses() as usize { + builder.add_witness(generate_bytes(rng).into()); + } + + // Given + while builder.outputs().len() < test_params().tx_params().max_outputs() as usize + 1 { + builder.add_output(Output::coin(rng.gen(), rng.gen(), AssetId::BASE)); + } + let tx = builder.finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionOutputsMax), result); +} + +#[test] +fn check__reached_max_witnesses() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let mut builder = valid_blob_transaction(); + + let secrets: Vec = (0..test_params().tx_params().max_inputs() as usize + - builder.inputs().len()) + .map(|_| SecretKey::random(rng)) + .collect(); + secrets.iter().for_each(|k| { + builder.add_unsigned_coin_input( + *k, + rng.gen(), + rng.gen(), + AssetId::BASE, + rng.gen(), + ); + }); + while builder.outputs().len() < test_params().tx_params().max_outputs() as usize { + builder.add_output(Output::coin(rng.gen(), rng.gen(), AssetId::BASE)); + } + + // Given + while builder.witnesses().len() + < test_params().tx_params().max_witnesses() as usize + 1 + { + builder.add_witness(generate_bytes(rng).into()); + } + let tx = builder.finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionWitnessesMax), result); +} + +#[test] +fn check__fail_if_output_change_asset_id_is_duplicated() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let secret = SecretKey::random(rng); + + // Given + let a: AssetId = rng.gen(); + let tx = valid_blob_transaction() + .add_unsigned_coin_input(secret, rng.gen(), rng.gen(), a, rng.gen()) + .add_output(Output::change(rng.gen(), rng.next_u64(), a)) + .add_output(Output::change(rng.gen(), rng.next_u64(), a)) + .finalize(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionOutputChangeAssetIdDuplicated(a)), + result + ); +} + +#[test] +fn check__fail_if_output_asset_id_not_in_inputs() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let c: AssetId = rng.gen(); + let tx = valid_blob_transaction() + .add_output(Output::change(rng.gen(), rng.next_u64(), c)) + .finalize(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionOutputChangeAssetIdNotFound(c)), + result + ); +} + +#[test] +fn check__cannot_have_contract_input() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_input(Input::contract( + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + rng.gen(), + )) + .add_output(Output::contract(1, rng.gen(), rng.gen())) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionInputContainsContract { index: 1 }), + result + ); +} + +#[test] +fn check__cannot_have_coin_with_non_base_asset_id() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let secret = SecretKey::random(rng); + + // Given + let tx = valid_blob_transaction() + .add_unsigned_coin_input(secret, rng.gen(), rng.gen(), rng.gen(), rng.gen()) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionInputContainsNonBaseAssetId { index: 1 }), + result + ); +} + +#[test] +fn check__can_have_message_coin_input() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let secret = SecretKey::random(rng); + + // Given + let empty_data = vec![]; + let tx = valid_blob_transaction() + .add_unsigned_message_input(secret, rng.gen(), rng.gen(), rng.gen(), empty_data) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Ok(()), result); +} + +#[test] +fn check__cannot_have_message_data_input() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let secret = SecretKey::random(rng); + + // Given + let not_empty_data = vec![0x1]; + let tx = valid_blob_transaction() + .add_unsigned_message_input( + secret, + rng.gen(), + rng.gen(), + rng.gen(), + not_empty_data, + ) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionInputContainsMessageData { index: 1 }), + result + ); +} + +#[test] +fn check__cannot_have_variable_output() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_output(Output::variable(rng.gen(), rng.gen(), rng.gen())) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionOutputContainsVariable { index: 0 }), + result + ); +} + +#[test] +fn check__cannot_have_contract_output() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_output(Output::Contract(rng.gen())) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::OutputContractInputIndex { index: 0 }), + result + ); +} + +#[test] +fn check__cannot_have_create_contract_output() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_output(Output::contract_created(rng.gen(), rng.gen())) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionOutputContainsContractCreated { index: 0 }), + result + ); +} + +#[test] +fn check__can_have_change_output() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_output(Output::change(rng.gen(), rng.gen(), AssetId::BASE)) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Ok(()), result); +} + +#[test] +fn check__errors_if_change_is_wrong_asset() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + + // Given + let a: AssetId = rng.gen(); + let tx = valid_blob_transaction() + .add_unsigned_coin_input( + SecretKey::random(rng), + rng.gen(), + rng.gen(), + a, + rng.gen(), + ) + .add_output(Output::change(rng.gen(), rng.gen(), a)) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!( + Err(ValidityError::TransactionInputContainsNonBaseAssetId { index: 1 }), + result, + ); +} + +#[test] +fn check__errors_when_transactions_too_big() { + let block_height = 1000.into(); + + // Given + let tx = valid_blob_transaction() + .add_witness(vec![0; test_params().tx_params().max_size() as usize].into()) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::TransactionSizeLimitExceeded), result); +} + +mod inputs { + use super::*; + use itertools::Itertools; + + #[test] + fn check__succeeds_with_correct_coin_predicate_owner() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let predicate = (0..100).map(|_| rng.gen()).collect_vec(); + let owner: Address = Input::predicate_owner(&predicate); + + // Given + let tx = valid_blob_transaction() + .add_input(Input::coin_predicate( + rng.gen(), + owner, + rng.gen(), + AssetId::BASE, + rng.gen(), + 0, + predicate, + vec![], + )) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Ok(()), result); + } + + #[test] + fn check__fails_with_incorrect_coin_predicate_owner() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let predicate = (0..100).map(|_| rng.gen()).collect_vec(); + let incorrect_owner: Address = [1; 32].into(); + + // Given + let tx = valid_blob_transaction() + .add_input(Input::coin_predicate( + rng.gen(), + incorrect_owner, + rng.gen(), + AssetId::BASE, + rng.gen(), + 0, + predicate, + vec![], + )) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::InputPredicateOwner { index: 1 }), result); + } + + #[test] + fn check__succeeds_with_correct_coin_predicate_input_owner() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let predicate = (0..100).map(|_| rng.gen()).collect_vec(); + let owner: Address = Input::predicate_owner(&predicate); + + // Given + let tx = valid_blob_transaction() + .add_input(Input::message_coin_predicate( + rng.gen(), + owner, + rng.gen(), + rng.gen(), + 0, + predicate, + vec![], + )) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Ok(()), result); + } + + #[test] + fn check__fails_with_incorrect_message_predicate_owner() { + let rng = &mut StdRng::seed_from_u64(8586); + let block_height = 1000.into(); + let predicate = (0..100).map(|_| rng.gen()).collect_vec(); + let incorrect_owner: Address = [1; 32].into(); + + // Given + let tx = valid_blob_transaction() + .add_input(Input::message_coin_predicate( + rng.gen(), + incorrect_owner, + rng.gen(), + rng.gen(), + 0, + predicate, + vec![], + )) + .finalize_as_transaction(); + + // When + let result = tx.check(block_height, &test_params()); + + // Then + assert_eq!(Err(ValidityError::InputPredicateOwner { index: 1 }), result); + } +} diff --git a/fuel-tx/src/transaction.rs b/fuel-tx/src/transaction.rs index d7b38544b0..ce3b3dd1e0 100644 --- a/fuel-tx/src/transaction.rs +++ b/fuel-tx/src/transaction.rs @@ -24,6 +24,7 @@ use fuel_types::{ }, Address, AssetId, + BlobId, Bytes32, Nonce, Salt, @@ -100,6 +101,7 @@ pub enum Transaction { Mint(Mint), Upgrade(Upgrade), Upload(Upload), + Blob(Blob), } #[cfg(feature = "test-helpers")] @@ -287,6 +289,45 @@ impl Transaction { } } + pub fn blob( + body: BlobBody, + policies: Policies, + inputs: Vec, + outputs: Vec, + witnesses: Vec, + ) -> Blob { + Blob { + body, + policies, + inputs, + outputs, + witnesses, + metadata: None, + } + } + + pub fn blob_from_bytes( + bytes: Vec, + policies: Policies, + inputs: Vec, + outputs: Vec, + mut witnesses: Vec, + ) -> Blob { + let body = BlobBody { + id: BlobId::compute(&bytes), + witness_index: u16::try_from(witnesses.len()).unwrap_or(u16::MAX), + }; + witnesses.push(bytes.into()); + Blob { + body, + policies, + inputs, + outputs, + witnesses, + metadata: None, + } + } + /// Convert the type into a JSON string /// /// This is implemented as infallible because serde_json will fail only if the type @@ -334,6 +375,10 @@ impl Transaction { matches!(self, Self::Upload { .. }) } + pub const fn is_blob(&self) -> bool { + matches!(self, Self::Blob { .. }) + } + pub const fn as_script(&self) -> Option<&Script> { match self { Self::Script(script) => Some(script), @@ -403,6 +448,20 @@ impl Transaction { _ => None, } } + + pub const fn as_blob(&self) -> Option<&Blob> { + match self { + Self::Blob(tx) => Some(tx), + _ => None, + } + } + + pub fn as_blob_mut(&mut self) -> Option<&mut Blob> { + match self { + Self::Blob(tx) => Some(tx), + _ => None, + } + } } pub trait Executable: field::Inputs + field::Outputs + field::Witnesses { @@ -563,6 +622,12 @@ impl From for Transaction { } } +impl From for Transaction { + fn from(tx: Blob) -> Self { + Self::Blob(tx) + } +} + impl Serialize for Transaction { fn size_static(&self) -> usize { match self { @@ -571,6 +636,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.size_static(), Self::Upgrade(tx) => tx.size_static(), Self::Upload(tx) => tx.size_static(), + Self::Blob(tx) => tx.size_static(), } } @@ -581,6 +647,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.size_dynamic(), Self::Upgrade(tx) => tx.size_dynamic(), Self::Upload(tx) => tx.size_dynamic(), + Self::Blob(tx) => tx.size_dynamic(), } } @@ -594,6 +661,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.encode_static(buffer), Self::Upgrade(tx) => tx.encode_static(buffer), Self::Upload(tx) => tx.encode_static(buffer), + Self::Blob(tx) => tx.encode_static(buffer), } } @@ -607,6 +675,7 @@ impl Serialize for Transaction { Self::Mint(tx) => tx.encode_dynamic(buffer), Self::Upgrade(tx) => tx.encode_dynamic(buffer), Self::Upload(tx) => tx.encode_dynamic(buffer), + Self::Blob(tx) => tx.encode_dynamic(buffer), } } } @@ -637,6 +706,9 @@ impl Deserialize for Transaction { TransactionRepr::Upload => { Ok(::decode_static(buffer)?.into()) } + TransactionRepr::Blob => { + Ok(::decode_static(buffer)?.into()) + } } } @@ -650,6 +722,7 @@ impl Deserialize for Transaction { Self::Mint(tx) => tx.decode_dynamic(buffer), Self::Upgrade(tx) => tx.decode_dynamic(buffer), Self::Upload(tx) => tx.decode_dynamic(buffer), + Self::Blob(tx) => tx.decode_dynamic(buffer), } } } @@ -954,6 +1027,16 @@ pub mod field { fn bytecode_root_offset_static() -> usize; } + pub trait BlobId { + fn blob_id(&self) -> &fuel_types::BlobId; + fn blob_id_mut(&mut self) -> &mut fuel_types::BlobId; + fn blob_id_offset(&self) -> usize { + Self::blob_id_offset_static() + } + + fn blob_id_offset_static() -> usize; + } + pub trait SubsectionIndex { fn subsection_index(&self) -> &u16; fn subsection_index_mut(&mut self) -> &mut u16; diff --git a/fuel-tx/src/transaction/consensus_parameters.rs b/fuel-tx/src/transaction/consensus_parameters.rs index 6edc8bfb6f..391d0ee575 100644 --- a/fuel-tx/src/transaction/consensus_parameters.rs +++ b/fuel-tx/src/transaction/consensus_parameters.rs @@ -10,6 +10,7 @@ pub mod gas; pub use gas::{ DependentCost, + GasCostNotDefined, GasCosts, GasCostsValues, }; diff --git a/fuel-tx/src/transaction/consensus_parameters/gas.rs b/fuel-tx/src/transaction/consensus_parameters/gas.rs index 8e26bdae63..7cee63dd7a 100644 --- a/fuel-tx/src/transaction/consensus_parameters/gas.rs +++ b/fuel-tx/src/transaction/consensus_parameters/gas.rs @@ -5,6 +5,7 @@ use core::ops::Deref; #[cfg(feature = "alloc")] use alloc::sync::Arc; +use fuel_asm::PanicReason; use fuel_types::Word; /// Default gas costs are generated from the @@ -77,6 +78,17 @@ pub enum GasCostsValues { V2(GasCostsValuesV2), /// Version 3 of the gas costs. V3(GasCostsValuesV3), + /// Version 4 of the gas costs. + V4(GasCostsValuesV4), +} + +/// Gas cost for this instruction is not defined for this version. +pub struct GasCostNotDefined; + +impl From for PanicReason { + fn from(_: GasCostNotDefined) -> PanicReason { + PanicReason::GasCostNotDefined + } } #[allow(missing_docs)] @@ -86,6 +98,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.add, GasCostsValues::V2(v2) => v2.add, GasCostsValues::V3(v3) => v3.add, + GasCostsValues::V4(v4) => v4.add, } } @@ -94,6 +107,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.addi, GasCostsValues::V2(v2) => v2.addi, GasCostsValues::V3(v3) => v3.addi, + GasCostsValues::V4(v4) => v4.addi, } } @@ -102,6 +116,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.and, GasCostsValues::V2(v2) => v2.and, GasCostsValues::V3(v3) => v3.and, + GasCostsValues::V4(v4) => v4.and, } } @@ -110,6 +125,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.andi, GasCostsValues::V2(v2) => v2.andi, GasCostsValues::V3(v3) => v3.andi, + GasCostsValues::V4(v4) => v4.andi, } } @@ -118,6 +134,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.bal, GasCostsValues::V2(v2) => v2.bal, GasCostsValues::V3(v3) => v3.bal, + GasCostsValues::V4(v4) => v4.bal, } } @@ -126,6 +143,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.bhei, GasCostsValues::V2(v2) => v2.bhei, GasCostsValues::V3(v3) => v3.bhei, + GasCostsValues::V4(v4) => v4.bhei, } } @@ -134,6 +152,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.bhsh, GasCostsValues::V2(v2) => v2.bhsh, GasCostsValues::V3(v3) => v3.bhsh, + GasCostsValues::V4(v4) => v4.bhsh, } } @@ -142,6 +161,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.burn, GasCostsValues::V2(v2) => v2.burn, GasCostsValues::V3(v3) => v3.burn, + GasCostsValues::V4(v4) => v4.burn, } } @@ -150,6 +170,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.cb, GasCostsValues::V2(v2) => v2.cb, GasCostsValues::V3(v3) => v3.cb, + GasCostsValues::V4(v4) => v4.cb, } } @@ -158,6 +179,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.cfsi, GasCostsValues::V2(v2) => v2.cfsi, GasCostsValues::V3(v3) => v3.cfsi, + GasCostsValues::V4(v4) => v4.cfsi, } } @@ -166,6 +188,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.div, GasCostsValues::V2(v2) => v2.div, GasCostsValues::V3(v3) => v3.div, + GasCostsValues::V4(v4) => v4.div, } } @@ -174,6 +197,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.divi, GasCostsValues::V2(v2) => v2.divi, GasCostsValues::V3(v3) => v3.divi, + GasCostsValues::V4(v4) => v4.divi, } } @@ -182,6 +206,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.eck1, GasCostsValues::V2(v2) => v2.eck1, GasCostsValues::V3(v3) => v3.eck1, + GasCostsValues::V4(v4) => v4.eck1, } } @@ -190,6 +215,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ecr1, GasCostsValues::V2(v2) => v2.ecr1, GasCostsValues::V3(v3) => v3.ecr1, + GasCostsValues::V4(v4) => v4.ecr1, } } @@ -198,6 +224,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ed19, GasCostsValues::V2(v2) => v2.ed19, GasCostsValues::V3(v3) => v3.ed19, + GasCostsValues::V4(v4) => v4.ed19, } } @@ -206,6 +233,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.eq, GasCostsValues::V2(v2) => v2.eq, GasCostsValues::V3(v3) => v3.eq, + GasCostsValues::V4(v4) => v4.eq, } } @@ -214,6 +242,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.exp, GasCostsValues::V2(v2) => v2.exp, GasCostsValues::V3(v3) => v3.exp, + GasCostsValues::V4(v4) => v4.exp, } } @@ -222,6 +251,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.expi, GasCostsValues::V2(v2) => v2.expi, GasCostsValues::V3(v3) => v3.expi, + GasCostsValues::V4(v4) => v4.expi, } } @@ -230,6 +260,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.flag, GasCostsValues::V2(v2) => v2.flag, GasCostsValues::V3(v3) => v3.flag, + GasCostsValues::V4(v4) => v4.flag, } } @@ -238,6 +269,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.gm, GasCostsValues::V2(v2) => v2.gm, GasCostsValues::V3(v3) => v3.gm, + GasCostsValues::V4(v4) => v4.gm, } } @@ -246,6 +278,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.gt, GasCostsValues::V2(v2) => v2.gt, GasCostsValues::V3(v3) => v3.gt, + GasCostsValues::V4(v4) => v4.gt, } } @@ -254,6 +287,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.gtf, GasCostsValues::V2(v2) => v2.gtf, GasCostsValues::V3(v3) => v3.gtf, + GasCostsValues::V4(v4) => v4.gtf, } } @@ -262,6 +296,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ji, GasCostsValues::V2(v2) => v2.ji, GasCostsValues::V3(v3) => v3.ji, + GasCostsValues::V4(v4) => v4.ji, } } @@ -270,6 +305,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jmp, GasCostsValues::V2(v2) => v2.jmp, GasCostsValues::V3(v3) => v3.jmp, + GasCostsValues::V4(v4) => v4.jmp, } } @@ -278,6 +314,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jne, GasCostsValues::V2(v2) => v2.jne, GasCostsValues::V3(v3) => v3.jne, + GasCostsValues::V4(v4) => v4.jne, } } @@ -286,6 +323,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jnei, GasCostsValues::V2(v2) => v2.jnei, GasCostsValues::V3(v3) => v3.jnei, + GasCostsValues::V4(v4) => v4.jnei, } } @@ -294,6 +332,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jnzi, GasCostsValues::V2(v2) => v2.jnzi, GasCostsValues::V3(v3) => v3.jnzi, + GasCostsValues::V4(v4) => v4.jnzi, } } @@ -302,6 +341,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jmpf, GasCostsValues::V2(v2) => v2.jmpf, GasCostsValues::V3(v3) => v3.jmpf, + GasCostsValues::V4(v4) => v4.jmpf, } } @@ -310,6 +350,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jmpb, GasCostsValues::V2(v2) => v2.jmpb, GasCostsValues::V3(v3) => v3.jmpb, + GasCostsValues::V4(v4) => v4.jmpb, } } @@ -318,6 +359,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jnzf, GasCostsValues::V2(v2) => v2.jnzf, GasCostsValues::V3(v3) => v3.jnzf, + GasCostsValues::V4(v4) => v4.jnzf, } } @@ -326,6 +368,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jnzb, GasCostsValues::V2(v2) => v2.jnzb, GasCostsValues::V3(v3) => v3.jnzb, + GasCostsValues::V4(v4) => v4.jnzb, } } @@ -334,6 +377,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jnef, GasCostsValues::V2(v2) => v2.jnef, GasCostsValues::V3(v3) => v3.jnef, + GasCostsValues::V4(v4) => v4.jnef, } } @@ -342,6 +386,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.jneb, GasCostsValues::V2(v2) => v2.jneb, GasCostsValues::V3(v3) => v3.jneb, + GasCostsValues::V4(v4) => v4.jneb, } } @@ -350,6 +395,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.lb, GasCostsValues::V2(v2) => v2.lb, GasCostsValues::V3(v3) => v3.lb, + GasCostsValues::V4(v4) => v4.lb, } } @@ -358,6 +404,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.log, GasCostsValues::V2(v2) => v2.log, GasCostsValues::V3(v3) => v3.log, + GasCostsValues::V4(v4) => v4.log, } } @@ -366,6 +413,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.lt, GasCostsValues::V2(v2) => v2.lt, GasCostsValues::V3(v3) => v3.lt, + GasCostsValues::V4(v4) => v4.lt, } } @@ -374,6 +422,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.lw, GasCostsValues::V2(v2) => v2.lw, GasCostsValues::V3(v3) => v3.lw, + GasCostsValues::V4(v4) => v4.lw, } } @@ -382,6 +431,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mint, GasCostsValues::V2(v2) => v2.mint, GasCostsValues::V3(v3) => v3.mint, + GasCostsValues::V4(v4) => v4.mint, } } @@ -390,6 +440,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mlog, GasCostsValues::V2(v2) => v2.mlog, GasCostsValues::V3(v3) => v3.mlog, + GasCostsValues::V4(v4) => v4.mlog, } } @@ -398,6 +449,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mod_op, GasCostsValues::V2(v2) => v2.mod_op, GasCostsValues::V3(v3) => v3.mod_op, + GasCostsValues::V4(v4) => v4.mod_op, } } @@ -406,6 +458,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.modi, GasCostsValues::V2(v2) => v2.modi, GasCostsValues::V3(v3) => v3.modi, + GasCostsValues::V4(v4) => v4.modi, } } @@ -414,6 +467,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.move_op, GasCostsValues::V2(v2) => v2.move_op, GasCostsValues::V3(v3) => v3.move_op, + GasCostsValues::V4(v4) => v4.move_op, } } @@ -422,6 +476,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.movi, GasCostsValues::V2(v2) => v2.movi, GasCostsValues::V3(v3) => v3.movi, + GasCostsValues::V4(v4) => v4.movi, } } @@ -430,6 +485,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mroo, GasCostsValues::V2(v2) => v2.mroo, GasCostsValues::V3(v3) => v3.mroo, + GasCostsValues::V4(v4) => v4.mroo, } } @@ -438,6 +494,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mul, GasCostsValues::V2(v2) => v2.mul, GasCostsValues::V3(v3) => v3.mul, + GasCostsValues::V4(v4) => v4.mul, } } @@ -446,6 +503,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.muli, GasCostsValues::V2(v2) => v2.muli, GasCostsValues::V3(v3) => v3.muli, + GasCostsValues::V4(v4) => v4.muli, } } @@ -454,6 +512,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mldv, GasCostsValues::V2(v2) => v2.mldv, GasCostsValues::V3(v3) => v3.mldv, + GasCostsValues::V4(v4) => v4.mldv, } } @@ -462,6 +521,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.noop, GasCostsValues::V2(v2) => v2.noop, GasCostsValues::V3(v3) => v3.noop, + GasCostsValues::V4(v4) => v4.noop, } } @@ -470,6 +530,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.not, GasCostsValues::V2(v2) => v2.not, GasCostsValues::V3(v3) => v3.not, + GasCostsValues::V4(v4) => v4.not, } } @@ -478,6 +539,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.or, GasCostsValues::V2(v2) => v2.or, GasCostsValues::V3(v3) => v3.or, + GasCostsValues::V4(v4) => v4.or, } } @@ -486,6 +548,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ori, GasCostsValues::V2(v2) => v2.ori, GasCostsValues::V3(v3) => v3.ori, + GasCostsValues::V4(v4) => v4.ori, } } @@ -494,6 +557,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.poph, GasCostsValues::V2(v2) => v2.poph, GasCostsValues::V3(v3) => v3.poph, + GasCostsValues::V4(v4) => v4.poph, } } @@ -502,6 +566,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.popl, GasCostsValues::V2(v2) => v2.popl, GasCostsValues::V3(v3) => v3.popl, + GasCostsValues::V4(v4) => v4.popl, } } @@ -510,6 +575,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.pshh, GasCostsValues::V2(v2) => v2.pshh, GasCostsValues::V3(v3) => v3.pshh, + GasCostsValues::V4(v4) => v4.pshh, } } @@ -518,6 +584,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.pshl, GasCostsValues::V2(v2) => v2.pshl, GasCostsValues::V3(v3) => v3.pshl, + GasCostsValues::V4(v4) => v4.pshl, } } @@ -526,6 +593,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ret, GasCostsValues::V2(v2) => v2.ret, GasCostsValues::V3(v3) => v3.ret, + GasCostsValues::V4(v4) => v4.ret, } } @@ -534,6 +602,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.rvrt, GasCostsValues::V2(v2) => v2.rvrt, GasCostsValues::V3(v3) => v3.rvrt, + GasCostsValues::V4(v4) => v4.rvrt, } } @@ -542,6 +611,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.sb, GasCostsValues::V2(v2) => v2.sb, GasCostsValues::V3(v3) => v3.sb, + GasCostsValues::V4(v4) => v4.sb, } } @@ -550,6 +620,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.sll, GasCostsValues::V2(v2) => v2.sll, GasCostsValues::V3(v3) => v3.sll, + GasCostsValues::V4(v4) => v4.sll, } } @@ -558,6 +629,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.slli, GasCostsValues::V2(v2) => v2.slli, GasCostsValues::V3(v3) => v3.slli, + GasCostsValues::V4(v4) => v4.slli, } } @@ -566,6 +638,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.srl, GasCostsValues::V2(v2) => v2.srl, GasCostsValues::V3(v3) => v3.srl, + GasCostsValues::V4(v4) => v4.srl, } } @@ -574,6 +647,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.srli, GasCostsValues::V2(v2) => v2.srli, GasCostsValues::V3(v3) => v3.srli, + GasCostsValues::V4(v4) => v4.srli, } } @@ -582,6 +656,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.srw, GasCostsValues::V2(v2) => v2.srw, GasCostsValues::V3(v3) => v3.srw, + GasCostsValues::V4(v4) => v4.srw, } } @@ -590,6 +665,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.sub, GasCostsValues::V2(v2) => v2.sub, GasCostsValues::V3(v3) => v3.sub, + GasCostsValues::V4(v4) => v4.sub, } } @@ -598,6 +674,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.subi, GasCostsValues::V2(v2) => v2.subi, GasCostsValues::V3(v3) => v3.subi, + GasCostsValues::V4(v4) => v4.subi, } } @@ -606,6 +683,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.sw, GasCostsValues::V2(v2) => v2.sw, GasCostsValues::V3(v3) => v3.sw, + GasCostsValues::V4(v4) => v4.sw, } } @@ -614,6 +692,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.sww, GasCostsValues::V2(v2) => v2.sww, GasCostsValues::V3(v3) => v3.sww, + GasCostsValues::V4(v4) => v4.sww, } } @@ -622,6 +701,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.time, GasCostsValues::V2(v2) => v2.time, GasCostsValues::V3(v3) => v3.time, + GasCostsValues::V4(v4) => v4.time, } } @@ -630,6 +710,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.tr, GasCostsValues::V2(v2) => v2.tr, GasCostsValues::V3(v3) => v3.tr, + GasCostsValues::V4(v4) => v4.tr, } } @@ -638,6 +719,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.tro, GasCostsValues::V2(v2) => v2.tro, GasCostsValues::V3(v3) => v3.tro, + GasCostsValues::V4(v4) => v4.tro, } } @@ -646,6 +728,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdcm, GasCostsValues::V2(v2) => v2.wdcm, GasCostsValues::V3(v3) => v3.wdcm, + GasCostsValues::V4(v4) => v4.wdcm, } } @@ -654,6 +737,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqcm, GasCostsValues::V2(v2) => v2.wqcm, GasCostsValues::V3(v3) => v3.wqcm, + GasCostsValues::V4(v4) => v4.wqcm, } } @@ -662,6 +746,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdop, GasCostsValues::V2(v2) => v2.wdop, GasCostsValues::V3(v3) => v3.wdop, + GasCostsValues::V4(v4) => v4.wdop, } } @@ -670,6 +755,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqop, GasCostsValues::V2(v2) => v2.wqop, GasCostsValues::V3(v3) => v3.wqop, + GasCostsValues::V4(v4) => v4.wqop, } } @@ -678,6 +764,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdml, GasCostsValues::V2(v2) => v2.wdml, GasCostsValues::V3(v3) => v3.wdml, + GasCostsValues::V4(v4) => v4.wdml, } } @@ -686,6 +773,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqml, GasCostsValues::V2(v2) => v2.wqml, GasCostsValues::V3(v3) => v3.wqml, + GasCostsValues::V4(v4) => v4.wqml, } } @@ -694,6 +782,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wddv, GasCostsValues::V2(v2) => v2.wddv, GasCostsValues::V3(v3) => v3.wddv, + GasCostsValues::V4(v4) => v4.wddv, } } @@ -702,6 +791,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqdv, GasCostsValues::V2(v2) => v2.wqdv, GasCostsValues::V3(v3) => v3.wqdv, + GasCostsValues::V4(v4) => v4.wqdv, } } @@ -710,6 +800,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdmd, GasCostsValues::V2(v2) => v2.wdmd, GasCostsValues::V3(v3) => v3.wdmd, + GasCostsValues::V4(v4) => v4.wdmd, } } @@ -718,6 +809,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqmd, GasCostsValues::V2(v2) => v2.wqmd, GasCostsValues::V3(v3) => v3.wqmd, + GasCostsValues::V4(v4) => v4.wqmd, } } @@ -726,6 +818,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdam, GasCostsValues::V2(v2) => v2.wdam, GasCostsValues::V3(v3) => v3.wdam, + GasCostsValues::V4(v4) => v4.wdam, } } @@ -734,6 +827,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqam, GasCostsValues::V2(v2) => v2.wqam, GasCostsValues::V3(v3) => v3.wqam, + GasCostsValues::V4(v4) => v4.wqam, } } @@ -742,6 +836,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wdmm, GasCostsValues::V2(v2) => v2.wdmm, GasCostsValues::V3(v3) => v3.wdmm, + GasCostsValues::V4(v4) => v4.wdmm, } } @@ -750,6 +845,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.wqmm, GasCostsValues::V2(v2) => v2.wqmm, GasCostsValues::V3(v3) => v3.wqmm, + GasCostsValues::V4(v4) => v4.wqmm, } } @@ -758,6 +854,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.xor, GasCostsValues::V2(v2) => v2.xor, GasCostsValues::V3(v3) => v3.xor, + GasCostsValues::V4(v4) => v4.xor, } } @@ -766,6 +863,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.xori, GasCostsValues::V2(v2) => v2.xori, GasCostsValues::V3(v3) => v3.xori, + GasCostsValues::V4(v4) => v4.xori, } } @@ -777,6 +875,7 @@ impl GasCostsValues { }, GasCostsValues::V2(v2) => v2.aloc, GasCostsValues::V3(v3) => v3.aloc, + GasCostsValues::V4(v4) => v4.aloc, } } @@ -791,6 +890,7 @@ impl GasCostsValues { gas_per_unit: 0, }, GasCostsValues::V3(v3) => v3.cfe, + GasCostsValues::V4(v4) => v4.cfe, } } @@ -805,6 +905,7 @@ impl GasCostsValues { gas_per_unit: 0, }, GasCostsValues::V3(v3) => v3.cfei, + GasCostsValues::V4(v4) => v4.cfei, } } @@ -813,6 +914,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.call, GasCostsValues::V2(v2) => v2.call, GasCostsValues::V3(v3) => v3.call, + GasCostsValues::V4(v4) => v4.call, } } @@ -821,6 +923,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ccp, GasCostsValues::V2(v2) => v2.ccp, GasCostsValues::V3(v3) => v3.ccp, + GasCostsValues::V4(v4) => v4.ccp, } } @@ -829,6 +932,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.croo, GasCostsValues::V2(v2) => v2.croo, GasCostsValues::V3(v3) => v3.croo, + GasCostsValues::V4(v4) => v4.croo, } } @@ -837,6 +941,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.csiz, GasCostsValues::V2(v2) => v2.csiz, GasCostsValues::V3(v3) => v3.csiz, + GasCostsValues::V4(v4) => v4.csiz, } } @@ -845,6 +950,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.k256, GasCostsValues::V2(v2) => v2.k256, GasCostsValues::V3(v3) => v3.k256, + GasCostsValues::V4(v4) => v4.k256, } } @@ -853,6 +959,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.ldc, GasCostsValues::V2(v2) => v2.ldc, GasCostsValues::V3(v3) => v3.ldc, + GasCostsValues::V4(v4) => v4.ldc, } } @@ -861,6 +968,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.logd, GasCostsValues::V2(v2) => v2.logd, GasCostsValues::V3(v3) => v3.logd, + GasCostsValues::V4(v4) => v4.logd, } } @@ -869,6 +977,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mcl, GasCostsValues::V2(v2) => v2.mcl, GasCostsValues::V3(v3) => v3.mcl, + GasCostsValues::V4(v4) => v4.mcl, } } @@ -877,6 +986,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mcli, GasCostsValues::V2(v2) => v2.mcli, GasCostsValues::V3(v3) => v3.mcli, + GasCostsValues::V4(v4) => v4.mcli, } } @@ -885,6 +995,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mcp, GasCostsValues::V2(v2) => v2.mcp, GasCostsValues::V3(v3) => v3.mcp, + GasCostsValues::V4(v4) => v4.mcp, } } @@ -893,6 +1004,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.mcpi, GasCostsValues::V2(v2) => v2.mcpi, GasCostsValues::V3(v3) => v3.mcpi, + GasCostsValues::V4(v4) => v4.mcpi, } } @@ -901,6 +1013,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.meq, GasCostsValues::V2(v2) => v2.meq, GasCostsValues::V3(v3) => v3.meq, + GasCostsValues::V4(v4) => v4.meq, } } @@ -909,6 +1022,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.retd, GasCostsValues::V2(v2) => v2.retd, GasCostsValues::V3(v3) => v3.retd, + GasCostsValues::V4(v4) => v4.retd, } } @@ -917,6 +1031,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.s256, GasCostsValues::V2(v2) => v2.s256, GasCostsValues::V3(v3) => v3.s256, + GasCostsValues::V4(v4) => v4.s256, } } @@ -925,6 +1040,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.scwq, GasCostsValues::V2(v2) => v2.scwq, GasCostsValues::V3(v3) => v3.scwq, + GasCostsValues::V4(v4) => v4.scwq, } } @@ -933,6 +1049,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.smo, GasCostsValues::V2(v2) => v2.smo, GasCostsValues::V3(v3) => v3.smo, + GasCostsValues::V4(v4) => v4.smo, } } @@ -941,6 +1058,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.srwq, GasCostsValues::V2(v2) => v2.srwq, GasCostsValues::V3(v3) => v3.srwq, + GasCostsValues::V4(v4) => v4.srwq, } } @@ -949,6 +1067,25 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.swwq, GasCostsValues::V2(v2) => v2.swwq, GasCostsValues::V3(v3) => v3.swwq, + GasCostsValues::V4(v4) => v4.swwq, + } + } + + pub fn bsiz(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(v4) => Ok(v4.bsiz), + } + } + + pub fn bldd(&self) -> Result { + match self { + GasCostsValues::V1(_v1) => Err(GasCostNotDefined), + GasCostsValues::V2(_v2) => Err(GasCostNotDefined), + GasCostsValues::V3(_v3) => Err(GasCostNotDefined), + GasCostsValues::V4(v4) => Ok(v4.bldd), } } @@ -957,6 +1094,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.contract_root, GasCostsValues::V2(v2) => v2.contract_root, GasCostsValues::V3(v3) => v3.contract_root, + GasCostsValues::V4(v4) => v4.contract_root, } } @@ -965,6 +1103,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.state_root, GasCostsValues::V2(v2) => v2.state_root, GasCostsValues::V3(v3) => v3.state_root, + GasCostsValues::V4(v4) => v4.state_root, } } @@ -973,6 +1112,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.new_storage_per_byte, GasCostsValues::V2(v2) => v2.new_storage_per_byte, GasCostsValues::V3(v3) => v3.new_storage_per_byte, + GasCostsValues::V4(v4) => v4.new_storage_per_byte, } } @@ -981,6 +1121,7 @@ impl GasCostsValues { GasCostsValues::V1(v1) => v1.vm_initialization, GasCostsValues::V2(v2) => v2.vm_initialization, GasCostsValues::V3(v3) => v3.vm_initialization, + GasCostsValues::V4(v4) => v4.vm_initialization, } } } @@ -1369,6 +1510,137 @@ pub struct GasCostsValuesV3 { pub vm_initialization: DependentCost, } +/// Gas costs for every op. +/// The difference with [`GasCostsValuesV3`]: +/// - Added `bsiz`, `bldd` instructions +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] +#[serde(default = "GasCostsValuesV4::unit")] +pub struct GasCostsValuesV4 { + 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 ed19: 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, + #[cfg_attr(feature = "serde", serde(rename = "mod"))] + pub mod_op: Word, + pub modi: Word, + #[cfg_attr(feature = "serde", serde(rename = "move"))] + pub move_op: Word, + pub movi: Word, + pub mroo: Word, + pub mul: Word, + pub muli: Word, + pub mldv: 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, + #[cfg_attr(feature = "serde", serde(rename = "ret_contract"))] + pub ret: Word, + #[cfg_attr(feature = "serde", 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, + + // 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 k256: DependentCost, + pub ldc: DependentCost, + pub logd: DependentCost, + pub mcl: DependentCost, + pub mcli: DependentCost, + pub mcp: DependentCost, + pub mcpi: DependentCost, + pub meq: DependentCost, + #[cfg_attr(feature = "serde", serde(rename = "retd_contract"))] + pub retd: DependentCost, + pub s256: DependentCost, + pub scwq: DependentCost, + pub smo: DependentCost, + pub srwq: DependentCost, + pub swwq: 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, @@ -1413,12 +1685,12 @@ impl GasCosts { impl GasCostsValues { /// Create costs that are all set to zero. pub fn free() -> Self { - GasCostsValuesV3::free().into() + GasCostsValuesV4::free().into() } /// Create costs that are all set to one. pub fn unit() -> Self { - GasCostsValuesV3::unit().into() + GasCostsValuesV4::unit().into() } } @@ -2138,6 +2410,250 @@ impl GasCostsValuesV3 { } } +impl GasCostsValuesV4 { + /// 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, + ed19: 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, + 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, + 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(), + 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(), + + // 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, + ed19: 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, + 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, + 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(), + 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(), + + // 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 { @@ -2252,6 +2768,11 @@ impl From for GasCostsValues { GasCostsValues::V3(i) } } +impl From for GasCostsValues { + fn from(i: GasCostsValuesV4) -> Self { + GasCostsValues::V4(i) + } +} #[cfg(test)] mod tests { 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 dbd5dc9a61..6f1b1bb913 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 @@ -2,8 +2,9 @@ use super::*; /// File generated by fuel-core: benches/src/bin/collect.rs:440. With the following git /// hash pub const GIT: &str = "98341e564b75d1157e61d7d5f38612f6224a5b30"; +/// Modified manually afterwards in https://github.com/FuelLabs/fuel-vm/pull/780 pub fn default_gas_costs() -> GasCostsValues { - GasCostsValuesV3 { + GasCostsValuesV4 { add: 1, addi: 1, and: 1, @@ -174,6 +175,14 @@ pub fn default_gas_costs() -> GasCostsValues { base: 44, units_per_gas: 5, }, + bsiz: DependentCost::LightOperation { + base: 17, + units_per_gas: 790, + }, + bldd: DependentCost::LightOperation { + base: 15, + units_per_gas: 272, + }, // Non-opcode costs contract_root: DependentCost::LightOperation { diff --git a/fuel-tx/src/transaction/id.rs b/fuel-tx/src/transaction/id.rs index 92b21598b4..f570c15b0c 100644 --- a/fuel-tx/src/transaction/id.rs +++ b/fuel-tx/src/transaction/id.rs @@ -45,6 +45,7 @@ impl UniqueIdentifier for Transaction { Self::Mint(tx) => tx.id(chain_id), Self::Upgrade(tx) => tx.id(chain_id), Self::Upload(tx) => tx.id(chain_id), + Self::Blob(tx) => tx.id(chain_id), } } @@ -55,6 +56,7 @@ impl UniqueIdentifier for Transaction { Self::Mint(tx) => tx.cached_id(), Self::Upgrade(tx) => tx.cached_id(), Self::Upload(tx) => tx.cached_id(), + Self::Blob(tx) => tx.cached_id(), } } } diff --git a/fuel-tx/src/transaction/metadata.rs b/fuel-tx/src/transaction/metadata.rs index c73acfb221..b66dde9b6b 100644 --- a/fuel-tx/src/transaction/metadata.rs +++ b/fuel-tx/src/transaction/metadata.rs @@ -30,6 +30,7 @@ impl Cacheable for super::Transaction { Self::Mint(tx) => tx.is_computed(), Self::Upgrade(tx) => tx.is_computed(), Self::Upload(tx) => tx.is_computed(), + Self::Blob(tx) => tx.is_computed(), } } @@ -40,6 +41,7 @@ impl Cacheable for super::Transaction { Self::Mint(tx) => tx.precompute(chain_id), Self::Upgrade(tx) => tx.precompute(chain_id), Self::Upload(tx) => tx.precompute(chain_id), + Self::Blob(tx) => tx.precompute(chain_id), } } } diff --git a/fuel-tx/src/transaction/repr.rs b/fuel-tx/src/transaction/repr.rs index 9da0231e43..231f3498ef 100644 --- a/fuel-tx/src/transaction/repr.rs +++ b/fuel-tx/src/transaction/repr.rs @@ -10,6 +10,7 @@ pub enum TransactionRepr { Mint = 0x02, Upgrade = 0x03, Upload = 0x04, + Blob = 0x05, } impl From<&Transaction> for TransactionRepr { @@ -20,6 +21,7 @@ impl From<&Transaction> for TransactionRepr { Transaction::Mint { .. } => Self::Mint, Transaction::Upgrade { .. } => Self::Upgrade, Transaction::Upload { .. } => Self::Upload, + Transaction::Blob { .. } => Self::Blob, } } } diff --git a/fuel-tx/src/transaction/types.rs b/fuel-tx/src/transaction/types.rs index 1ef8b7b42a..cec7e8974b 100644 --- a/fuel-tx/src/transaction/types.rs +++ b/fuel-tx/src/transaction/types.rs @@ -1,3 +1,4 @@ +mod blob; mod chargeable_transaction; mod create; pub mod input; @@ -10,6 +11,12 @@ mod upload; mod utxo_id; mod witness; +pub use blob::{ + Blob, + BlobBody, + BlobIdExt, + BlobMetadata, +}; pub use chargeable_transaction::{ ChargeableMetadata, ChargeableTransaction, diff --git a/fuel-tx/src/transaction/types/blob.rs b/fuel-tx/src/transaction/types/blob.rs new file mode 100644 index 0000000000..9eb9210461 --- /dev/null +++ b/fuel-tx/src/transaction/types/blob.rs @@ -0,0 +1,243 @@ +use crate::{ + transaction::{ + fee::min_gas, + id::PrepareSign, + metadata::CommonMetadata, + types::chargeable_transaction::{ + ChargeableMetadata, + ChargeableTransaction, + UniqueFormatValidityChecks, + }, + Chargeable, + }, + ConsensusParameters, + FeeParameters, + GasCosts, + Input, + Output, + TransactionRepr, + ValidityError, +}; +use derivative::Derivative; +use fuel_types::{ + bytes::WORD_SIZE, + canonical::Serialize, + BlobId, + ChainId, + Word, +}; + +/// Adds method to `BlobId` to compute the it from blob data. +pub trait BlobIdExt { + /// Computes the `BlobId` from by hashing the given data. + fn compute(data: &[u8]) -> BlobId; +} + +impl BlobIdExt for BlobId { + fn compute(data: &[u8]) -> Self { + Self::new(*fuel_crypto::Hasher::hash(data)) + } +} + +pub type Blob = ChargeableTransaction; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] +pub struct BlobMetadata; + +/// The body of the [`Blob`] transaction. +#[derive(Clone, Default, Derivative)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(fuel_types::canonical::Deserialize, fuel_types::canonical::Serialize)] +#[canonical(prefix = TransactionRepr::Blob)] +#[derivative(Eq, PartialEq, Hash, Debug)] +pub struct BlobBody { + /// Hash of the bytecode. Used both as a unique identifier and to verify the + /// bytecode. + pub id: BlobId, + /// The witness index of the payload. + pub witness_index: u16, +} + +impl PrepareSign for BlobBody { + fn prepare_sign(&mut self) {} +} + +impl Chargeable for Blob { + fn min_gas(&self, gas_costs: &GasCosts, fee: &FeeParameters) -> fuel_asm::Word { + min_gas(self, gas_costs, fee) + } + + #[inline(always)] + fn metered_bytes_size(&self) -> usize { + Serialize::size(self) + } + + #[inline(always)] + fn gas_used_by_metadata(&self, gas_cost: &GasCosts) -> Word { + let bytes = Serialize::size(self); + let blob_len = self + .witnesses + .get(self.body.witness_index as usize) + .map(|c| c.as_ref().len()) + .unwrap_or(0); + + // Gas required to calculate the `tx_id` and `blob_id`. + gas_cost + .s256() + .resolve(bytes as u64) + .saturating_add(gas_cost.s256().resolve(blob_len as u64)) + } +} + +impl UniqueFormatValidityChecks for Blob { + fn check_unique_rules( + &self, + consensus_params: &ConsensusParameters, + ) -> Result<(), ValidityError> { + let index = self.body.witness_index as usize; + let witness = self + .witnesses + .get(index) + .ok_or(ValidityError::InputWitnessIndexBounds { index })?; + + // Verify that blob id is correct + if BlobId::compute(witness.as_ref()) != self.body.id { + return Err(ValidityError::TransactionBlobIdVerificationFailed); + } + + self.inputs + .iter() + .enumerate() + .try_for_each(|(index, input)| { + if let Some(asset_id) = input.asset_id(consensus_params.base_asset_id()) { + if asset_id != consensus_params.base_asset_id() { + return Err( + ValidityError::TransactionInputContainsNonBaseAssetId { + index, + }, + ); + } + } + + match input { + Input::Contract(_) => { + Err(ValidityError::TransactionInputContainsContract { index }) + } + Input::MessageDataSigned(_) | Input::MessageDataPredicate(_) => { + Err(ValidityError::TransactionInputContainsMessageData { index }) + } + _ => Ok(()), + } + })?; + + self.outputs + .iter() + .enumerate() + .try_for_each(|(index, output)| match output { + Output::Contract(_) => { + Err(ValidityError::TransactionOutputContainsContract { index }) + } + + Output::Variable { .. } => { + Err(ValidityError::TransactionOutputContainsVariable { index }) + } + + Output::Change { asset_id, .. } => { + if asset_id != consensus_params.base_asset_id() { + Err(ValidityError::TransactionChangeChangeUsesNotBaseAsset { + index, + }) + } else { + Ok(()) + } + } + + Output::ContractCreated { .. } => { + Err(ValidityError::TransactionOutputContainsContractCreated { index }) + } + + Output::Coin { .. } => Ok(()), + })?; + + Ok(()) + } +} + +impl crate::Cacheable for Blob { + fn is_computed(&self) -> bool { + self.metadata.is_some() + } + + fn precompute(&mut self, chain_id: &ChainId) -> Result<(), ValidityError> { + self.metadata = None; + self.metadata = Some(ChargeableMetadata { + common: CommonMetadata::compute(self, chain_id)?, + body: BlobMetadata {}, + }); + Ok(()) + } +} + +mod field { + use super::*; + use crate::field::{ + self, + BlobId as BlobIdField, + BytecodeWitnessIndex, + }; + + impl field::BlobId for Blob { + #[inline(always)] + fn blob_id(&self) -> &BlobId { + &self.body.id + } + + #[inline(always)] + fn blob_id_mut(&mut self) -> &mut BlobId { + &mut self.body.id + } + + #[inline(always)] + fn blob_id_offset_static() -> usize { + WORD_SIZE // `Transaction` enum discriminant + } + } + + impl field::BytecodeWitnessIndex for Blob { + #[inline(always)] + fn bytecode_witness_index(&self) -> &u16 { + &self.body.witness_index + } + + #[inline(always)] + fn bytecode_witness_index_mut(&mut self) -> &mut u16 { + &mut self.body.witness_index + } + + #[inline(always)] + fn bytecode_witness_index_offset_static() -> usize { + Self::blob_id_offset_static().saturating_add(BlobId::LEN) + } + } + + impl field::ChargeableBody for Blob { + fn body(&self) -> &BlobBody { + &self.body + } + + fn body_mut(&mut self) -> &mut BlobBody { + &mut self.body + } + + #[allow(clippy::arithmetic_side_effects)] // Statically known to be ok + fn body_offset_end(&self) -> usize { + Self::bytecode_witness_index_offset_static().saturating_add( + WORD_SIZE // witness_index + + WORD_SIZE // Policies size + + WORD_SIZE // Inputs size + + WORD_SIZE // Outputs size + + WORD_SIZE, // Witnesses size + ) + } + } +} diff --git a/fuel-tx/src/transaction/validity.rs b/fuel-tx/src/transaction/validity.rs index 98bbfd7a3b..6fabb318c1 100644 --- a/fuel-tx/src/transaction/validity.rs +++ b/fuel-tx/src/transaction/validity.rs @@ -274,6 +274,7 @@ impl FormatValidityChecks for Transaction { Self::Mint(tx) => tx.check_signatures(chain_id), Self::Upgrade(tx) => tx.check_signatures(chain_id), Self::Upload(tx) => tx.check_signatures(chain_id), + Self::Blob(tx) => tx.check_signatures(chain_id), } } @@ -296,6 +297,7 @@ impl FormatValidityChecks for Transaction { Self::Upload(tx) => { tx.check_without_signatures(block_height, consensus_params) } + Self::Blob(tx) => tx.check_without_signatures(block_height, consensus_params), } } } diff --git a/fuel-tx/src/transaction/validity/error.rs b/fuel-tx/src/transaction/validity/error.rs index e20440c8c1..cfda83321c 100644 --- a/fuel-tx/src/transaction/validity/error.rs +++ b/fuel-tx/src/transaction/validity/error.rs @@ -176,4 +176,6 @@ pub enum ValidityError { }, /// The `Create` transaction doesn't contain `Output::ContractCreated`. TransactionOutputDoesntContainContractCreated, + /// Blob id of the transaction differs from the data. + TransactionBlobIdVerificationFailed, } diff --git a/fuel-types/src/array_types.rs b/fuel-types/src/array_types.rs index f62f0c2251..1766e21f92 100644 --- a/fuel-types/src/array_types.rs +++ b/fuel-types/src/array_types.rs @@ -344,6 +344,7 @@ macro_rules! key_methods { key!(Address, 32); key!(AssetId, 32); +key!(BlobId, 32); key!(ContractId, 32); key!(Bytes4, 4); key!(Bytes8, 8); diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index 0b39c442f5..36c33dc0c1 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -519,6 +519,7 @@ impl EstimatePredicates for Transaction { Self::Mint(_) => Ok(()), Self::Upgrade(tx) => tx.estimate_predicates(params, memory), Self::Upload(tx) => tx.estimate_predicates(params, memory), + Self::Blob(tx) => tx.estimate_predicates(params, memory), } } @@ -533,6 +534,7 @@ impl EstimatePredicates for Transaction { Self::Mint(_) => Ok(()), Self::Upgrade(tx) => tx.estimate_predicates_async::(params, pool).await, Self::Upload(tx) => tx.estimate_predicates_async::(params, pool).await, + Self::Blob(tx) => tx.estimate_predicates_async::(params, pool).await, } } } @@ -582,6 +584,9 @@ impl CheckPredicates for Checked { CheckedTransaction::Upload(tx) => { CheckPredicates::check_predicates(tx, params, memory)?.into() } + CheckedTransaction::Blob(tx) => { + CheckPredicates::check_predicates(tx, params, memory)?.into() + } }; Ok(checked_transaction.into()) } @@ -622,6 +627,11 @@ impl CheckPredicates for Checked { .await? .into() } + CheckedTransaction::Blob(tx) => { + CheckPredicates::check_predicates_async::(tx, params, pool) + .await? + .into() + } }; Ok(checked_transaction.into()) @@ -641,6 +651,7 @@ pub enum CheckedTransaction { Mint(Checked), Upgrade(Checked), Upload(Checked), + Blob(Checked), } impl From> for CheckedTransaction { @@ -668,6 +679,9 @@ impl From> for CheckedTransaction { (Transaction::Upload(transaction), CheckedMetadata::Upload(metadata)) => { Self::Upload(Checked::new(transaction, metadata, checks_bitmask)) } + (Transaction::Blob(transaction), CheckedMetadata::Blob(metadata)) => { + Self::Blob(Checked::new(transaction, metadata, checks_bitmask)) + } // The code should produce the `CheckedMetadata` for the corresponding // transaction variant. It is done in the implementation of the // `IntoChecked` trait for `Transaction`. With the current @@ -677,6 +691,7 @@ impl From> for CheckedTransaction { (Transaction::Mint(_), _) => unreachable!(), (Transaction::Upgrade(_), _) => unreachable!(), (Transaction::Upload(_), _) => unreachable!(), + (Transaction::Blob(_), _) => unreachable!(), } } } @@ -711,6 +726,12 @@ impl From> for CheckedTransaction { } } +impl From> for CheckedTransaction { + fn from(checked: Checked) -> Self { + Self::Blob(checked) + } +} + impl From for Checked { fn from(checked: CheckedTransaction) -> Self { match checked { @@ -739,6 +760,11 @@ impl From for Checked { metadata, checks_bitmask, }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask), + CheckedTransaction::Blob(Checked { + transaction, + metadata, + checks_bitmask, + }) => Checked::new(transaction.into(), metadata.into(), checks_bitmask), } } } @@ -752,6 +778,7 @@ pub enum CheckedMetadata { Mint(::Metadata), Upgrade(::Metadata), Upload(::Metadata), + Blob(::Metadata), } impl From<