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

Refactor: Simplify transaction handling and gas calculations #2262

Closed
wants to merge 6 commits into from
Closed
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 bin/e2e-test-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ impl Default for SuiteConfig {

#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct ClientConfig {
// overrides the default endpoint for the suite
// Overrides the default endpoint for the suite
pub endpoint: Option<String>,
// the account to use
// The account to use
pub secret: SecretKey,
}

Expand Down
135 changes: 49 additions & 86 deletions bin/e2e-test-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::{
config::SuiteConfig,
test_context::TestContext,
};
use futures::future::BoxFuture;
use futures::FutureExt; // For using the `.boxed()` method
use libtest_mimic::{
Arguments,
Failed,
Expand All @@ -11,6 +13,7 @@ use std::{
env,
fs,
future::Future,
sync::Arc,
time::Duration,
};

Expand All @@ -22,110 +25,72 @@ pub mod test_context;
pub mod tests;

pub fn main_body(config: SuiteConfig, mut args: Arguments) {
fn with_cloned(
config: &SuiteConfig,
f: impl FnOnce(SuiteConfig) -> anyhow::Result<(), Failed>,
) -> impl FnOnce() -> anyhow::Result<(), Failed> {
let config = config.clone();
move || f(config)
}

// If we run tests in parallel they may fail because try to use the same state like UTXOs.
// If we run tests in parallel they may fail because they try to use the same state like UTXOs.
args.test_threads = Some(1);

let tests = vec![
Trial::test(
"can transfer from alice to bob",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::transfers::basic_transfer(&ctx).await
})
}),
),
Trial::test(
create_test("can transfer from alice to bob", &config, tests::transfers::basic_transfer),
create_test(
"can transfer from alice to bob and back",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::transfers::transfer_back(&ctx).await
})
}),
),
Trial::test(
"can collect fee from alice",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::collect_fee::collect_fee(&ctx).await
})
}),
&config,
tests::transfers::transfer_back,
),
Trial::test(
"can execute script and get receipts",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::transfers::transfer_back(&ctx).await
})
}),
),
Trial::test(
create_test("can collect fee from alice", &config, tests::collect_fee::collect_fee),
create_test("can execute script and get receipts", &config, tests::script::receipts),
create_test(
"can dry run transfer script and get receipts",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::script::dry_run(&ctx).await
})?;
Ok(())
}),
&config,
tests::script::dry_run,
),
Trial::test(
create_test(
"can dry run multiple transfer scripts and get receipts",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::script::dry_run_multiple_txs(&ctx).await
})?;
Ok(())
}),
&config,
tests::script::dry_run_multiple_txs,
),
Trial::test(
create_test(
"dry run script that touches the contract with large state",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::script::run_contract_large_state(&ctx).await
})?;
Ok(())
}),
&config,
tests::script::run_contract_large_state,
),
Trial::test(
create_test(
"dry run transaction from `arbitrary_tx.raw` file",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::script::arbitrary_transaction(&ctx).await
})?;
Ok(())
}),
&config,
tests::script::arbitrary_transaction,
),
Trial::test(
create_test(
"can deploy a large contract",
with_cloned(&config, |config| {
async_execute(async {
let ctx = TestContext::new(config).await;
tests::transfers::transfer_back(&ctx).await
})
}),
&config,
tests::contracts::deploy_large_contract,
),
];

libtest_mimic::run(&args, tests).exit();
}

// Helper function to reduce code duplication when creating tests
fn create_test<F>(
name: &'static str,
config: &SuiteConfig,
test_fn: F,
) -> Trial
where
F: FnOnce(Arc<TestContext>) -> BoxFuture<'static, anyhow::Result<(), Failed>>
+ Send
+ Sync
+ 'static,
{
let cloned_config = config.clone();
Trial::test(
name,
move || async_execute(async move {
let ctx = Arc::new(TestContext::new(cloned_config).await);
test_fn(ctx).await
}),
)
}

pub fn load_config_env() -> SuiteConfig {
// load from env var
// Load from environment variable
env::var_os(CONFIG_FILE_KEY)
.map(|path| load_config(path.to_string_lossy().to_string()))
.unwrap_or_default()
Expand All @@ -136,9 +101,7 @@ pub fn load_config(path: String) -> SuiteConfig {
toml::from_slice(&file).unwrap()
}

fn async_execute<F: Future<Output = anyhow::Result<(), Failed>>>(
func: F,
) -> Result<(), Failed> {
fn async_execute<F: Future<Output = anyhow::Result<(), Failed>>>(func: F) -> Result<(), Failed> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
Expand Down
73 changes: 24 additions & 49 deletions bin/e2e-test-client/src/test_context.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,32 @@
//! Utilities and helper methods for writing tests

use anyhow::{
anyhow,
Context,
};
use anyhow::{anyhow, Context};
use fuel_core_chain_config::ContractConfig;
use fuel_core_client::client::{
types::{
CoinType,
TransactionStatus,
},
types::{CoinType, TransactionStatus},
FuelClient,
};
use fuel_core_types::{
fuel_asm::{
op,
GTFArgs,
RegId,
},
fuel_asm::{op, GTFArgs, RegId},
fuel_crypto::PublicKey,
fuel_tx::{
ConsensusParameters,
Contract,
ContractId,
Finalizable,
Input,
Output,
Transaction,
TransactionBuilder,
TxId,
UniqueIdentifier,
UtxoId,
ConsensusParameters, Contract, ContractId, Finalizable, Input, Output, Transaction,
TransactionBuilder, TxId, UniqueIdentifier, UtxoId,
},
fuel_types::{
canonical::Serialize,
Address,
AssetId,
Salt,
Address, AssetId, Salt,
},
fuel_vm::SecretKey,
};
use itertools::Itertools;

use crate::config::{
ClientConfig,
SuiteConfig,
};
use crate::config::{ClientConfig, SuiteConfig};

// The base amount needed to cover the cost of a simple transaction
pub const BASE_AMOUNT: u64 = 100_000_000;

#[derive(Clone)]
pub struct TestContext {
pub alice: Wallet,
pub bob: Wallet,
Expand All @@ -61,8 +38,8 @@ impl TestContext {
let alice_client = Self::new_client(config.endpoint.clone(), &config.wallet_a);
let bob_client = Self::new_client(config.endpoint.clone(), &config.wallet_b);
Self {
alice: Wallet::new(config.wallet_a.secret, alice_client).await,
bob: Wallet::new(config.wallet_b.secret, bob_client).await,
alice: Wallet::new(config.wallet_a.secret.clone(), alice_client).await,
bob: Wallet::new(config.wallet_b.secret.clone(), bob_client).await,
config,
}
}
Expand All @@ -84,7 +61,7 @@ impl Wallet {
pub async fn new(secret: SecretKey, client: FuelClient) -> Self {
let public_key: PublicKey = (&secret).into();
let address = Input::owner(&public_key);
// get consensus params
// Get consensus params
let consensus_params = client
.chain_info()
.await
Expand All @@ -98,7 +75,7 @@ impl Wallet {
}
}

/// returns the balance associated with a wallet
/// Returns the balance associated with a wallet
pub async fn balance(&self, asset_id: Option<AssetId>) -> anyhow::Result<u64> {
self.client
.balance(&self.address, Some(&asset_id.unwrap_or_default()))
Expand All @@ -122,21 +99,21 @@ impl Wallet {
) -> anyhow::Result<Transaction> {
let asset_id = asset_id.unwrap_or(*self.consensus_params.base_asset_id());
let total_amount = transfer_amount + BASE_AMOUNT;
// select coins
// Select coins
let coins = &self
.client
.coins_to_spend(&self.address, vec![(asset_id, total_amount, None)], None)
.await?[0];

// build transaction
// Build transaction
let mut tx = TransactionBuilder::script(Default::default(), Default::default());
tx.max_fee_limit(BASE_AMOUNT);
tx.script_gas_limit(0);

for coin in coins {
if let CoinType::Coin(coin) = coin {
tx.add_unsigned_coin_input(
self.secret,
self.secret.clone(),
coin.utxo_id,
coin.amount,
coin.asset_id,
Expand Down Expand Up @@ -165,7 +142,7 @@ impl Wallet {
coinbase_contract: ContractId,
asset_id: AssetId,
) -> anyhow::Result<Transaction> {
// select coins
// Select coins
let coins = &self
.client
.coins_to_spend(
Expand All @@ -190,7 +167,7 @@ impl Wallet {
op::ret(RegId::ONE),
];

// build transaction
// Build transaction
let mut tx_builder = TransactionBuilder::script(
script.into_iter().collect(),
asset_id
Expand All @@ -216,7 +193,7 @@ impl Wallet {
for coin in coins {
if let CoinType::Coin(coin) = coin {
tx_builder.add_unsigned_coin_input(
self.secret,
self.secret.clone(),
coin.utxo_id,
coin.amount,
coin.asset_id,
Expand Down Expand Up @@ -258,10 +235,10 @@ impl Wallet {
println!("submitting tx... {:?}", tx_id);
let status = self.client.submit_and_await_commit(&tx).await?;

// we know the transferred coin should be output 0 from above
// We know the transferred coin should be output 0 from above
let transferred_utxo = UtxoId::new(tx_id, 0);

// get status and return the utxo id of transferred coin
// Get status and return the utxo id of transferred coin
Ok(TransferResult {
tx_id,
transferred_utxo,
Expand All @@ -277,7 +254,7 @@ impl Wallet {
) -> anyhow::Result<()> {
let asset_id = *self.consensus_params.base_asset_id();
let total_amount = BASE_AMOUNT;
// select coins
// Select coins
let coins = &self
.client
.coins_to_spend(&self.address, vec![(asset_id, total_amount, None)], None)
Expand All @@ -301,7 +278,7 @@ impl Wallet {
for coin in coins {
if let CoinType::Coin(coin) = coin {
tx.add_unsigned_coin_input(
self.secret,
self.secret.clone(),
coin.utxo_id,
coin.amount,
coin.asset_id,
Expand Down Expand Up @@ -329,10 +306,8 @@ impl Wallet {
.submit_and_await_commit(&tx.clone().into())
.await?;

// check status of contract deployment
if let TransactionStatus::Failure { .. } | TransactionStatus::SqueezedOut { .. } =
&status
{
// Check status of contract deployment
if let TransactionStatus::Failure { .. } | TransactionStatus::SqueezedOut { .. } = &status {
return Err(anyhow!(format!("unexpected transaction status {status:?}")));
}

Expand Down
Loading