Skip to content

Commit

Permalink
Add gas factor to gas cost and refund calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
vlopes11 committed Jun 13, 2022
1 parent 52cbe36 commit 72dbdcf
Show file tree
Hide file tree
Showing 20 changed files with 236 additions and 177 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fuel-asm = "0.5"
fuel-crypto = "0.5"
fuel-merkle = "0.1"
fuel-storage = "0.1"
fuel-tx = "0.12"
fuel-tx = "0.13"
fuel-types = "0.5"
itertools = "0.10"
secp256k1 = { version = "0.20", features = ["recovery"] }
Expand All @@ -27,7 +27,7 @@ tracing = "0.1"
rand = { version = "0.8", optional = true }

[dev-dependencies]
fuel-tx = { version = "0.12", features = ["random"] }
fuel-tx = { version = "0.13", features = ["random"] }
fuel-vm = { path = ".", default-features = false, features = ["test-helpers"] }
serde_json = "1.0"

Expand Down
10 changes: 1 addition & 9 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! VM parameters
use fuel_tx::default_parameters::*;
use fuel_types::{AssetId, Bytes32, Word};
use fuel_types::Word;

use std::mem;

Expand Down Expand Up @@ -83,13 +82,6 @@ pub const MEM_MAX_ACCESS_SIZE: u64 = VM_MAX_RAM;
/// Encoded len of a register id in an instruction (unused)
pub const VM_REGISTER_WIDTH: u8 = 6;

/// Transaction offset in the VM memory
pub const VM_TX_MEMORY: usize = Bytes32::LEN // Tx ID
+ WORD_SIZE // Tx size
+ MAX_INPUTS as usize * (
AssetId::LEN + WORD_SIZE
); // Asset ID/Balance coin input pairs

/// Empty merkle root for receipts tree
pub const EMPTY_RECEIPTS_MERKLE_ROOT: [u8; 32] = [
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41,
Expand Down
12 changes: 11 additions & 1 deletion src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct Interpreter<S> {
profiler: Profiler,
// track the offset for each unused balance in memory
unused_balance_index: HashMap<AssetId, usize>,
consensus_parameters: ConsensusParameters,
params: ConsensusParameters,
}

impl<S> Interpreter<S> {
Expand Down Expand Up @@ -94,6 +94,16 @@ impl<S> Interpreter<S> {
&self.tx
}

/// Consensus parameters
pub const fn params(&self) -> &ConsensusParameters {
&self.params
}

/// Replace the consensus parameters
pub fn set_params(&mut self, params: ConsensusParameters) {
self.params = params;
}

/// Receipts generated by a transaction execution.
pub fn receipts(&self) -> &[Receipt] {
self.receipts.as_slice()
Expand Down
3 changes: 1 addition & 2 deletions src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::error::RuntimeError;
use crate::storage::InterpreterStorage;

use fuel_asm::PanicReason;
use fuel_tx::default_parameters::*;
use fuel_tx::Input;
use fuel_types::{bytes, Address, AssetId, Bytes32, Bytes8, ContractId, RegisterId, Word};

Expand Down Expand Up @@ -40,7 +39,7 @@ where
// Validate arguments
if ssp + length_to_copy_unpadded > hp
|| (id_addr + ContractId::LEN) as u64 > VM_MAX_RAM
|| length_to_copy_unpadded > CONTRACT_MAX_SIZE.min(MEM_MAX_ACCESS_SIZE)
|| length_to_copy_unpadded > self.params.contract_max_size.min(MEM_MAX_ACCESS_SIZE)
{
return Err(PanicReason::MemoryOverflow.into());
}
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl<S> Interpreter<S> {
#[cfg(feature = "profile-any")]
profiler: Profiler::default(),
unused_balance_index: Default::default(),
consensus_parameters: params,
params: params,
}
}

Expand Down
26 changes: 14 additions & 12 deletions src/interpreter/executors/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl Interpreter<PredicateStorage> {
.inputs()
.iter()
.enumerate()
.filter_map(|(idx, _)| Self::input_to_predicate(&tx, idx))
.filter_map(|(idx, _)| vm.input_to_predicate(&tx, idx))
.collect();

predicates
Expand All @@ -52,7 +52,7 @@ impl Interpreter<PredicateStorage> {
/// For additional information, check [`Self::check_predicates`]
pub fn check_predicate(&mut self, tx: Transaction, idx: usize) -> bool {
tx.check_predicate_owner(idx)
.then(|| Self::input_to_predicate(&tx, idx))
.then(|| self.input_to_predicate(&tx, idx))
.flatten()
.map(|predicate| self.init_predicate(tx) && self._check_predicate(predicate))
.unwrap_or(false)
Expand All @@ -61,12 +61,12 @@ impl Interpreter<PredicateStorage> {
fn init_predicate(&mut self, tx: Transaction) -> bool {
let block_height = 0;

self.init(true, block_height, tx, self.consensus_parameters).is_ok()
self.init(true, block_height, tx).is_ok()
}

fn input_to_predicate(tx: &Transaction, idx: usize) -> Option<MemoryRange> {
fn input_to_predicate(&self, tx: &Transaction, idx: usize) -> Option<MemoryRange> {
tx.input_coin_predicate_offset(idx)
.map(|(ofs, len)| (ofs as Word + VM_TX_MEMORY as Word, len as Word))
.map(|(ofs, len)| (ofs as Word + self.tx_offset() as Word, len as Word))
.map(|(ofs, len)| MemoryRange::new(ofs, len))
}

Expand Down Expand Up @@ -130,7 +130,7 @@ where
// Verify predicates
// https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/tx_validity.md#predicate-verification
// TODO implement debug support
if !Interpreter::<PredicateStorage>::check_predicates(self.tx.clone(), self.consensus_parameters) {
if !Interpreter::<PredicateStorage>::check_predicates(self.tx.clone(), self.params) {
return Err(InterpreterError::PredicateFailure);
}

Expand All @@ -148,7 +148,7 @@ where
return Err(InterpreterError::Panic(PanicReason::ContractNotFound));
}

let offset = (VM_TX_MEMORY + Transaction::script_offset()) as Word;
let offset = (self.tx_offset() + Transaction::script_offset()) as Word;

self.registers[REG_PC] = offset;
self.registers[REG_IS] = offset;
Expand Down Expand Up @@ -211,9 +211,13 @@ where
self.tx.set_receipts_root(receipts_root);
}

// refund remaining global gas
let gas_refund = self.registers[REG_GGAS] * self.tx.gas_price();
// used bytes are non-refundable
let factor = self.params.gas_price_factor as f64;
let gas_refund = self.tx.gas_price().saturating_mul(self.registers[REG_GGAS]) as f64;
let gas_refund = (gas_refund / factor).floor() as Word;

let revert = matches!(state, ProgramState::Revert(_));

self.finalize_outputs(gas_refund, revert)?;

Ok(state)
Expand Down Expand Up @@ -299,9 +303,7 @@ where
/// of the interpreter and will avoid unnecessary copy with the data
/// that can be referenced from the interpreter instance itself.
pub fn transact(&mut self, tx: Transaction) -> Result<StateTransitionRef<'_>, InterpreterError> {
let state_result = self
.init_with_storage(tx, self.consensus_parameters)
.and_then(|_| self.run());
let state_result = self.init_with_storage(tx).and_then(|_| self.run());

#[cfg(feature = "profile-any")]
self.profiler.on_transaction(&state_result);
Expand Down
78 changes: 35 additions & 43 deletions src/interpreter/initialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use crate::context::Context;
use crate::error::InterpreterError;
use crate::storage::InterpreterStorage;

use fuel_tx::default_parameters::*;
use fuel_tx::{ConsensusParameters, Input, Output, Transaction, ValidationError};
use fuel_tx::{Input, Output, Transaction, ValidationError};
use fuel_types::bytes::{SerializableVec, SizedBytes};
use fuel_types::{AssetId, Word};
use itertools::Itertools;
Expand All @@ -15,15 +14,12 @@ use std::io;

impl<S> Interpreter<S> {
/// Initialize the VM with a given transaction
pub fn init(
&mut self,
predicate: bool,
block_height: u32,
mut tx: Transaction,
params: ConsensusParameters,
) -> Result<(), InterpreterError> {
tx.validate_without_signature(self.block_height() as Word, &params)?;
tx.precompute_metadata();
pub fn init(&mut self, predicate: bool, block_height: u32, tx: Transaction) -> Result<(), InterpreterError> {
self.tx = tx;

self.tx
.validate_without_signature(self.block_height() as Word, &self.params)?;
self.tx.precompute_metadata();

self.block_height = block_height;
self.context = if predicate { Context::Predicate } else { Context::Script };
Expand All @@ -40,15 +36,15 @@ impl<S> Interpreter<S> {
// Set heap area
self.registers[REG_HP] = VM_MAX_RAM - 1;

self.push_stack(tx.id().as_ref())
self.push_stack(self.tx.id().as_ref())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

let free_balances = if predicate {
// predicate verification should zero asset ids
0
} else {
// Set initial unused balances
let free_balances = Self::initial_free_balances(&tx)?;
let free_balances = self.initial_free_balances()?;

for (asset_id, amount) in free_balances.iter().sorted_by_key(|i| i.0) {
// push asset ID
Expand All @@ -68,36 +64,35 @@ impl<S> Interpreter<S> {
};

// zero out remaining unused balance types
let unused_balances = MAX_INPUTS as Word - free_balances;
let unused_balances = self.params.max_inputs as Word - free_balances;
let unused_balances = unused_balances * (AssetId::LEN + WORD_SIZE) as Word;

// Its safe to just reserve since the memory was properly zeroed before in this routine
self.reserve_stack(unused_balances)?;

let tx_size = tx.serialized_size() as Word;
let tx_size = self.tx.serialized_size() as Word;

self.registers[REG_GGAS] = tx.gas_limit();
self.registers[REG_CGAS] = tx.gas_limit();
self.registers[REG_GGAS] = self.tx.gas_limit();
self.registers[REG_CGAS] = self.tx.gas_limit();

self.push_stack(&tx_size.to_be_bytes())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

self.push_stack(tx.to_bytes().as_slice())
let tx = self.tx.to_bytes();
self.push_stack(tx.as_slice())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;

self.registers[REG_SP] = self.registers[REG_SSP];

self.tx = tx;

Ok(())
}

// compute the initial free balances for each asset type
pub(crate) fn initial_free_balances(tx: &Transaction) -> Result<HashMap<AssetId, Word>, InterpreterError> {
pub(crate) fn initial_free_balances(&self) -> Result<HashMap<AssetId, Word>, InterpreterError> {
let mut balances = HashMap::<AssetId, Word>::new();

// Add up all the inputs for each asset ID
for (asset_id, amount) in tx.inputs().iter().filter_map(|input| match input {
for (asset_id, amount) in self.tx.inputs().iter().filter_map(|input| match input {
Input::CoinPredicate { asset_id, amount, .. } | Input::CoinSigned { asset_id, amount, .. } => {
Some((asset_id, amount))
}
Expand All @@ -109,35 +104,32 @@ impl<S> Interpreter<S> {
// Reduce by unavailable balances
let base_asset = AssetId::default();
if let Some(base_asset_balance) = balances.get_mut(&base_asset) {
// remove byte costs from base asset spendable balance
let byte_balance = (tx.metered_bytes_size() as Word)
.checked_mul(tx.byte_price())
.ok_or(ValidationError::ArithmeticOverflow)?;

// remove gas costs from base asset spendable balance
// gas = limit * price
let gas_cost = tx
.gas_limit()
.checked_mul(tx.gas_price())
.ok_or(ValidationError::ArithmeticOverflow)?;

// add up total amount of required fees
let total_fee = byte_balance
.checked_add(gas_cost)
.ok_or(ValidationError::ArithmeticOverflow)?;
// calculate the fee with used witness bytes + gas limit
let factor = self.params.gas_price_factor as f64;

let bytes = self
.tx
.byte_price()
.saturating_mul(self.tx.metered_bytes_size() as Word);
let bytes = (bytes as f64 / factor).ceil() as Word;

let gas = self.tx.gas_price().saturating_mul(self.tx.gas_limit()) as f64;
let gas = (gas / factor).ceil() as Word;

let fee = bytes.saturating_add(gas);

// subtract total fee from base asset balance
*base_asset_balance =
base_asset_balance
.checked_sub(total_fee)
.checked_sub(fee)
.ok_or(ValidationError::InsufficientFeeAmount {
expected: total_fee,
expected: fee,
provided: *base_asset_balance,
})?;
}

// reduce free balances by coin and withdrawal outputs
for (asset_id, amount) in tx.outputs().iter().filter_map(|output| match output {
for (asset_id, amount) in self.tx.outputs().iter().filter_map(|output| match output {
Output::Coin { asset_id, amount, .. } => Some((asset_id, amount)),
Output::Withdrawal { asset_id, amount, .. } => Some((asset_id, amount)),
_ => None,
Expand Down Expand Up @@ -166,10 +158,10 @@ where
/// execution of contract opcodes.
///
/// For predicate verification, check [`Self::init`]
pub fn init_with_storage(&mut self, tx: Transaction, params: ConsensusParameters) -> Result<(), InterpreterError> {
pub fn init_with_storage(&mut self, tx: Transaction) -> Result<(), InterpreterError> {
let predicate = false;
let block_height = self.storage.block_height().map_err(InterpreterError::from_io)?;

self.init(predicate, block_height, tx, params)
self.init(predicate, block_height, tx)
}
}
17 changes: 9 additions & 8 deletions src/interpreter/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl<S> Interpreter<S> {
}

// update serialized memory state
let offset = VM_TX_MEMORY + self.tx.output_offset(out_idx).ok_or(PanicReason::OutputNotFound)?;
let offset = self.tx_offset() + self.tx.output_offset(out_idx).ok_or(PanicReason::OutputNotFound)?;
let bytes = &mut self.memory[offset..];
let _ = output.read(bytes)?;

Expand All @@ -217,7 +217,7 @@ impl<S> Interpreter<S> {

self.tx
.receipts_root_offset()
.map(|offset| offset + VM_TX_MEMORY)
.map(|offset| offset + self.tx_offset())
.map(|offset| {
// TODO this generates logarithmic gas cost to the receipts count. This won't fit the
// linear monadic model and should be discussed. Maybe the receipts tree should have
Expand All @@ -235,11 +235,14 @@ impl<S> Interpreter<S> {
(&mut self.memory[offset..offset + Bytes32::LEN]).copy_from_slice(&root[..]);
});
}

pub(crate) const fn tx_offset(&self) -> usize {
self.params.tx_offset()
}
}

#[cfg(all(test, feature = "random"))]
mod tests {
use crate::consts::VM_TX_MEMORY;
use crate::prelude::*;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
Expand Down Expand Up @@ -276,8 +279,7 @@ mod tests {
vec![vec![].into()],
);

vm.init_with_storage(tx, Default::default())
.expect("Failed to init VM!");
vm.init_with_storage(tx).expect("Failed to init VM!");

for (asset_id, amount) in balances {
assert!(vm.external_asset_id_balance_sub(&asset_id, amount + 1).is_err());
Expand Down Expand Up @@ -320,8 +322,7 @@ mod tests {
vec![Witness::default()],
);

vm.init_with_storage(tx, Default::default())
.expect("Failed to init VM!");
vm.init_with_storage(tx).expect("Failed to init VM!");

// increase variable output
vm.set_variable_output(0, asset_id_to_update, amount_to_set, owner)
Expand All @@ -336,7 +337,7 @@ mod tests {
));

// verify the vm memory is updated properly
let position = VM_TX_MEMORY + vm.tx.output_offset(0).unwrap();
let position = vm.params.tx_offset() + vm.tx.output_offset(0).unwrap();
let mut mem_output = Output::variable(Default::default(), Default::default(), Default::default());
let _ = mem_output.write(&vm.memory()[position..]).unwrap();
assert_eq!(vm.tx.outputs()[0], mem_output);
Expand Down
Loading

0 comments on commit 72dbdcf

Please sign in to comment.