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

fix: include witness data in estimation #1426

Merged
merged 9 commits into from
Jun 14, 2024
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
1 change: 1 addition & 0 deletions e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ build = "build.rs"
[dev-dependencies]
# used in test assertions
chrono = { workspace = true }
fuel-asm = { workspace = true }
# TODO: [issue](https://github.com/FuelLabs/fuels-rs/issues/1375) needs to be removed, `ScriptTransaction` and `CreateTransaction` in `fuels` use `fuel_tx::Input` but don't reexport or convert it into a `fuels` owned type
fuel-tx = { workspace = true }
fuels = { workspace = true }
Expand Down
75 changes: 75 additions & 0 deletions e2e/tests/providers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{ops::Add, path::Path};

use chrono::{DateTime, Duration, TimeZone, Utc};
use fuel_asm::RegId;
use fuel_tx::Witness;
use fuels::{
accounts::Account,
client::{PageDirection, PaginationRequest},
Expand Down Expand Up @@ -1071,3 +1073,76 @@ async fn tx_respects_policies() -> Result<()> {

Ok(())
}

#[tokio::test]
async fn tx_with_witness_data() -> Result<()> {
use fuel_asm::{op, GTFArgs};

let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?;

let receiver = WalletUnlocked::new_random(Some(provider.clone()));

let inputs = wallet
.get_asset_inputs_for_amount(*provider.base_asset_id(), 10000, None)
.await?;
let outputs =
wallet.get_asset_outputs_for_amount(receiver.address(), *provider.base_asset_id(), 1);

let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;

// we test that the witness data wasn't tempered with during the build (gas estimation) process
// if the witness data is tempered with, the estimation will be off and the transaction
// will error out with `OutOfGas`
let script: Vec<u8> = vec![
// load witness data into register 0x10
op::gtf(0x10, 0x00, GTFArgs::WitnessData.into()),
op::lw(0x10, 0x10, 0x00),
// load expected value into register 0x11
op::movi(0x11, 0x0f),
// load the offset of the revert instruction into register 0x12
op::movi(0x12, 0x08),
// compare the two values and jump to the revert instruction if they are not equal
op::jne(0x10, 0x11, 0x12),
// do some expensive operation so gas estimation is higher if comparison passes
op::gtf(0x13, 0x01, GTFArgs::WitnessData.into()),
op::gtf(0x14, 0x01, GTFArgs::WitnessDataLength.into()),
op::aloc(0x14),
op::eck1(RegId::HP, 0x13, 0x13),
// return the witness data
op::ret(0x10),
op::rvrt(RegId::ZERO),
]
.into_iter()
.collect();
tb.script = script;

let expected_data = 15u64;
let witness = Witness::from(expected_data.to_be_bytes().to_vec());
tb.witnesses_mut().push(witness);

let tx = tb
.with_tx_policies(TxPolicies::default().with_witness_limit(1000))
.build(provider)
.await?;

let status = provider.send_transaction_and_await_commit(tx).await?;

match status {
TxStatus::Success { receipts } => {
let ret: u64 = receipts
.into_iter()
.find_map(|receipt| match receipt {
Receipt::Return { val, .. } => Some(val),
_ => None,
})
.expect("should have return value");

assert_eq!(ret, expected_data);
}
_ => panic!("expected success status"),
}

Ok(())
}
14 changes: 9 additions & 5 deletions packages/fuels-core/src/types/transaction_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,8 @@ impl ScriptTransactionBuilder {

async fn resolve_fuel_tx(self, dry_runner: impl DryRunner) -> Result<Script> {
let num_resolved_witnesses = self.num_witnesses()?;
let mut script_dry_runner = self.script_dry_runner(num_resolved_witnesses, &dry_runner);
let predefined_witnesses = self.witnesses.clone();
let mut script_dry_runner = self.script_dry_runner(predefined_witnesses, &dry_runner);

let mut tx = FuelTransaction::script(
0, // default value - will be overwritten
Expand Down Expand Up @@ -566,13 +567,16 @@ impl ScriptTransactionBuilder {
Ok(())
}

fn script_dry_runner<D>(&self, num_resolved_witnesses: u16, dry_runner: D) -> ScriptDryRunner<D>
fn script_dry_runner<D>(
&self,
predefined_witnesses: Vec<Witness>,
dry_runner: D,
) -> ScriptDryRunner<D>
where
D: DryRunner,
{
let total_witnesses = self.unresolved_witness_indexes.owner_to_idx_offset.len()
+ num_resolved_witnesses as usize;
ScriptDryRunner::new(dry_runner, total_witnesses)
let num_unresolved_witnesses = self.unresolved_witness_indexes.owner_to_idx_offset.len();
ScriptDryRunner::new(dry_runner, predefined_witnesses, num_unresolved_witnesses)
}

async fn add_variable_outputs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ use crate::{

pub(crate) struct ScriptDryRunner<R> {
dry_runner: R,
num_total_witnesses: usize,
predefined_witnesses: Vec<Witness>,
num_unresolved_witnesses: usize,
last_dry_run: Option<DryRun>,
}

impl<R> ScriptDryRunner<R> {
pub fn new(dry_runner: R, num_total_witnesses: usize) -> Self {
pub fn new(
dry_runner: R,
predefined_witnesses: Vec<Witness>,
num_unresolved_witnesses: usize,
) -> Self {
Self {
dry_runner,
num_total_witnesses,
predefined_witnesses,
num_unresolved_witnesses,
last_dry_run: None,
}
}
Expand Down Expand Up @@ -78,9 +84,13 @@ impl<R: DryRunner> ScriptDryRunner<R> {
// Using a `Signature` ensures that the calculated fee includes the fee generated by the witnesses.
fn add_fake_witnesses(&mut self, tx: &mut fuel_tx::Script) {
let witness: Witness = Signature::default().as_ref().into();
let dry_run_witnesses: Vec<_> = repeat(witness).take(self.num_total_witnesses).collect();
let dry_run_witnesses: Vec<_> = repeat(witness)
.take(self.num_unresolved_witnesses)
.collect();

*tx.witnesses_mut() = dry_run_witnesses;
let predefined_witnesses = std::mem::take(&mut self.predefined_witnesses);

*tx.witnesses_mut() = [predefined_witnesses, dry_run_witnesses].concat();
}

fn add_fake_coins(&mut self, tx: &mut fuel_tx::Script) {
Expand Down
Loading