Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use REG_CGAS as default forwarded gas #1122

Merged
merged 4 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ mod tests {
.await?;
// ANCHOR_END: contract_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 333);
assert_eq!(transaction_cost.gas_used, 331);

Ok(())
}
Expand Down Expand Up @@ -636,7 +636,7 @@ mod tests {
.await?;
// ANCHOR_END: multi_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 546);
assert_eq!(transaction_cost.gas_used, 542);

Ok(())
}
Expand Down
155 changes: 91 additions & 64 deletions packages/fuels-programs/src/call_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ use crate::contract::ContractCall;
/// Specifies offsets of [`Instruction::CALL`] parameters stored in the script
/// data from which they can be loaded into registers
pub(crate) struct CallOpcodeParamsOffset {
pub asset_id_offset: usize,
pub amount_offset: usize,
pub gas_forwarded_offset: usize,
pub call_data_offset: usize,
pub amount_offset: usize,
pub asset_id_offset: usize,
pub gas_forwarded_offset: Option<usize>,
}

/// How many times to attempt to resolve missing tx dependencies.
Expand Down Expand Up @@ -110,7 +110,7 @@ pub(crate) async fn build_tx_from_contract_calls(
let data_offset = call_script_data_offset(&consensus_parameters, calls_instructions_len);

let (script_data, call_param_offsets) =
build_script_data_from_contract_calls(calls, data_offset, tx_parameters.gas_limit());
build_script_data_from_contract_calls(calls, data_offset);

let script = get_instructions(calls, call_param_offsets);

Expand Down Expand Up @@ -143,27 +143,28 @@ pub(crate) async fn build_tx_from_contract_calls(
/// Compute the length of the calling scripts for the two types of contract calls: those that return
/// a heap type, and those that don't.
fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize {
let n_heap_type_calls = calls
calls
.iter()
.filter(|c| c.output_param.is_vm_heap_type())
.count();
let n_stack_type_calls = calls.len() - n_heap_type_calls;

let total_instructions_len_stack_data =
// Use placeholder for `call_param_offsets` and `output_param_type`, because the length of
// the calling script doesn't depend on the underlying type, just on whether or not the
// contract call output type is a heap type.
get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64).len()
* n_stack_type_calls;

let total_instructions_len_heap_data = get_single_call_instructions(
&CallOpcodeParamsOffset::default(),
&ParamType::Vector(Box::from(ParamType::U64)),
)
.len()
* n_heap_type_calls;
.map(|c| {
// Use placeholder for `call_param_offsets` and `output_param_type`, because the length of
// the calling script doesn't depend on the underlying type, just on whether or not
// gas was forwarded or contract call output type is a heap type.

total_instructions_len_stack_data + total_instructions_len_heap_data
let mut call_opcode_params = CallOpcodeParamsOffset::default();

if c.call_parameters.gas_forwarded().is_some() {
call_opcode_params.gas_forwarded_offset = Some(0);
}

let param_type = if c.output_param.is_vm_heap_type() {
ParamType::Vector(Box::from(ParamType::U64))
} else {
ParamType::U64
};

get_single_call_instructions(&call_opcode_params, &param_type).len()
})
.sum()
}

/// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls`
Expand Down Expand Up @@ -225,17 +226,16 @@ pub(crate) fn get_instructions(
}

/// Returns script data, consisting of the following items in the given order:
/// 1. Asset ID to be forwarded ([`AssetId::LEN`])
/// 2. Amount to be forwarded `(1 * `[`WORD_SIZE`]`)`
/// 3. Gas to be forwarded `(1 * `[`WORD_SIZE`]`)`
/// 4. Contract ID ([`ContractId::LEN`]);
/// 5. Function selector `(1 * `[`WORD_SIZE`]`)`
/// 1. Amount to be forwarded `(1 * `[`WORD_SIZE`]`)`
/// 2. Asset ID to be forwarded ([`AssetId::LEN`])
/// 3. Contract ID ([`ContractId::LEN`]);
/// 4. Function selector `(1 * `[`WORD_SIZE`]`)`
/// 5. Gas to be forwarded `(1 * `[`WORD_SIZE`]`)`
/// 6. Calldata offset (optional) `(1 * `[`WORD_SIZE`]`)`
/// 7. Encoded arguments (optional) (variable length)
pub(crate) fn build_script_data_from_contract_calls(
calls: &[ContractCall],
data_offset: usize,
gas_limit: u64,
) -> (Vec<u8>, Vec<CallOpcodeParamsOffset>) {
let mut script_data = vec![];
let mut param_offsets = vec![];
Expand All @@ -244,36 +244,46 @@ pub(crate) fn build_script_data_from_contract_calls(
let mut segment_offset = data_offset;

for call in calls {
let gas_forwarded = call.call_parameters.gas_forwarded();

let call_param_offsets = CallOpcodeParamsOffset {
asset_id_offset: segment_offset,
amount_offset: segment_offset + AssetId::LEN,
gas_forwarded_offset: segment_offset + AssetId::LEN + WORD_SIZE,
call_data_offset: segment_offset + AssetId::LEN + 2 * WORD_SIZE,
amount_offset: segment_offset,
asset_id_offset: segment_offset + WORD_SIZE,
call_data_offset: segment_offset + WORD_SIZE + AssetId::LEN,
gas_forwarded_offset: gas_forwarded
.map(|_| segment_offset + WORD_SIZE + AssetId::LEN + ContractId::LEN + WORD_SIZE),
};
param_offsets.push(call_param_offsets);

script_data.extend(call.call_parameters.asset_id().iter());

script_data.extend(call.call_parameters.amount().to_be_bytes());

// If gas_forwarded is not set, use the transaction gas limit
let gas_forwarded = call.call_parameters.gas_forwarded().unwrap_or(gas_limit);
script_data.extend(gas_forwarded.to_be_bytes());

script_data.extend(call.call_parameters.asset_id().iter());
script_data.extend(call.contract_id.hash().as_ref());

script_data.extend(call.encoded_selector);

let gas_forwarded_size = gas_forwarded
.map(|gf| {
script_data.extend(gf.to_be_bytes());

WORD_SIZE
})
.unwrap_or_default();

// If the method call takes custom inputs or has more than
// one argument, we need to calculate the `call_data_offset`,
// which points to where the data for the custom types start in the
// transaction. If it doesn't take any custom inputs, this isn't necessary.
let encoded_args_start_offset = if call.compute_custom_input_offset {
// Custom inputs are stored after the previously added parameters,
// including custom_input_offset
let custom_input_offset =
segment_offset + AssetId::LEN + 2 * WORD_SIZE + ContractId::LEN + 2 * WORD_SIZE;
let custom_input_offset = segment_offset
+ WORD_SIZE // amount size
+ AssetId::LEN
+ ContractId::LEN
+ WORD_SIZE // encoded_selector size
+ gas_forwarded_size
+ WORD_SIZE; // custom_input_offset size
script_data.extend((custom_input_offset as Word).to_be_bytes());

custom_input_offset
} else {
segment_offset
Expand All @@ -295,9 +305,9 @@ pub(crate) fn build_script_data_from_contract_calls(
/// pointing at the following registers:
///
/// 0x10 Script data offset
/// 0x11 Gas forwarded
/// 0x12 Coin amount
/// 0x13 Asset ID
/// 0x11 Coin amount
/// 0x12 Asset ID
/// 0x13 Gas forwarded
///
/// Note that these are soft rules as we're picking this addresses simply because they
/// non-reserved register.
Expand All @@ -309,10 +319,6 @@ pub(crate) fn get_single_call_instructions(
.call_data_offset
.try_into()
.expect("call_data_offset out of range");
let gas_forwarded_offset = offsets
.gas_forwarded_offset
.try_into()
.expect("gas_forwarded_offset out of range");
let amount_offset = offsets
.amount_offset
.try_into()
Expand All @@ -324,14 +330,28 @@ pub(crate) fn get_single_call_instructions(

let mut instructions = [
op::movi(0x10, call_data_offset),
op::movi(0x11, gas_forwarded_offset),
op::movi(0x11, amount_offset),
op::lw(0x11, 0x11, 0),
op::movi(0x12, amount_offset),
op::lw(0x12, 0x12, 0),
op::movi(0x13, asset_id_offset),
op::call(0x10, 0x12, 0x13, 0x11),
op::movi(0x12, asset_id_offset),
]
.to_vec();

match offsets.gas_forwarded_offset {
Some(gas_forwarded_offset) => {
let gas_forwarded_offset = gas_forwarded_offset
.try_into()
.expect("gas_forwarded_offset out of range");
hal3e marked this conversation as resolved.
Show resolved Hide resolved

instructions.extend(&[
op::movi(0x13, gas_forwarded_offset),
op::lw(0x13, 0x13, 0),
op::call(0x10, 0x11, 0x12, 0x13),
]);
}
// If `gas_forwarded` was not set use `REG_CGAS`
None => instructions.push(op::call(0x10, 0x11, 0x12, RegId::CGAS)),
};

// The instructions are different if you want to return data that was on the heap
if let Some(inner_type_byte_size) = output_param_type.heap_inner_element_size() {
instructions.extend([
Expand Down Expand Up @@ -550,6 +570,7 @@ mod test {
async fn test_script_data() {
// Arrange
const SELECTOR_LEN: usize = WORD_SIZE;
const GAS_FORWARDED_SIZE: usize = WORD_SIZE;
const NUM_CALLS: usize = 3;

let contract_ids = vec![
Expand Down Expand Up @@ -588,7 +609,7 @@ mod test {
.collect();

// Act
let (script_data, param_offsets) = build_script_data_from_contract_calls(&calls, 0, 0);
let (script_data, param_offsets) = build_script_data_from_contract_calls(&calls, 0);

// Assert
assert_eq!(param_offsets.len(), NUM_CALLS);
Expand All @@ -602,9 +623,9 @@ mod test {
script_data[offsets.amount_offset..offsets.amount_offset + WORD_SIZE].to_vec();
assert_eq!(amount, idx.to_be_bytes());

let gas = script_data
[offsets.gas_forwarded_offset..offsets.gas_forwarded_offset + WORD_SIZE]
.to_vec();
let gas_forwarded_offset = offsets.gas_forwarded_offset.expect("is set");

let gas = script_data[gas_forwarded_offset..gas_forwarded_offset + WORD_SIZE].to_vec();
assert_eq!(gas, idx.to_be_bytes().to_vec());

let contract_id =
Expand All @@ -618,25 +639,31 @@ mod test {
}

// Calls 1 and 3 have their input arguments after the selector
let call_1_arg_offset = param_offsets[0].call_data_offset + ContractId::LEN + SELECTOR_LEN;
let call_1_arg_offset =
param_offsets[0].call_data_offset + ContractId::LEN + SELECTOR_LEN + GAS_FORWARDED_SIZE;
let call_1_arg = script_data[call_1_arg_offset..call_1_arg_offset + WORD_SIZE].to_vec();
assert_eq!(call_1_arg, args[0].resolve(0));

let call_3_arg_offset = param_offsets[2].call_data_offset + ContractId::LEN + SELECTOR_LEN;
let call_3_arg_offset =
param_offsets[2].call_data_offset + ContractId::LEN + SELECTOR_LEN + GAS_FORWARDED_SIZE;
let call_3_arg = script_data[call_3_arg_offset..call_3_arg_offset + WORD_SIZE].to_vec();
assert_eq!(call_3_arg, args[2].resolve(0));

// Call 2 has custom inputs and custom_input_offset
let call_2_arg_offset = param_offsets[1].call_data_offset + ContractId::LEN + SELECTOR_LEN;
let call_2_arg_offset =
param_offsets[1].call_data_offset + ContractId::LEN + SELECTOR_LEN + GAS_FORWARDED_SIZE;
let custom_input_offset =
script_data[call_2_arg_offset..call_2_arg_offset + WORD_SIZE].to_vec();
assert_eq!(
custom_input_offset,
(call_2_arg_offset + WORD_SIZE).to_be_bytes()
);

let custom_input_offset =
param_offsets[1].call_data_offset + ContractId::LEN + SELECTOR_LEN + WORD_SIZE;
let custom_input_offset = param_offsets[1].call_data_offset
+ ContractId::LEN
+ SELECTOR_LEN
+ GAS_FORWARDED_SIZE
+ WORD_SIZE;
let custom_input =
script_data[custom_input_offset..custom_input_offset + WORD_SIZE].to_vec();
assert_eq!(custom_input, args[1].resolve(0));
Expand Down
6 changes: 3 additions & 3 deletions packages/fuels/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,9 @@ async fn test_contract_call_fee_estimation() -> Result<()> {
let tolerance = 0.2;

let expected_min_gas_price = 0; // This is the default min_gas_price from the ConsensusParameters
let expected_gas_used = 399;
let expected_metered_bytes_size = 728;
let expected_total_fee = 332;
let expected_gas_used = 397;
let expected_metered_bytes_size = 712;
let expected_total_fee = 325;

let estimated_transaction_cost = contract_instance
.methods()
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels/tests/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<()> {
);

// The gas used by the script to call a contract and forward remaining gas limit.
let gas_used_by_script = 161;
let gas_used_by_script = 159;
let gas_limit = 225_883;
let response = contract_instance
.methods()
Expand Down
Loading