diff --git a/components/brave_wallet/browser/internal/BUILD.gn b/components/brave_wallet/browser/internal/BUILD.gn index 9b7a65cddee..bbd3c478345 100644 --- a/components/brave_wallet/browser/internal/BUILD.gn +++ b/components/brave_wallet/browser/internal/BUILD.gn @@ -123,6 +123,7 @@ rust_static_library("polkadot_extrinsic_rs") { deps = [ "//brave/third_party/rust/blake2b_simd/v1:lib", + "//brave/third_party/rust/memchr/v2:lib", "//brave/third_party/rust/parity_scale_codec/v3:lib", ] } diff --git a/components/brave_wallet/browser/internal/polkadot_extrinsic.rs b/components/brave_wallet/browser/internal/polkadot_extrinsic.rs index 68f66960f55..59bd8b4e5ad 100644 --- a/components/brave_wallet/browser/internal/polkadot_extrinsic.rs +++ b/components/brave_wallet/browser/internal/polkadot_extrinsic.rs @@ -15,6 +15,17 @@ const MULTIADDRESS_TYPE: u8 = 0x00; const SR25519_SIGNATURE: u8 = 0x01; const PERIOD: u32 = 64; +const PHASE_APPLY_EXTRINSIC: u8 = 0; + +const WITHDRAW_VARIANT_INDEX: u8 = 0x08; + +// transactionpayment(TransactionFeePaid) +const TRANSACTION_FEE_PAID_VARIANT_INDEX: u8 = 0x00; + +// system(ExtrinsicSuccess | ExtrinsicFailed) +const EXTRINSIC_SUCCESS_VARIANT_INDEX: u8 = 0x00; +const EXTRINSIC_FAILED_VARIANT_INDEX: u8 = 0x01; + const UNSIGNED_TRANSFER_ALLOW_DEATH_MIN_LEN: usize = 1 /* extrinsic version */ + 1 /* pallet index */ + 1 /* call index */ @@ -89,6 +100,14 @@ mod ffi { ) -> Vec; fn parse_fee_info(input: &[u8], fee_bytes: &mut [u8; 16]) -> bool; + + fn was_extrinsic_successful( + events: &[u8], + extrinsic_idx: u32, + sender: &[u8; 32], + chain_metadata: &CxxPolkadotChainMetadata, + actual_fee: &mut [u8; 16], + ) -> bool; } } @@ -446,3 +465,160 @@ fn parse_fee_info(input: &[u8], fee_bytes: &mut [u8; 16]) -> bool { fn compact_scale_encode_u32(x: u32) -> Vec { Compact(x).encode() } + +fn was_extrinsic_successful( + events: &[u8], + extrinsic_idx: u32, + sender: &[u8; 32], + chain_metadata: &CxxPolkadotChainMetadata, + actual_fee: &mut [u8; 16], +) -> bool { + /* + For a send transaction, a simplified event flow looks roughly like this: + + ┌─────────────────────────────────┐ + │ balances(Withdraw) │ + └─────────────────────────────────┘ + │ │ + [success] [error] + │ │ + ▼ │ + ┌──────────────────────┐ │ + │ balances(Transfer) │ │ + └──────────────────────┘ │ + │ │ + └──────┬───────┘ + │ + ▼ + ┌───────────────────────────────────┐ + │ balances(Deposit), ... │ + └───────────────────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────┐ + │ transactionpayment(TransactionFeePaid) │ + └──────────────────────────────────────────┘ + │ + ┌────────┴──────────────────────┐ + │ │ + ▼ ▼ + ┌──────────────────────────┐ ┌─────────────────────────┐ + │ system(ExtrinsicSuccess) │ │ system(ExtrinsicFailed) │ + └──────────────────────────┘ └─────────────────────────┘ + */ + + // But in general, it seems like the events flow can become quite complex: + // https://polkadot.subscan.io/extrinsic/30123219-2 + // The thing to note is that the extrinsic always ends with the same two + // events, the fee was paid and the system gave the extrinsic a final status. + // + // In Polkadot, an event is defined as: {phase, event, topics} + // https://github.com/polkadot-js/api/blob/eb34741c871ca8d029a9706ae989ba8ce865db0f/packages/types-support/src/metadata/v15/polkadot-types.json#L519-L542 + // + // Because the events are a massive binary blob that rely on quite a bit of + // Polkadot runtime metadata to fully parse, we just probe for the two events + // for our extrinsic that we care about: the transaction fee paid and the final + // status. We can theoretically probe for everything such as who the fee was + // paid out to but it isn't strictly required for our current needs. + + // We first probe for the balances(Withdraw) event, so that we can use the + // withdrawn fee as a sanity check when we probe for our TransactionFeePaid + // event later on. + let mut withdraw_needle = [0_u8; 39]; + withdraw_needle[0] = PHASE_APPLY_EXTRINSIC; + withdraw_needle[1..5].copy_from_slice(&extrinsic_idx.to_le_bytes()); + withdraw_needle[5] = chain_metadata.balances_pallet_index; + withdraw_needle[6] = WITHDRAW_VARIANT_INDEX; + withdraw_needle[7..39].copy_from_slice(sender); + + // Use `rfind` here because extrinsic blobs can be huge, and our events are + // typically found at the end of the events blob. + let mut events = events; + let Some(needle_idx) = memchr::memmem::rfind(events, &withdraw_needle) else { + return false; + }; + + events = &events[needle_idx + withdraw_needle.len()..]; + + let Ok(withdrawn_fee) = next_n_bytes(&mut events, 16) else { + return false; + }; + + let Ok(topics) = next_n_bytes(&mut events, 1) else { + return false; + }; + + if topics[0] != 0 { + return false; + }; + + // Look for the remainining two events we need, + // transactionpayment(TransactionFeePaid) and system(ExtrinsicSuccess | + // ExtrinsicFailed) + let mut transaction_fee_paid_needle = [0_u8; 39]; + transaction_fee_paid_needle[0] = PHASE_APPLY_EXTRINSIC; + transaction_fee_paid_needle[1..5].copy_from_slice(&extrinsic_idx.to_le_bytes()); + transaction_fee_paid_needle[5] = chain_metadata.transaction_payment_pallet_index; + transaction_fee_paid_needle[6] = TRANSACTION_FEE_PAID_VARIANT_INDEX; + transaction_fee_paid_needle[7..39].copy_from_slice(sender); + + // Use `find` here because we've located the start of our event sequence above. + let Some(needle_idx) = memchr::memmem::find(events, &transaction_fee_paid_needle) else { + return false; + }; + + events = &events[needle_idx + transaction_fee_paid_needle.len()..]; + let Ok(fee) = next_n_bytes(&mut events, 16) else { + return false; + }; + + // If our fees don't match here, we can consider the events blob invalid. + if withdrawn_fee != fee { + return false; + } + + let Ok(_tip) = next_n_bytes(&mut events, 16) else { + return false; + }; + + let Ok(topics) = next_n_bytes(&mut events, 1) else { + return false; + }; + + if topics[0] != 0 { + return false; + }; + + let Ok(phase) = next_n_bytes(&mut events, 1) else { + return false; + }; + + if phase[0] != PHASE_APPLY_EXTRINSIC { + return false; + } + + let Ok(idx) = next_n_bytes(&mut events, 4) else { + return false; + }; + + if idx != &extrinsic_idx.to_le_bytes() { + return false; + } + + let Ok(call_index) = next_n_bytes(&mut events, 2) else { + return false; + }; + + if call_index[0] != chain_metadata.system_pallet_index { + return false; + }; + + if call_index[1] != EXTRINSIC_SUCCESS_VARIANT_INDEX + && call_index[1] != EXTRINSIC_FAILED_VARIANT_INDEX + { + return false; + }; + + actual_fee.copy_from_slice(fee); + call_index[1] == EXTRINSIC_SUCCESS_VARIANT_INDEX +} diff --git a/components/brave_wallet/browser/polkadot/polkadot_extrinsic_unittest.cc b/components/brave_wallet/browser/polkadot/polkadot_extrinsic_unittest.cc index f6eff6b516d..9fe42105a54 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_extrinsic_unittest.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_extrinsic_unittest.cc @@ -821,4 +821,491 @@ TEST(PolkadotExtrinsics, MetadataSerde) { } } +TEST(PolkadotExtrinsics, EventsParsing) { + // This event comes from: + // https://polkadot.subscan.io/extrinsic/30267458-2 + // Note that because the entire events blob for a block is ~12 kB, we choose + // to only include a subset for this test. + + std::array sender = {}; + const char sender_hex[] = + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db"; + ASSERT_TRUE(base::HexStringToSpan(sender_hex, sender)); + + auto chain_metadata = + PolkadotChainMetadata::FromChainName("Polkadot").value(); + + const uint32_t extrinsic_idx = 2; + + const char events_hex[] = + // balances(Withdraw) + "0002000000" + "0508" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00" + // balances(Transfer) + "0002000000" + "0502" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5d70f7105a51be4a5afd2f10377d9bec9b8cdb971d6e8c436630f236a805926e" + "a1d0724a020000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18a9ad07000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0002000000" + "2000" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicSuccess) + "0002000000" + "0000" + "a2e910976da80000" + "00"; + + std::vector events; + ASSERT_TRUE(base::HexStringToBytes(events_hex, &events)); + + std::array actual_fee_bytes = {}; + + EXPECT_TRUE(was_extrinsic_successful(rust::Slice(events), + extrinsic_idx, sender, *chain_metadata, + actual_fee_bytes)); + + EXPECT_EQ(base::bit_cast(actual_fee_bytes), uint128_t{161026911}); + EXPECT_EQ(base::HexEncodeLower(actual_fee_bytes), + "5f139909000000000000000000000000"); +} + +TEST(PolkadotExtrinsics, EventsParsing_WithAccountCreation) { + // This event comes from: + // https://polkadot.subscan.io/extrinsic/30123219-2 + + std::array sender = {}; + const char sender_hex[] = + "2a27dd26f5f3fe4f48fc67cddb54a8cdb0f3c6e4b9c8cf751a59466771dc6144"; + ASSERT_TRUE(base::HexStringToSpan(sender_hex, sender)); + + auto chain_metadata = + PolkadotChainMetadata::FromChainName("Polkadot").value(); + + uint32_t extrinsic_idx = 2; + + const char events_hex[] = + // balances(Withdraw) + "0002000000" + "0508" + "2a27dd26f5f3fe4f48fc67cddb54a8cdb0f3c6e4b9c8cf751a59466771dc6144" + "5f139909000000000000000000000000" + "00" + // system(NewAccount) + "0002000000" + "0003" + "70617261550d0000000000000000000000000000000000000000000000000000" + "00" + // balances(Endowed) + "0002000000" + "0500" + "70617261550d0000000000000000000000000000000000000000000000000000" + "00d8bc7ced0000000000000000000000" + "00" + // balances(Transfer) + "0002000000" + "0502" + "2a27dd26f5f3fe4f48fc67cddb54a8cdb0f3c6e4b9c8cf751a59466771dc6144" + "70617261550d0000000000000000000000000000000000000000000000000000" + "00d8bc7ced0000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18a9ad07000000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "d8837440d77698a5ac63985587594e45d0029538f9b413495621913a68f64941" + "476aeb01000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0002000000" + "2000" + "2a27dd26f5f3fe4f48fc67cddb54a8cdb0f3c6e4b9c8cf751a59466771dc6144" + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicSuccess) + "0002000000" + "0000" + "a2e910976da80000" + "00"; + + std::vector events; + ASSERT_TRUE(base::HexStringToBytes(events_hex, &events)); + + std::array actual_fee_bytes = {}; + + EXPECT_TRUE(was_extrinsic_successful(rust::Slice(events), + extrinsic_idx, sender, *chain_metadata, + actual_fee_bytes)); + + EXPECT_EQ(base::bit_cast(actual_fee_bytes), uint128_t{161026911}); + EXPECT_EQ(base::HexEncodeLower(actual_fee_bytes), + "5f139909000000000000000000000000"); +} + +TEST(PolkadotExtrinsics, EventsParsing_FailedExtrinsic_ArithmeticUnderflow) { + // This event comes from: + // https://polkadot.subscan.io/extrinsic/29943577-2 + + std::array sender = {}; + const char sender_hex[] = + "d44c4639d57190aed08f053cac6db1c85221253e7353d484dba9caa663d86a5f"; + ASSERT_TRUE(base::HexStringToSpan(sender_hex, sender)); + + auto chain_metadata = + PolkadotChainMetadata::FromChainName("Polkadot").value(); + + uint32_t extrinsic_idx = 2; + + const char events_hex[] = + // balances(Withdraw) + "0002000000" + "0508" + "d44c4639d57190aed08f053cac6db1c85221253e7353d484dba9caa663d86a5f" + "9f5ee509000000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18b2ea07000000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "e08785d4123f656862a5fd4b2286ae30ab1ebf17f60538961d3f91f02c73ee91" + "87acfa01000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0002000000" + "2000" + "d44c4639d57190aed08f053cac6db1c85221253e7353d484dba9caa663d86a5f" + "9f5ee509000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicFailed) + "0002000000" + "0001" + "0800a2e910976da80000" + "00"; + + std::vector events; + ASSERT_TRUE(base::HexStringToBytes(events_hex, &events)); + + std::array actual_fee_bytes = {}; + + EXPECT_FALSE(was_extrinsic_successful(rust::Slice(events), + extrinsic_idx, sender, *chain_metadata, + actual_fee_bytes)); + + EXPECT_EQ(base::bit_cast(actual_fee_bytes), uint128_t{166026911}); + EXPECT_EQ(base::HexEncodeLower(actual_fee_bytes), + "9f5ee509000000000000000000000000"); +} + +TEST(PolkadotExtrinsics, EventsParsing_FailedExtrinsic_BelowMinimum) { + // This event comes from: + // https://polkadot.subscan.io/extrinsic/29509101-2 + + std::array sender = {}; + const char sender_hex[] = + "3c67dd0ea1126b09609ac341b4417251457f0fad467b8e1d3004209d4756ea2e"; + ASSERT_TRUE(base::HexStringToSpan(sender_hex, sender)); + + auto chain_metadata = + PolkadotChainMetadata::FromChainName("Polkadot").value(); + + uint32_t extrinsic_idx = 2; + + const char events_hex[] = + // balances(Withdraw) + "0002000000" + "0508" + "3c67dd0ea1126b09609ac341b4417251457f0fad467b8e1d3004209d4756ea2e" + "5f139909000000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18a9ad07000000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "c5e80accf4092ea6f8ed087544576dddcfdd51366b492868f73b0c9ca19c5f31" + "476aeb01000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0002000000" + "2000" + "3c67dd0ea1126b09609ac341b4417251457f0fad467b8e1d3004209d4756ea2e" + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicFailed) + "0002000000" + "0001" + "0702a2e910976da80000" + "00"; + + std::vector events; + ASSERT_TRUE(base::HexStringToBytes(events_hex, &events)); + + std::array actual_fee_bytes = {}; + + EXPECT_FALSE(was_extrinsic_successful(rust::Slice(events), + extrinsic_idx, sender, *chain_metadata, + actual_fee_bytes)); + + EXPECT_EQ(base::bit_cast(actual_fee_bytes), uint128_t{161026911}); + EXPECT_EQ(base::HexEncodeLower(actual_fee_bytes), + "5f139909000000000000000000000000"); +} + +TEST(PolkadotExtrinsics, EventsParsing_Error) { + const char sender_hex[] = + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db"; + + std::array sender = {}; + + ASSERT_TRUE(base::HexStringToSpan(sender_hex, sender)); + + auto chain_metadata = + PolkadotChainMetadata::FromChainName("Polkadot").value(); + + uint32_t extrinsic_idx = 2; + + const std::string valid_events = + // balances(Withdraw) + "0002000000" + "0508" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00" + // balances(Transfer) + "0002000000" + "0502" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5d70f7105a51be4a5afd2f10377d9bec9b8cdb971d6e8c436630f236a805926e" + "a1d0724a020000000000000000000000" + "00" + // balances(Deposit) + "0002000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18a9ad07000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0002000000" + "2000" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicSuccess) + "0002000000" + "0000" + "a2e910976da80000" + "00"; + + std::vector inputs; + + // Incorrect balances(Withdraw). + { + std::string bad_withdraw_event_prefix = valid_events; + auto n = bad_withdraw_event_prefix.find("00020000000508"); + bad_withdraw_event_prefix[n] = '1'; + inputs.push_back(std::move(bad_withdraw_event_prefix)); + } + { + std::string bad_withdraw_event_prefix = valid_events; + auto n = bad_withdraw_event_prefix.find("00020000000508"); + bad_withdraw_event_prefix.erase(n + 5, 2); + inputs.push_back(std::move(bad_withdraw_event_prefix)); + } + + // Incorrect transactionpayment(TransactionFeePaid). + { + std::string bad_fee_paid_event_prefix = valid_events; + auto n = bad_fee_paid_event_prefix.find("00020000002000"); + bad_fee_paid_event_prefix[n] = '1'; + inputs.push_back(std::move(bad_fee_paid_event_prefix)); + } + { + std::string bad_fee_paid_event_prefix = valid_events; + auto n = bad_fee_paid_event_prefix.find("00020000002000"); + bad_fee_paid_event_prefix.erase(n + 5, 2); + inputs.push_back(std::move(bad_fee_paid_event_prefix)); + } + + // Incorrect system(ExtrinsicSuccess). + { + std::string bad_extrinsic_success_event_prefix = valid_events; + auto n = bad_extrinsic_success_event_prefix.find("00020000000000"); + bad_extrinsic_success_event_prefix[n] = '1'; + inputs.push_back(std::move(bad_extrinsic_success_event_prefix)); + } + { + std::string bad_extrinsic_success_event_prefix = valid_events; + auto n = bad_extrinsic_success_event_prefix.find("00020000000000"); + bad_extrinsic_success_event_prefix.erase(n + 5, 2); + inputs.push_back(std::move(bad_extrinsic_success_event_prefix)); + } + + // Incorrect sender in balances withdraw. + { + std::string bad_sender_transfer = valid_events; + auto n = bad_sender_transfer.find(sender_hex); + bad_sender_transfer[n] = '0'; + inputs.push_back(std::move(bad_sender_transfer)); + } + + // Incorrect sender in transaction fee paid. + { + std::string bad_sender_transfer = valid_events; + auto n = bad_sender_transfer.find(sender_hex); + n = bad_sender_transfer.find(sender_hex, n + 64); + n = bad_sender_transfer.find(sender_hex, n + 64); + bad_sender_transfer[n] = '0'; + inputs.push_back(std::move(bad_sender_transfer)); + } + + // Incorrect topics for withdrawal. + { + std::string needle = + "5f139909000000000000000000000000" + "00" + "0002000000"; + + std::string bad_fee_paid_topics = valid_events; + auto n = bad_fee_paid_topics.find(needle); + bad_fee_paid_topics[n + needle.size() - + std::string_view("0002000000").size() - 2] = '1'; + + inputs.push_back(std::move(bad_fee_paid_topics)); + } + + // Incorrect topics for fee paid. + { + std::string needle = + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00"; + + std::string bad_fee_paid_topics = valid_events; + auto n = bad_fee_paid_topics.find(needle); + bad_fee_paid_topics[n + needle.size() - 2] = '1'; + + inputs.push_back(std::move(bad_fee_paid_topics)); + } + + // Extrinsic indexes don't match. + { + std::string invalid_event = + // balances(Withdraw) + "0003000000" + "0508" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00" + // balances(Transfer) + "0003000000" + "0502" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5d70f7105a51be4a5afd2f10377d9bec9b8cdb971d6e8c436630f236a805926e" + "a1d0724a020000000000000000000000" + "00" + // balances(Deposit) + "0003000000" + "0507" + "6d6f646c70792f74727372790000000000000000000000000000000000000000" + "18a9ad07000000000000000000000000" + "00" + // transactionpayment(TransactionFeePaid) + "0003000000" + "2000" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00" + // system(ExtrinsicSuccess) + "0003000000" + "0000" + "a2e910976da80000" + "00"; + + inputs.push_back(std::move(invalid_event)); + } + + // Empty string. + { + inputs.push_back(""); + } + + // Truncated TransactionFeePaid. + { + std::string needle = + "5f139909000000000000000000000000" + "00000000000000000000000000000000" + "00"; + + std::string truncated = valid_events; + auto n = truncated.find(needle); + truncated.erase(n); + + inputs.push_back(std::move(truncated)); + } + + // Withdrawal doesn't match transaction fee paid. + { + std::string needle = + "0508" + "bf0be0352ca5bc12a8ac6cf0006e220e5c55bb03126890ad37ce9753f9b3e3db" + "5f139909000000000000000000000000"; + + std::string mismatched_withdrawal = valid_events; + auto n = mismatched_withdrawal.find(needle); + mismatched_withdrawal[n + 4 + 64 + 1] = '6'; + + inputs.push_back(std::move(mismatched_withdrawal)); + } + + ASSERT_FALSE(inputs.empty()); + for (const auto& input : inputs) { + std::vector events; + if (!input.empty()) { + ASSERT_TRUE(base::HexStringToBytes(input, &events)); + } + + std::array actual_fee_bytes = {}; + + EXPECT_FALSE(was_extrinsic_successful(rust::Slice(events), + extrinsic_idx, sender, + *chain_metadata, actual_fee_bytes)) + << input; + + EXPECT_EQ(base::bit_cast(actual_fee_bytes), uint128_t{0}); + } +} + } // namespace brave_wallet diff --git a/components/brave_wallet/common/Cargo.toml b/components/brave_wallet/common/Cargo.toml index cced8b6074c..5b5c80e526a 100644 --- a/components/brave_wallet/common/Cargo.toml +++ b/components/brave_wallet/common/Cargo.toml @@ -15,6 +15,7 @@ schnorrkel = "0.11.4" ciborium = "0.2.2" parity-scale-codec = "3.7.5" crc32fast = "1.5.0" +memchr = "2.7.6" [lib] name = "brave_wallet" diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock index ab2aa569eca..210757ee808 100644 --- a/third_party/rust/chromium_crates_io/Cargo.lock +++ b/third_party/rust/chromium_crates_io/Cargo.lock @@ -268,6 +268,7 @@ dependencies = [ "crc32fast", "curve25519-dalek", "cxx", + "memchr", "parity-scale-codec", "schnorrkel", ]