diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 117082d296..111f0c8410 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ concurrency: env: CARGO_TERM_COLOR: always RUST_VERSION: 1.73.0 - NIGHTLY_RUST_VERSION: nightly-2023-08-28 + NIGHTLY_RUST_VERSION: nightly-2023-10-29 jobs: check-changelog: diff --git a/CHANGELOG.md b/CHANGELOG.md index 14649e9d12..4a85970744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Changed + +#### Breaking + +- [#623](https://github.com/FuelLabs/fuel-vm/pull/623): + Added support for transaction policies. The `Script` and `Create` + transactions received a new field, `policies`. Policies allow the addition + of some limits to the transaction to protect the user or specify some details regarding execution. + This change makes the `GasPrice` and `Maturity` fields optional, allowing to save space in the future. + Also, this will enable us to support multidimensional prices later. + `GasLimit` was renamed to `ScriptGasLimit`. + + Along with this change, we introduced two new policies: + - `WitnessLimit` - allows the limitation of the maximum size of witnesses in bytes for the contract. Because of the changes in the gas calculation model(the blockchain also charges the user for the witness data), the user should protect himself from the block producer or third parties blowing up witness data and draining the user's funds. + - `MaxFee` - allows the upper bound for the maximum fee that users agree to pay for the transaction. + + This change brings the following modification to the gas model: + - The `ScriptGasLimit` only limits script execution. Previously, the `ScriptGasLimit` also limited the predicate execution time, instead predicate gas is now directly included into `min_fee`. So, it is not possible to use the `ScriptGasLimit` for transaction cost limitations. A new `MaxFee` policy is a way to do that. The `GasLimit` field was removed from the `Create` transaction because it only relates to the script execution (which the `Create` transaction doesn't have). + - The blockchain charges the user for the size of witness data (before it was free). There is no separate price for the storage, so it uses gas to charge the user. This change affects `min_gas` and `min_fee` calculation. + - A new policy called `WitnessLimit` also impacts the `max_gas` and `max_fee` calculation in addition to `ScriptGasLimit`(in the case of `Create` transaction only `WitnessLimit` affects the `max_gas` and `max_fee`). + - The minimal gas also charges the user for transaction ID calculation. + + The change has the following modification to the transaction layout: + - The `Create` transaction doesn't have the `ScriptGasLimit` field anymore. Because the `Create` transaction doesn't have any script to execute + - The `Create` and `Script` transactions don't have explicit `maturity` and `gas_price` fields. Instead, these fields can be set via a new `policies` field. + - The `Create` and `Script` transactions have a new `policies` field with a unique canonical serialization and deserialization for optimal space consumption. + + Other breaking changes caused by the change: + - Each transaction requires setting the `GasPrice` policy. + - Previously, `ScriptGasLimit` should be less than the `MAX_GAS_PER_TX` constant. After removing this field from the `Create` transaction, it is impossible to require it. Instead, it requires that `max_gas <= MAX_GAS_PER_TX` for any transaction. Consequently, any `Script` transaction that uses `MAX_GAS_PER_TX` as a `ScriptGasLimit` will always fail because of a new rule. Setting the estimated gas usage instead solves the problem. + - If the `max_fee > policies.max_fee`, then transaction will be rejected. + - If the `witnessses_size > policies.witness_limit`, then transaction will be rejected. + - GTF opcode changed its hardcoded constants for fields. It should be updated according to the values from the specification on the Sway side. + ## [Version 0.41.0] #### Breaking diff --git a/Cargo.toml b/Cargo.toml index d8928202bc..21688a74eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,6 @@ fuel-storage = { version = "0.41.0", path = "fuel-storage", default-features = f fuel-tx = { version = "0.41.0", path = "fuel-tx", default-features = false } fuel-types = { version = "0.41.0", path = "fuel-types", default-features = false } fuel-vm = { version = "0.41.0", path = "fuel-vm", default-features = false } +bitflags = "2" bincode = { version = "1.3", default-features = false } criterion = "0.5.0" diff --git a/fuel-asm/Cargo.toml b/fuel-asm/Cargo.toml index 099eb20aa8..b4ab088384 100644 --- a/fuel-asm/Cargo.toml +++ b/fuel-asm/Cargo.toml @@ -12,7 +12,7 @@ description = "Atomic types of the FuelVM." [dependencies] arbitrary = { version = "1.1", features = ["derive"], optional = true } -bitflags = "1.3" +bitflags = { workspace = true } fuel-types = { workspace = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } strum = { version = "0.24", default-features = false, features = ["derive"] } diff --git a/fuel-asm/src/args.rs b/fuel-asm/src/args.rs index 91eb0ce629..035f6b0a3e 100644 --- a/fuel-asm/src/args.rs +++ b/fuel-asm/src/args.rs @@ -50,218 +50,218 @@ crate::enum_try_from! { /// Set `$rA` to `tx.type` Type = 0x001, - /// Set `$rA` to `tx.gasPrice` - ScriptGasPrice = 0x002, - - /// Set `$rA` to `tx.gasLimit` - ScriptGasLimit = 0x003, - - /// Set `$rA` to `tx.maturity` - ScriptMaturity = 0x004, + /// Set `$rA` to `tx.scriptGasLimit` + ScriptGasLimit = 0x002, /// Set `$rA` to `tx.scriptLength` - ScriptLength = 0x005, + ScriptLength = 0x003, /// Set `$rA` to `tx.scriptDataLength` - ScriptDataLength = 0x006, + ScriptDataLength = 0x004, /// Set `$rA` to `tx.inputsCount` - ScriptInputsCount = 0x007, + ScriptInputsCount = 0x005, /// Set `$rA` to `tx.outputsCount` - ScriptOutputsCount = 0x008, + ScriptOutputsCount = 0x006, /// Set `$rA` to `tx.witnessesCount` - ScriptWitnessesCound = 0x009, + ScriptWitnessesCount = 0x007, /// Set `$rA` to `Memory address of tx.receiptsRoot` - ScriptReceiptsRoot = 0x00A, + ScriptReceiptsRoot = 0x008, /// Set `$rA` to `Memory address of tx.script` - Script = 0x00B, + Script = 0x009, /// Set `$rA` to `Memory address of tx.scriptData` - ScriptData = 0x00C, + ScriptData = 0x00A, /// Set `$rA` to `Memory address of tx.inputs[$rB]` - ScriptInputAtIndex = 0x00D, + ScriptInputAtIndex = 0x00B, /// Set `$rA` to `Memory address of t.outputs[$rB]` - ScriptOutputAtIndex = 0x00E, + ScriptOutputAtIndex = 0x00C, /// Set `$rA` to `Memory address of tx.witnesses[$rB]` - ScriptWitnessAtIndex = 0x00F, - - /// Set `$rA` to `tx.gasPrice` - CreateGasPrice = 0x010, - - /// Set `$rA` to `tx.gasLimit` - CreateGasLimit = 0x011, - - /// Set `$rA` to `tx.maturity` - CreateMaturity = 0x012, + ScriptWitnessAtIndex = 0x00D, /// Set `$rA` to `tx.bytecodeLength` - CreateBytecodeLength = 0x013, + CreateBytecodeLength = 0x100, /// Set `$rA` to `tx.bytecodeWitnessIndex` - CreateBytecodeWitnessIndex = 0x014, + CreateBytecodeWitnessIndex = 0x101, /// Set `$rA` to `tx.storageSlotsCount` - CreateStorageSlotsCount = 0x015, + CreateStorageSlotsCount = 0x102, /// Set `$rA` to `tx.inputsCount` - CreateInputsCount = 0x016, + CreateInputsCount = 0x103, /// Set `$rA` to `tx.outputsCount` - CreateOutputsCount = 0x017, + CreateOutputsCount = 0x104, /// Set `$rA` to `tx.witnessesCount` - CreateWitnessesCount = 0x018, + CreateWitnessesCount = 0x105, /// Set `$rA` to `Memory address of tx.salt` - CreateSalt = 0x019, + CreateSalt = 0x106, /// Set `$rA` to `Memory address of tx.storageSlots[$rB]` - CreateStorageSlotAtIndex = 0x01A, + CreateStorageSlotAtIndex = 0x107, /// Set `$rA` to `Memory address of tx.inputs[$rB]` - CreateInputAtIndex = 0x01B, + CreateInputAtIndex = 0x108, /// Set `$rA` to `Memory address of t.outputs[$rB]` - CreateOutputAtIndex = 0x01C, + CreateOutputAtIndex = 0x109, /// Set `$rA` to `Memory address of tx.witnesses[$rB]` - CreateWitnessAtIndex = 0x01D, + CreateWitnessAtIndex = 0x10A, /// Set `$rA` to `tx.inputs[$rB].type` - InputType = 0x101, + InputType = 0x200, /// Set `$rA` to `Memory address of tx.inputs[$rB].txID` - InputCoinTxId = 0x102, + InputCoinTxId = 0x201, /// Set `$rA` to `tx.inputs[$rB].outputIndex` - InputCoinOutputIndex = 0x103, + InputCoinOutputIndex = 0x202, /// Set `$rA` to `Memory address of tx.inputs[$rB].owner` - InputCoinOwner = 0x104, + InputCoinOwner = 0x203, /// Set `$rA` to `tx.inputs[$rB].amount` - InputCoinAmount = 0x105, + InputCoinAmount = 0x204, /// Set `$rA` to `Memory address of tx.inputs[$rB].asset_id` - InputCoinAssetId = 0x106, + InputCoinAssetId = 0x205, /// Set `$rA` to `Memory address of tx.inputs[$rB].txPointer` - InputCoinTxPointer = 0x107, + InputCoinTxPointer = 0x206, /// Set `$rA` to `tx.inputs[$rB].witnessIndex` - InputCoinWitnessIndex = 0x108, + InputCoinWitnessIndex = 0x207, /// Set `$rA` to `tx.inputs[$rB].maturity` - InputCoinMaturity = 0x109, + InputCoinMaturity = 0x208, /// Set `$rA` to `tx.inputs[$rB].predicateLength` - InputCoinPredicateLength = 0x10A, + InputCoinPredicateLength = 0x209, /// Set `$rA` to `tx.inputs[$rB].predicateDataLength` - InputCoinPredicateDataLength = 0x10B, + InputCoinPredicateDataLength = 0x20A, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicate` - InputCoinPredicate = 0x10C, + InputCoinPredicate = 0x20B, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicateData` - InputCoinPredicateData = 0x10D, + InputCoinPredicateData = 0x20C, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicateGasUsed` - InputCoinPredicateGasUsed = 0x10E, + InputCoinPredicateGasUsed = 0x20D, /// Set `$rA` to `Memory address of tx.inputs[$rB].txID` - InputContractTxId = 0x10F, + InputContractTxId = 0x220, /// Set `$rA` to `tx.inputs[$rB].outputIndex` - InputContractOutputIndex = 0x110, + InputContractOutputIndex = 0x221, /// Set `$rA` to `Memory address of tx.inputs[$rB].balanceRoot` - InputContractBalanceRoot = 0x111, + InputContractBalanceRoot = 0x222, /// Set `$rA` to `Memory address of tx.inputs[$rB].stateRoot` - InputContractStateRoot = 0x112, + InputContractStateRoot = 0x223, /// Set `$rA` to `Memory address of tx.inputs[$rB].txPointer` - InputContractTxPointer = 0x113, + InputContractTxPointer = 0x224, /// Set `$rA` to `Memory address of tx.inputs[$rB].contractID` - InputContractId = 0x114, + InputContractId = 0x225, /// Set `$rA` to `Memory address of tx.inputs[$rB].sender` - InputMessageSender = 0x115, + InputMessageSender = 0x240, /// Set `$rA` to `Memory address of tx.inputs[$rB].recipient` - InputMessageRecipient = 0x116, + InputMessageRecipient = 0x241, /// Set `$rA` to `tx.inputs[$rB].amount` - InputMessageAmount = 0x117, + InputMessageAmount = 0x242, /// Set `$rA` to `Memory address of tx.inputs[$rB].nonce` - InputMessageNonce = 0x118, + InputMessageNonce = 0x243, /// Set `$rA` to `tx.inputs[$rB].witnessIndex` - InputMessageWitnessIndex = 0x119, + InputMessageWitnessIndex = 0x244, /// Set `$rA` to `tx.inputs[$rB].dataLength` - InputMessageDataLength = 0x11A, + InputMessageDataLength = 0x245, /// Set `$rA` to `tx.inputs[$rB].predicateLength` - InputMessagePredicateLength = 0x11B, + InputMessagePredicateLength = 0x246, /// Set `$rA` to `tx.inputs[$rB].predicateDataLength` - InputMessagePredicateDataLength = 0x11C, + InputMessagePredicateDataLength = 0x247, /// Set `$rA` to `Memory address of tx.inputs[$rB].data` - InputMessageData = 0x11D, + InputMessageData = 0x248, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicate` - InputMessagePredicate = 0x11E, + InputMessagePredicate = 0x249, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicateData` - InputMessagePredicateData = 0x11F, + InputMessagePredicateData = 0x24A, /// Set `$rA` to `Memory address of tx.inputs[$rB].predicateGasUsed` - InputMessagePredicateGasUsed = 0x120, + InputMessagePredicateGasUsed = 0x24B, /// Set `$rA` to `tx.outputs[$rB].type` - OutputType = 0x201, + OutputType = 0x300, /// Set `$rA` to `Memory address of tx.outputs[$rB].to` - OutputCoinTo = 0x202, + OutputCoinTo = 0x301, /// Set `$rA` to `tx.outputs[$rB].amount` - OutputCoinAmount = 0x203, + OutputCoinAmount = 0x302, /// Set `$rA` to `Memory address of tx.outputs[$rB].asset_id` - OutputCoinAssetId = 0x204, + OutputCoinAssetId = 0x303, /// Set `$rA` to `tx.outputs[$rB].inputIndex` - OutputContractInputIndex = 0x205, + OutputContractInputIndex = 0x304, /// Set `$rA` to `Memory address of tx.outputs[$rB].balanceRoot` - OutputContractBalanceRoot = 0x206, + OutputContractBalanceRoot = 0x305, /// Set `$rA` to `Memory address of tx.outputs[$rB].stateRoot` - OutputContractStateRoot = 0x207, + OutputContractStateRoot = 0x306, /// Set `$rA` to `Memory address of tx.outputs[$rB].contractID` - OutputContractCreatedContractId = 0x208, + OutputContractCreatedContractId = 0x307, /// Set `$rA` to `Memory address of tx.outputs[$rB].stateRoot` - OutputContractCreatedStateRoot = 0x209, + OutputContractCreatedStateRoot = 0x308, /// Set `$rA` to `tx.witnesses[$rB].dataLength` - WitnessDataLength = 0x301, + WitnessDataLength = 0x400, /// Set `$rA` to `Memory address of tx.witnesses[$rB].data` - WitnessData = 0x302, + WitnessData = 0x401, + + /// Set `$rA` to `tx.policyTypes` + PolicyTypes = 0x500, + + /// Set `$rA` to `tx.policies[0x00].gasPrice` + PolicyGasPrice = 0x501, + + /// Set `$rA` to `tx.policies[count_ones(0b11 & tx.policyTypes) - 1].witnessLimit` + PolicyWitnessLimit = 0x502, + + /// Set `$rA` to `tx.policies[count_ones(0b111 & tx.policyTypes) - 1].maturity` + PolicyMaturity = 0x503, + + /// Set `$rA` to `tx.policies[count_ones(0b1111 & tx.policyTypes) - 1].maxFee` + PolicyMaxFee = 0x504, }, Immediate12 } @@ -295,23 +295,18 @@ fn encode_gm_args() { fn encode_gtf_args() { let args = vec![ GTFArgs::Type, - GTFArgs::ScriptGasPrice, GTFArgs::ScriptGasLimit, - GTFArgs::ScriptMaturity, GTFArgs::ScriptLength, GTFArgs::ScriptDataLength, GTFArgs::ScriptInputsCount, GTFArgs::ScriptOutputsCount, - GTFArgs::ScriptWitnessesCound, + GTFArgs::ScriptWitnessesCount, GTFArgs::ScriptReceiptsRoot, GTFArgs::Script, GTFArgs::ScriptData, GTFArgs::ScriptInputAtIndex, GTFArgs::ScriptOutputAtIndex, GTFArgs::ScriptWitnessAtIndex, - GTFArgs::CreateGasPrice, - GTFArgs::CreateGasLimit, - GTFArgs::CreateMaturity, GTFArgs::CreateBytecodeLength, GTFArgs::CreateBytecodeWitnessIndex, GTFArgs::CreateStorageSlotsCount, @@ -366,6 +361,11 @@ fn encode_gtf_args() { GTFArgs::OutputContractCreatedStateRoot, GTFArgs::WitnessDataLength, GTFArgs::WitnessData, + GTFArgs::PolicyTypes, + GTFArgs::PolicyGasPrice, + GTFArgs::PolicyWitnessLimit, + GTFArgs::PolicyMaturity, + GTFArgs::PolicyMaxFee, ]; args.into_iter().for_each(|a| { diff --git a/fuel-asm/src/panic_reason.rs b/fuel-asm/src/panic_reason.rs index 3298424930..13ac220725 100644 --- a/fuel-asm/src/panic_reason.rs +++ b/fuel-asm/src/panic_reason.rs @@ -115,6 +115,10 @@ enum_from! { InvalidInstruction = 0x26, /// Memory outside $is..$ssp range is not executable MemoryNotExecutable = 0x27, + /// The policy is not set. + PolicyIsNotSet = 0x28, + /// The policy is not found across policies. + PolicyNotFound = 0x29, } } diff --git a/fuel-tx/Cargo.toml b/fuel-tx/Cargo.toml index 1ff1183df4..75604fd5d6 100644 --- a/fuel-tx/Cargo.toml +++ b/fuel-tx/Cargo.toml @@ -11,6 +11,7 @@ repository = { workspace = true } description = "FuelVM transaction." [dependencies] +bitflags = { workspace = true } derivative = { version = "2.2.0", default-features = false, features = ["use_core"], optional = true } derive_more = { version = "0.99", default-features = false, features = ["display"] } fuel-asm = { workspace = true, default-features = false } @@ -19,7 +20,6 @@ fuel-merkle = { workspace = true, default-features = false, optional = true } fuel-types = { workspace = true, default-features = false } hashbrown = { version = "0.14", optional = true } itertools = { version = "0.10", default-features = false, optional = true } -num-integer = { version = "0.1", default-features = false, optional = true } rand = { version = "0.8", default-features = false, features = ["std_rng"], optional = true } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } @@ -45,6 +45,6 @@ builder = ["alloc", "internals"] internals = [] random = ["fuel-crypto/random", "fuel-types/random", "rand"] std = ["alloc", "fuel-asm/std", "fuel-crypto/std", "fuel-merkle/std", "fuel-types/std", "itertools/default", "rand?/default", "serde?/default", "hex/std"] -alloc = ["hashbrown", "fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", "num-integer", "strum", "strum_macros"] +alloc = ["hashbrown", "fuel-types/alloc", "itertools/use_alloc", "derivative", "fuel-merkle", "strum", "strum_macros"] # serde is requiring alloc because its mandatory for serde_json. to avoid adding a new feature only for serde_json, we just require `alloc` here since as of the moment we don't have a use case of serde without alloc. -serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "fuel-merkle/serde", "serde_json", "hashbrown/serde"] +serde = ["alloc", "dep:serde", "fuel-asm/serde", "fuel-crypto/serde", "fuel-types/serde", "fuel-merkle/serde", "serde_json", "hashbrown/serde", "bitflags/serde"] diff --git a/fuel-tx/src/builder.rs b/fuel-tx/src/builder.rs index 8819fccf94..f583c8019a 100644 --- a/fuel-tx/src/builder.rs +++ b/fuel-tx/src/builder.rs @@ -6,6 +6,8 @@ use crate::{ field::{ BytecodeLength, BytecodeWitnessIndex, + GasPrice, + Maturity, Witnesses, }, Chargeable, @@ -34,6 +36,17 @@ use crate::{ Signable, }; +use crate::{ + field::{ + MaxFeeLimit, + WitnessLimit, + }, + policies::Policies, +}; +use alloc::{ + collections::BTreeMap, + vec::Vec, +}; use fuel_crypto::SecretKey; use fuel_types::{ AssetId, @@ -44,21 +57,9 @@ use fuel_types::{ Word, }; -use alloc::{ - collections::BTreeMap, - vec::Vec, -}; - pub trait BuildableAloc where - Self: Default - + Clone - + Executable - + Chargeable - + field::GasLimit - + field::GasPrice - + field::Maturity - + Into, + Self: Default + Clone + Executable + Chargeable + field::Policies + Into, { } @@ -80,31 +81,17 @@ where self.witnesses_mut().push(witness); } - /// Set the gas price - fn set_gas_price(&mut self, price: Word) { - *self.gas_price_mut() = price; - } - - /// Set the gas limit - fn set_gas_limit(&mut self, limit: Word) { - *self.gas_limit_mut() = limit; - } - - /// Set the maturity - fn set_maturity(&mut self, maturity: BlockHeight) { - *self.maturity_mut() = maturity; + /// Set the `Script`'s gas limit + fn set_script_gas_limit(&mut self, limit: Word) + where + Self: field::ScriptGasLimit, + { + *self.script_gas_limit_mut() = limit; } } impl BuildableAloc for T where - Self: Default - + Clone - + Executable - + Chargeable - + field::GasLimit - + field::GasPrice - + field::Maturity - + Into + Self: Default + Clone + Executable + Chargeable + field::Policies + Into { } @@ -129,11 +116,10 @@ pub struct TransactionBuilder { impl TransactionBuilder