diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52cad62d49..f367656a3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ env: FUEL_CORE_PATCH_BRANCH: "" FUEL_CORE_PATCH_REVISION: "" RUST_VERSION: 1.79.0 - FORC_VERSION: 0.66.4 + FORC_VERSION: 0.66.5 FORC_PATCH_BRANCH: "" FORC_PATCH_REVISION: "" NEXTEST_HIDE_PROGRESS_BAR: "true" diff --git a/Cargo.toml b/Cargo.toml index 9e1180d8db..ef2004d574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ elliptic-curve = { version = "0.13.8", default-features = false } test-case = { version = "3.3", default-features = false } eth-keystore = "0.5.0" flate2 = { version = "1.0", default-features = false } -fuel-abi-types = "0.7.0" +fuel-abi-types = "0.8.0" futures = "0.3.29" hex = { version = "0.4.3", default-features = false } itertools = "0.12.0" @@ -88,23 +88,23 @@ dotenv = { version = "0.15", default-features = false } toml = { version = "0.8", default-features = false } # Dependencies from the `fuel-core` repository: -fuel-core = { version = "0.40.0", default-features = false, features = [ +fuel-core = { version = "0.40.1", default-features = false, features = [ "wasm-executor", ] } -fuel-core-chain-config = { version = "0.40.0", default-features = false } -fuel-core-client = { version = "0.40.0", default-features = false } -fuel-core-poa = { version = "0.40.0", default-features = false } -fuel-core-services = { version = "0.40.0", default-features = false } -fuel-core-types = { version = "0.40.0", default-features = false } +fuel-core-chain-config = { version = "0.40.1", default-features = false } +fuel-core-client = { version = "0.40.1", default-features = false } +fuel-core-poa = { version = "0.40.1", default-features = false } +fuel-core-services = { version = "0.40.1", default-features = false } +fuel-core-types = { version = "0.40.1", default-features = false } # Dependencies from the `fuel-vm` repository: -fuel-asm = { version = "0.58.0" } -fuel-crypto = { version = "0.58.0" } -fuel-merkle = { version = "0.58.0" } -fuel-storage = { version = "0.58.0" } -fuel-tx = { version = "0.58.0" } -fuel-types = { version = "0.58.0" } -fuel-vm = { version = "0.58.0" } +fuel-asm = { version = "0.58.2" } +fuel-crypto = { version = "0.58.2" } +fuel-merkle = { version = "0.58.2" } +fuel-storage = { version = "0.58.2" } +fuel-tx = { version = "0.58.2" } +fuel-types = { version = "0.58.2" } +fuel-vm = { version = "0.58.2" } # Workspace projects fuels = { version = "0.66.10", path = "./packages/fuels", default-features = false } diff --git a/e2e/Forc.toml b/e2e/Forc.toml index 530e385130..365dc9e567 100644 --- a/e2e/Forc.toml +++ b/e2e/Forc.toml @@ -24,7 +24,7 @@ members = [ 'sway/contracts/needs_custom_decoder', 'sway/contracts/payable_annotation', 'sway/contracts/proxy', - 'sway/contracts/require', + 'sway/contracts/revert_logs', 'sway/contracts/revert_transaction_error', 'sway/contracts/storage', 'sway/contracts/token_ops', @@ -55,7 +55,7 @@ members = [ 'sway/scripts/script_enum', 'sway/scripts/script_needs_custom_decoder', 'sway/scripts/script_proxy', - 'sway/scripts/script_require', + 'sway/scripts/script_revert_logs', 'sway/scripts/script_struct', 'sway/scripts/transfer_script', 'sway/types/contracts/b256', diff --git a/e2e/sway/contracts/asserts/src/main.sw b/e2e/sway/contracts/asserts/src/main.sw index a35e061e9a..63ec8546ab 100644 --- a/e2e/sway/contracts/asserts/src/main.sw +++ b/e2e/sway/contracts/asserts/src/main.sw @@ -29,9 +29,14 @@ impl Eq for TestEnum { abi TestContract { fn assert_primitive(a: u64, b: u64); + fn assert_eq_primitive(a: u64, b: u64); fn assert_eq_struct(test_struct: TestStruct, test_struct2: TestStruct); fn assert_eq_enum(test_enum: TestEnum, test_enum2: TestEnum); + + fn assert_ne_primitive(a: u64, b: u64); + fn assert_ne_struct(test_struct: TestStruct, test_struct2: TestStruct); + fn assert_ne_enum(test_enum: TestEnum, test_enum2: TestEnum); } impl TestContract for Contract { @@ -42,12 +47,20 @@ impl TestContract for Contract { fn assert_eq_primitive(a: u64, b: u64) { assert_eq(a, b); } - fn assert_eq_struct(test_struct: TestStruct, test_struct2: TestStruct) { assert_eq(test_struct, test_struct2); } - fn assert_eq_enum(test_enum: TestEnum, test_enum2: TestEnum) { assert_eq(test_enum, test_enum2); } + + fn assert_ne_primitive(a: u64, b: u64) { + assert_ne(a, b); + } + fn assert_ne_struct(test_struct: TestStruct, test_struct2: TestStruct) { + assert_ne(test_struct, test_struct2); + } + fn assert_ne_enum(test_enum: TestEnum, test_enum2: TestEnum) { + assert_ne(test_enum, test_enum2); + } } diff --git a/e2e/sway/contracts/require/Forc.toml b/e2e/sway/contracts/revert_logs/Forc.toml similarity index 81% rename from e2e/sway/contracts/require/Forc.toml rename to e2e/sway/contracts/revert_logs/Forc.toml index e69f6bab05..5845d86cfb 100644 --- a/e2e/sway/contracts/require/Forc.toml +++ b/e2e/sway/contracts/revert_logs/Forc.toml @@ -2,4 +2,4 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" -name = "require" +name = "revert_logs" diff --git a/e2e/sway/contracts/require/src/main.sw b/e2e/sway/contracts/revert_logs/src/main.sw similarity index 63% rename from e2e/sway/contracts/require/src/main.sw rename to e2e/sway/contracts/revert_logs/src/main.sw index 6f33644caf..5f6d096086 100644 --- a/e2e/sway/contracts/require/src/main.sw +++ b/e2e/sway/contracts/revert_logs/src/main.sw @@ -25,6 +25,10 @@ abi TestContract { fn require_string(); fn require_custom_generic(); fn require_with_additional_logs(); + + fn rev_w_log_primitive(); + fn rev_w_log_string(); + fn rev_w_log_custom_generic(); } impl TestContract for Contract { @@ -57,4 +61,28 @@ impl TestContract for Contract { log(__to_str_array("fuel")); require(false, 64); } + + fn rev_w_log_primitive() { + revert_with_log(42); + } + + fn rev_w_log_string() { + revert_with_log(__to_str_array("fuel")); + } + + fn rev_w_log_custom_generic() { + let l: [u8; 3] = [1u8, 2u8, 3u8]; + + let test_enum = EnumWithGeneric::VariantOne(l); + let test_struct_nested = StructWithNestedGeneric { + field_1: test_enum, + field_2: 64, + }; + let test_deeply_nested_generic = StructDeeplyNestedGeneric { + field_1: test_struct_nested, + field_2: 64, + }; + + revert_with_log(test_deeply_nested_generic); + } } diff --git a/e2e/sway/scripts/script_asserts/src/main.sw b/e2e/sway/scripts/script_asserts/src/main.sw index 8ab0c8ad06..898ece135a 100644 --- a/e2e/sway/scripts/script_asserts/src/main.sw +++ b/e2e/sway/scripts/script_asserts/src/main.sw @@ -33,6 +33,9 @@ enum MatchEnum { AssertEqPrimitive: (u64, u64), AssertEqStruct: (TestStruct, TestStruct), AssertEqEnum: (TestEnum, TestEnum), + AssertNePrimitive: (u64, u64), + AssertNeStruct: (TestStruct, TestStruct), + AssertNeEnum: (TestEnum, TestEnum), } fn main(match_enum: MatchEnum) { @@ -46,5 +49,13 @@ fn main(match_enum: MatchEnum) { } else if let MatchEnum::AssertEqEnum((test_enum, test_enum2)) = match_enum { assert_eq(test_enum, test_enum2); + } else if let MatchEnum::AssertNePrimitive((a, b)) = match_enum { + assert_ne(a, b); + } else if let MatchEnum::AssertNeStruct((test_struct, test_struct2)) = match_enum + { + assert_ne(test_struct, test_struct2); + } else if let MatchEnum::AssertNeEnum((test_enum, test_enum2)) = match_enum + { + assert_ne(test_enum, test_enum2); } } diff --git a/e2e/sway/scripts/script_require/Forc.toml b/e2e/sway/scripts/script_revert_logs/Forc.toml similarity index 76% rename from e2e/sway/scripts/script_require/Forc.toml rename to e2e/sway/scripts/script_revert_logs/Forc.toml index 2cfce30dde..fb2c6d9289 100644 --- a/e2e/sway/scripts/script_require/Forc.toml +++ b/e2e/sway/scripts/script_revert_logs/Forc.toml @@ -2,4 +2,4 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" -name = "script_require" +name = "script_revert_logs" diff --git a/e2e/sway/scripts/script_require/src/main.sw b/e2e/sway/scripts/script_revert_logs/src/main.sw similarity index 63% rename from e2e/sway/scripts/script_require/src/main.sw rename to e2e/sway/scripts/script_revert_logs/src/main.sw index 62afeb88ae..4cea809dc1 100644 --- a/e2e/sway/scripts/script_require/src/main.sw +++ b/e2e/sway/scripts/script_revert_logs/src/main.sw @@ -26,6 +26,9 @@ enum MatchEnum { RequireString: (), RequireCustomGeneric: (), RequireWithAdditionalLogs: (), + RevWLogPrimitive: (), + RevWLogString: (), + RevWLogCustomGeneric: (), } fn main(match_enum: MatchEnum) { @@ -51,5 +54,23 @@ fn main(match_enum: MatchEnum) { log(42); log(__to_str_array("fuel")); require(false, 64); + } else if let MatchEnum::RevWLogPrimitive = match_enum { + revert_with_log(42); + } else if let MatchEnum::RevWLogString = match_enum { + revert_with_log(__to_str_array("fuel")); + } else if let MatchEnum::RevWLogCustomGeneric = match_enum { + let l: [u8; 3] = [1u8, 2u8, 3u8]; + + let test_enum = EnumWithGeneric::VariantOne(l); + let test_struct_nested = StructWithNestedGeneric { + field_1: test_enum, + field_2: 64, + }; + let test_deeply_nested_generic = StructDeeplyNestedGeneric { + field_1: test_struct_nested, + field_2: 64, + }; + + revert_with_log(test_deeply_nested_generic); } } diff --git a/e2e/sway/types/contracts/vectors/src/main.sw b/e2e/sway/types/contracts/vectors/src/main.sw index a42ed5c7a4..6ea807bbf8 100644 --- a/e2e/sway/types/contracts/vectors/src/main.sw +++ b/e2e/sway/types/contracts/vectors/src/main.sw @@ -1,6 +1,6 @@ contract; -mod data_structures; +pub mod data_structures; mod eq_impls; mod utils; diff --git a/e2e/sway/types/scripts/script_vectors/src/main.sw b/e2e/sway/types/scripts/script_vectors/src/main.sw index fa19a2f3e3..a277fce3da 100644 --- a/e2e/sway/types/scripts/script_vectors/src/main.sw +++ b/e2e/sway/types/scripts/script_vectors/src/main.sw @@ -1,6 +1,6 @@ script; -mod data_structures; +pub mod data_structures; mod eq_impls; mod utils; diff --git a/e2e/tests/logs.rs b/e2e/tests/logs.rs index f9dc89dabd..664333c0a9 100644 --- a/e2e/tests/logs.rs +++ b/e2e/tests/logs.rs @@ -434,16 +434,16 @@ fn assert_revert_containing_msg(msg: &str, error: Error) { } #[tokio::test] -async fn test_require_log() -> Result<()> { +async fn test_revert_logs() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( - name = "RequireContract", - project = "e2e/sway/contracts/require" + name = "RevertLogsContract", + project = "e2e/sway/contracts/revert_logs" )), Deploy( name = "contract_instance", - contract = "RequireContract", + contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), @@ -472,36 +472,52 @@ async fn test_require_log() -> Result<()> { }; } - reverts_with_msg!(require_primitive, call, "42"); - reverts_with_msg!(require_primitive, simulate, "42"); + { + reverts_with_msg!(require_primitive, call, "42"); + reverts_with_msg!(require_primitive, simulate, "42"); - reverts_with_msg!(require_string, call, "fuel"); - reverts_with_msg!(require_string, simulate, "fuel"); + reverts_with_msg!(require_string, call, "fuel"); + reverts_with_msg!(require_string, simulate, "fuel"); - reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric"); - reverts_with_msg!( - require_custom_generic, - simulate, - "StructDeeplyNestedGeneric" - ); + reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric"); + reverts_with_msg!( + require_custom_generic, + simulate, + "StructDeeplyNestedGeneric" + ); + + reverts_with_msg!(require_with_additional_logs, call, "64"); + reverts_with_msg!(require_with_additional_logs, simulate, "64"); + } + { + reverts_with_msg!(rev_w_log_primitive, call, "42"); + reverts_with_msg!(rev_w_log_primitive, simulate, "42"); - reverts_with_msg!(require_with_additional_logs, call, "64"); - reverts_with_msg!(require_with_additional_logs, simulate, "64"); + reverts_with_msg!(rev_w_log_string, call, "fuel"); + reverts_with_msg!(rev_w_log_string, simulate, "fuel"); + + reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric"); + reverts_with_msg!( + rev_w_log_custom_generic, + simulate, + "StructDeeplyNestedGeneric" + ); + } Ok(()) } #[tokio::test] -async fn test_multi_call_require_log_single_contract() -> Result<()> { +async fn test_multi_call_revert_logs_single_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( - name = "RequireContract", - project = "e2e/sway/contracts/require" + name = "RevertLogsContract", + project = "e2e/sway/contracts/revert_logs" )), Deploy( name = "contract_instance", - contract = "RequireContract", + contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), @@ -513,7 +529,7 @@ async fn test_multi_call_require_log_single_contract() -> Result<()> { // handlers as the script returns the first revert it finds. { let call_handler_1 = contract_methods.require_string(); - let call_handler_2 = contract_methods.require_custom_generic(); + let call_handler_2 = contract_methods.rev_w_log_custom_generic(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) @@ -535,7 +551,7 @@ async fn test_multi_call_require_log_single_contract() -> Result<()> { } { let call_handler_1 = contract_methods.require_custom_generic(); - let call_handler_2 = contract_methods.require_string(); + let call_handler_2 = contract_methods.rev_w_log_string(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) @@ -560,22 +576,22 @@ async fn test_multi_call_require_log_single_contract() -> Result<()> { } #[tokio::test] -async fn test_multi_call_require_log_multi_contract() -> Result<()> { +async fn test_multi_call_revert_logs_multi_contract() -> Result<()> { setup_program_test!( Wallets("wallet"), Abigen(Contract( - name = "RequireContract", - project = "e2e/sway/contracts/require" + name = "RevertLogsContract", + project = "e2e/sway/contracts/revert_logs" )), Deploy( name = "contract_instance", - contract = "RequireContract", + contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), Deploy( name = "contract_instance2", - contract = "RequireContract", + contract = "RevertLogsContract", wallet = "wallet", random_salt = false, ), @@ -588,7 +604,7 @@ async fn test_multi_call_require_log_multi_contract() -> Result<()> { // handlers as the script returns the first revert it finds. { let call_handler_1 = contract_methods.require_string(); - let call_handler_2 = contract_methods2.require_custom_generic(); + let call_handler_2 = contract_methods2.rev_w_log_custom_generic(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) @@ -610,7 +626,7 @@ async fn test_multi_call_require_log_multi_contract() -> Result<()> { } { let call_handler_1 = contract_methods2.require_custom_generic(); - let call_handler_2 = contract_methods.require_string(); + let call_handler_2 = contract_methods.rev_w_log_string(); let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone()) .add_call(call_handler_1) @@ -639,13 +655,13 @@ async fn test_multi_call_require_log_multi_contract() -> Result<()> { async fn test_script_decode_logs() -> Result<()> { // ANCHOR: script_logs abigen!(Script( - name = "log_script", + name = "LogScript", abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json" )); let wallet = launch_provider_and_get_wallet().await?; let bin_path = "sway/logs/script_logs/out/release/script_logs.bin"; - let instance = log_script::new(wallet.clone(), bin_path); + let instance = LogScript::new(wallet.clone(), bin_path); let response = instance.main().call().await?; @@ -911,7 +927,7 @@ async fn test_script_require_log() -> Result<()> { Wallets("wallet"), Abigen(Script( name = "LogScript", - project = "e2e/sway/scripts/script_require" + project = "e2e/sway/scripts/script_revert_logs" )), LoadScript( name = "script_instance", @@ -939,25 +955,45 @@ async fn test_script_require_log() -> Result<()> { }; } - reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42"); - reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42"); + { + reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42"); + reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42"); - reverts_with_msg!(MatchEnum::RequireString, call, "fuel"); - reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel"); + reverts_with_msg!(MatchEnum::RequireString, call, "fuel"); + reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel"); - reverts_with_msg!( - MatchEnum::RequireCustomGeneric, - call, - "StructDeeplyNestedGeneric" - ); - reverts_with_msg!( - MatchEnum::RequireCustomGeneric, - simulate, - "StructDeeplyNestedGeneric" - ); + reverts_with_msg!( + MatchEnum::RequireCustomGeneric, + call, + "StructDeeplyNestedGeneric" + ); + reverts_with_msg!( + MatchEnum::RequireCustomGeneric, + simulate, + "StructDeeplyNestedGeneric" + ); - reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64"); - reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64"); + reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64"); + reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64"); + } + { + reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42"); + reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42"); + + reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel"); + reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel"); + + reverts_with_msg!( + MatchEnum::RevWLogCustomGeneric, + call, + "StructDeeplyNestedGeneric" + ); + reverts_with_msg!( + MatchEnum::RevWLogCustomGeneric, + simulate, + "StructDeeplyNestedGeneric" + ); + } Ok(()) } @@ -1152,7 +1188,16 @@ async fn test_loader_script_require_from_loader_contract() -> Result<()> { } fn assert_assert_eq_containing_msg(left: T, right: T, error: Error) { - let msg = format!("left: `\"{left:?}\"`\n right: `\"{right:?}\"`"); + let msg = format!( + "assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`" + ); + assert_revert_containing_msg(&msg, error) +} + +fn assert_assert_ne_containing_msg(left: T, right: T, error: Error) { + let msg = format!( + "assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`" + ); assert_revert_containing_msg(&msg, error) } @@ -1256,6 +1301,59 @@ async fn test_contract_asserts_log() -> Result<()> { ); } + macro_rules! reverts_with_assert_ne_msg { + (($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => { + let error = contract_instance + .methods() + .$method($($arg,)*) + .call() + .await + .expect_err("should return a revert error"); + assert_assert_ne_containing_msg($($arg,)* error); + } + } + + { + reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed"); + reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed"); + } + { + let test_struct = TestStruct { + field_1: true, + field_2: 64, + }; + + reverts_with_assert_ne_msg!( + (test_struct.clone(), test_struct.clone(),), + assert_ne_struct, + call, + "assertion failed" + ); + + reverts_with_assert_ne_msg!( + (test_struct.clone(), test_struct.clone(),), + assert_ne_struct, + simulate, + "assertion failed" + ); + } + { + let test_enum = TestEnum::VariantOne; + reverts_with_assert_ne_msg!( + (test_enum.clone(), test_enum.clone(),), + assert_ne_enum, + call, + "assertion failed" + ); + + reverts_with_assert_ne_msg!( + (test_enum.clone(), test_enum.clone(),), + assert_ne_enum, + simulate, + "assertion failed" + ); + } + Ok(()) } @@ -1292,7 +1390,7 @@ async fn test_script_asserts_log() -> Result<()> { }; } - macro_rules! reverts_with_assert_eq_msg { + macro_rules! reverts_with_assert_eq_ne_msg { ($arg:expr, call, $msg:expr) => { let error = script_instance .main($arg) @@ -1323,15 +1421,15 @@ async fn test_script_asserts_log() -> Result<()> { ); } { - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqPrimitive((32, 64)), call, - "assertion failed" + "assertion failed: `(left == right)`" ); - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqPrimitive((32, 64)), simulate, - "assertion failed" + "assertion failed: `(left == right)`" ); } { @@ -1344,30 +1442,73 @@ async fn test_script_asserts_log() -> Result<()> { field_1: false, field_2: 32, }; - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)), call, - "assertion failed" + "assertion failed: `(left == right)`" ); - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)), simulate, - "assertion failed" + "assertion failed: `(left == right)`" ); } { let test_enum = TestEnum::VariantOne; let test_enum2 = TestEnum::VariantTwo; - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)), call, - "assertion failed" + "assertion failed: `(left == right)`" ); - reverts_with_assert_eq_msg!( + reverts_with_assert_eq_ne_msg!( MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)), simulate, - "assertion failed" + "assertion failed: `(left == right)`" + ); + } + + { + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNePrimitive((32, 32)), + call, + "assertion failed: `(left != right)`" + ); + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNePrimitive((32, 32)), + simulate, + "assertion failed: `(left != right)`" + ); + } + { + let test_struct = TestStruct { + field_1: true, + field_2: 64, + }; + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)), + call, + "assertion failed: `(left != right)`" + ); + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)), + simulate, + "assertion failed: `(left != right)`" + ); + } + { + let test_enum = TestEnum::VariantOne; + + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)), + call, + "assertion failed: `(left != right)`" + ); + reverts_with_assert_eq_ne_msg!( + MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)), + simulate, + "assertion failed: `(left != right)`" ); } diff --git a/e2e/tests/providers.rs b/e2e/tests/providers.rs index 74d47cc8ce..a56b6d7666 100644 --- a/e2e/tests/providers.rs +++ b/e2e/tests/providers.rs @@ -776,6 +776,34 @@ async fn create_transfer( tb.build(wallet.try_provider()?).await } +#[cfg(feature = "coin-cache")] +#[tokio::test] +async fn transactions_with_the_same_utxo() -> Result<()> { + use fuels::types::errors::transaction; + + let wallet_1 = launch_provider_and_get_wallet().await?; + let provider = wallet_1.provider().unwrap(); + let wallet_2 = WalletUnlocked::new_random(Some(provider.clone())); + + let tx_1 = create_transfer(&wallet_1, 100, wallet_2.address()).await?; + let tx_2 = create_transfer(&wallet_1, 101, wallet_2.address()).await?; + + let _tx_id = provider.send_transaction(tx_1).await?; + let res = provider.send_transaction(tx_2).await; + + let err = res.expect_err("is error"); + + assert!(matches!( + err, + Error::Transaction(transaction::Reason::Validation(..)) + )); + assert!(err + .to_string() + .contains("was submitted recently in a transaction ")); + + Ok(()) +} + #[cfg(feature = "coin-cache")] #[tokio::test] async fn test_caching() -> Result<()> { diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 862b3414fd..eea4cb2ddb 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -165,6 +165,10 @@ impl Provider { &self, tx: T, ) -> Result { + #[cfg(feature = "coin-cache")] + self.check_inputs_already_in_cache(&tx.used_coins(self.base_asset_id())) + .await?; + let tx = self.prepare_transaction_for_sending(tx).await?; let tx_status = self .client @@ -233,9 +237,55 @@ impl Provider { Ok(self.client.submit(&tx.into()).await?) } + #[cfg(feature = "coin-cache")] + async fn find_in_cache<'a>( + &self, + coin_ids: impl IntoIterator)>, + ) -> Option<((Bech32Address, AssetId), CoinTypeId)> { + let mut locked_cache = self.cache.lock().await; + + for (key, ids) in coin_ids { + let items = locked_cache.get_active(key); + + if items.is_empty() { + continue; + } + + for id in ids { + if items.contains(id) { + return Some((key.clone(), id.clone())); + } + } + } + + None + } + + #[cfg(feature = "coin-cache")] + async fn check_inputs_already_in_cache<'a>( + &self, + coin_ids: impl IntoIterator)>, + ) -> Result<()> { + use fuels_core::types::errors::{transaction, Error}; + + if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await { + let msg = match coin_type_id { + CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"), + CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"), + }; + Err(Error::Transaction(transaction::Reason::Validation( + format!("{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`"), + ))) + } else { + Ok(()) + } + } + #[cfg(feature = "coin-cache")] async fn submit(&self, tx: T) -> Result { let used_utxos = tx.used_coins(self.base_asset_id()); + self.check_inputs_already_in_cache(&used_utxos).await?; + let tx_id = self.client.submit(&tx.into()).await?; self.cache.lock().await.insert_multiple(used_utxos); diff --git a/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs b/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs index 6a370d0462..1a8bf5243d 100644 --- a/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs +++ b/packages/fuels-accounts/src/provider/supported_fuel_core_version.rs @@ -1 +1 @@ -pub const SUPPORTED_FUEL_CORE_VERSION: semver::Version = semver::Version::new(0, 40, 0); +pub const SUPPORTED_FUEL_CORE_VERSION: semver::Version = semver::Version::new(0, 40, 1); diff --git a/packages/fuels-core/src/types/tx_status.rs b/packages/fuels-core/src/types/tx_status.rs index 7f90ac478d..c5dff11402 100644 --- a/packages/fuels-core/src/types/tx_status.rs +++ b/packages/fuels-core/src/types/tx_status.rs @@ -1,6 +1,6 @@ use fuel_abi_types::error_codes::{ - FAILED_ASSERT_EQ_SIGNAL, FAILED_ASSERT_SIGNAL, FAILED_REQUIRE_SIGNAL, - FAILED_SEND_MESSAGE_SIGNAL, FAILED_TRANSFER_TO_ADDRESS_SIGNAL, + FAILED_ASSERT_EQ_SIGNAL, FAILED_ASSERT_NE_SIGNAL, FAILED_ASSERT_SIGNAL, FAILED_REQUIRE_SIGNAL, + FAILED_SEND_MESSAGE_SIGNAL, FAILED_TRANSFER_TO_ADDRESS_SIGNAL, REVERT_WITH_LOG_SIGNAL, }; #[cfg(feature = "std")] use fuel_core_client::client::types::TransactionStatus as ClientTransactionStatus; @@ -56,6 +56,9 @@ impl TxStatus { (FAILED_REQUIRE_SIGNAL, Some(log_decoder)) => log_decoder .decode_last_log(receipts) .unwrap_or_else(|err| format!("failed to decode log from require revert: {err}")), + (REVERT_WITH_LOG_SIGNAL, Some(log_decoder)) => log_decoder + .decode_last_log(receipts) + .unwrap_or_else(|err| format!("failed to decode log from revert_with_log: {err}")), (FAILED_ASSERT_EQ_SIGNAL, Some(log_decoder)) => { match log_decoder.decode_last_two_logs(receipts) { Ok((lhs, rhs)) => format!( @@ -66,6 +69,16 @@ impl TxStatus { } } } + (FAILED_ASSERT_NE_SIGNAL, Some(log_decoder)) => { + match log_decoder.decode_last_two_logs(receipts) { + Ok((lhs, rhs)) => format!( + "assertion failed: `(left != right)`\n left: `{lhs:?}`\n right: `{rhs:?}`" + ), + Err(err) => { + format!("failed to decode log from assert_eq revert: {err}") + } + } + } (FAILED_ASSERT_SIGNAL, _) => "assertion failed".into(), (FAILED_SEND_MESSAGE_SIGNAL, _) => "failed to send message".into(), (FAILED_TRANSFER_TO_ADDRESS_SIGNAL, _) => "failed transfer to address".into(),