diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index 24cac1a38b1..0a51edb6fd9 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -771,6 +771,7 @@ source_set("unit_tests") { "polkadot/polkadot_keyring_unittest.cc", "polkadot/polkadot_metadata_provider_unittest.cc", "polkadot/polkadot_substrate_rpc_unittest.cc", + "polkadot/polkadot_test_utils_unittest.cc", "polkadot/polkadot_transaction_status_task_unittest.cc", "polkadot/polkadot_transaction_unittest.cc", "polkadot/polkadot_tx_manager_unittest.cc", diff --git a/components/brave_wallet/browser/internal/polkadot_chain_metadata.rs b/components/brave_wallet/browser/internal/polkadot_chain_metadata.rs index 2c6c49a72ce..ecc596611e8 100644 --- a/components/brave_wallet/browser/internal/polkadot_chain_metadata.rs +++ b/components/brave_wallet/browser/internal/polkadot_chain_metadata.rs @@ -196,10 +196,9 @@ fn parse_pallet(input: &mut &[u8], has_pallet_docs: bool) -> Result - let _storage = decode_option(input, |input| { + decode_option(input, |input| { let _: String = decode_scale(input)?; // prefix - let _ = decode_vec(input, parse_storage_entry)?; - Ok(()) + decode_vec(input, parse_storage_entry) })?; // calls: Option @@ -356,6 +355,8 @@ fn parse_extrinsic_metadata(input: &mut &[u8], version: u8) -> Result Result Result Vec; + fn scale_encode_string(value: &[u8]) -> Vec; type CxxPolkadotDecodeUnsignedTransferResult; type CxxPolkadotChainMetadataResult; @@ -521,6 +526,10 @@ fn compact_scale_encode_u32(x: u32) -> Vec { Compact(x).encode() } +fn scale_encode_string(value: &[u8]) -> Vec { + value.encode() +} + fn was_extrinsic_successful( events: &[u8], extrinsic_idx: u32, diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.cc b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.cc index a5b79b87f3c..1ed8f8fdfe0 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.cc @@ -48,15 +48,23 @@ PolkadotChainMetadata PolkadotChainMetadata::FromFields( uint8_t transfer_all_call_index, uint16_t ss58_prefix, uint32_t spec_version, - bool asset_tx_payment) { + bool asset_tx_payment, + bool has_assets_pallet, + uint8_t assets_pallet_index, + uint8_t assets_transfer_all_call_index, + uint8_t assets_transfer_keep_alive_call_index) { PolkadotChainMetadata metadata; - metadata->system_pallet_index = system_pallet_index; metadata->balances_pallet_index = balances_pallet_index; metadata->transaction_payment_pallet_index = transaction_payment_pallet_index; metadata->transfer_allow_death_call_index = transfer_allow_death_call_index; metadata->transfer_keep_alive_call_index = transfer_keep_alive_call_index; metadata->transfer_all_call_index = transfer_all_call_index; + metadata->assets_pallet_index = assets_pallet_index; + metadata->assets_transfer_all_call_index = assets_transfer_all_call_index; + metadata->assets_transfer_keep_alive_call_index = + assets_transfer_keep_alive_call_index; + metadata->has_assets_pallet = has_assets_pallet; metadata->ss58_prefix = ss58_prefix; metadata->spec_version = spec_version; metadata->asset_tx_payment = asset_tx_payment; diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h index 44f56ea884f..9f6343fb6d3 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h @@ -47,7 +47,11 @@ class PolkadotChainMetadata { uint8_t transfer_all_call_index, uint16_t ss58_prefix, uint32_t spec_version, - bool asset_tx_payment); + bool asset_tx_payment, + bool has_assets_pallet, + uint8_t assets_pallet_index, + uint8_t assets_transfer_all_call_index, + uint8_t assets_transfer_keep_alive_call_index); // Obtain a reference to the underlying opaque type so that it can be passed // to Rust routines. diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.cc b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.cc index 185a7dabf08..f661e746b5b 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.cc @@ -24,6 +24,11 @@ constexpr char kTransferAllowDeathCallIndex[] = "transfer_allow_death_call_index"; constexpr char kTransferKeepAliveCallIndex[] = "transfer_keep_alive_call_index"; constexpr char kTransferAllCallIndex[] = "transfer_all_call_index"; +constexpr char kHasAssetsPallet[] = "has_assets_pallet"; +constexpr char kAssetsPalletIndex[] = "assets_pallet_index"; +constexpr char kAssetsTransferAllCallIndex[] = "assets_transfer_all_call_index"; +constexpr char kAssetsTransferKeepAliveCallIndex[] = + "assets_transfer_keep_alive_call_index"; constexpr char kAssetTxPayment[] = "asset_tx_payment"; constexpr char kSs58Prefix[] = "ss58_prefix"; constexpr char kSpecVersion[] = "spec_version"; @@ -39,6 +44,10 @@ constexpr char kVersionField[] = "version"; // "transfer_allow_death_call_index": int, // u8 // "transfer_keep_alive_call_index": int, // u8 // "transfer_all_call_index": int, // u8 +// "has_assets_pallet": bool, // bool +// "assets_pallet_index": int, // u8 +// "assets_transfer_all_call_index": int, // u8 +// "assets_transfer_keep_alive_call_index": int, // u8 // "asset_tx_payment": bool, // bool // "ss58_prefix": int, // u16 // "spec_version": int // u32 @@ -128,6 +137,26 @@ PolkadotChainMetadataPrefs::GetChainMetadata(std::string_view chain_id) const { return std::nullopt; } + if (!ReadBool(*chain_metadata, kHasAssetsPallet, + &metadata->has_assets_pallet)) { + return std::nullopt; + } + + if (!ReadUint(*chain_metadata, kAssetsPalletIndex, + &metadata->assets_pallet_index)) { + return std::nullopt; + } + + if (!ReadUint(*chain_metadata, kAssetsTransferAllCallIndex, + &metadata->assets_transfer_all_call_index)) { + return std::nullopt; + } + + if (!ReadUint(*chain_metadata, kAssetsTransferKeepAliveCallIndex, + &metadata->assets_transfer_keep_alive_call_index)) { + return std::nullopt; + } + if (!ReadBool(*chain_metadata, kAssetTxPayment, &metadata->asset_tx_payment)) { return std::nullopt; @@ -154,6 +183,9 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata( int transfer_allow_death_call_index = 0; int transfer_keep_alive_call_index = 0; int transfer_all_call_index = 0; + int assets_pallet_index = 0; + int assets_transfer_all_call_index = 0; + int assets_transfer_keep_alive_call_index = 0; int ss58_prefix = 0; int spec_version = 0; if (!base::CheckedNumeric(metadata->system_pallet_index) @@ -168,6 +200,13 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata( .AssignIfValid(&transfer_keep_alive_call_index) || !base::CheckedNumeric(metadata->transfer_all_call_index) .AssignIfValid(&transfer_all_call_index) || + !base::CheckedNumeric(metadata->assets_pallet_index) + .AssignIfValid(&assets_pallet_index) || + !base::CheckedNumeric(metadata->assets_transfer_all_call_index) + .AssignIfValid(&assets_transfer_all_call_index) || + !base::CheckedNumeric( + metadata->assets_transfer_keep_alive_call_index) + .AssignIfValid(&assets_transfer_keep_alive_call_index) || !base::CheckedNumeric(metadata->ss58_prefix) .AssignIfValid(&ss58_prefix) || !base::CheckedNumeric(metadata->spec_version) @@ -181,6 +220,11 @@ bool PolkadotChainMetadataPrefs::SetChainMetadata( value.Set(kTransferAllowDeathCallIndex, transfer_allow_death_call_index); value.Set(kTransferKeepAliveCallIndex, transfer_keep_alive_call_index); value.Set(kTransferAllCallIndex, transfer_all_call_index); + value.Set(kHasAssetsPallet, metadata->has_assets_pallet); + value.Set(kAssetsPalletIndex, assets_pallet_index); + value.Set(kAssetsTransferAllCallIndex, assets_transfer_all_call_index); + value.Set(kAssetsTransferKeepAliveCallIndex, + assets_transfer_keep_alive_call_index); value.Set(kAssetTxPayment, metadata->asset_tx_payment); value.Set(kSs58Prefix, ss58_prefix); value.Set(kSpecVersion, spec_version); diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.h b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.h index 6bbff1f2f98..6989ac2a04b 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.h +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs.h @@ -18,7 +18,7 @@ namespace brave_wallet { class PolkadotChainMetadataPrefs { public: - static constexpr int kVersion = 1; + static constexpr int kVersion = 2; explicit PolkadotChainMetadataPrefs(PrefService& profile_prefs); virtual ~PolkadotChainMetadataPrefs(); diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs_unittest.cc b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs_unittest.cc index de728a1e76a..aa7360e56ae 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs_unittest.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_prefs_unittest.cc @@ -24,6 +24,11 @@ constexpr char kTransferAllowDeathCallIndex[] = "transfer_allow_death_call_index"; constexpr char kTransferKeepAliveCallIndex[] = "transfer_keep_alive_call_index"; constexpr char kTransferAllCallIndex[] = "transfer_all_call_index"; +constexpr char kHasAssetsPallet[] = "has_assets_pallet"; +constexpr char kAssetsPalletIndex[] = "assets_pallet_index"; +constexpr char kAssetsTransferAllCallIndex[] = "assets_transfer_all_call_index"; +constexpr char kAssetsTransferKeepAliveCallIndex[] = + "assets_transfer_keep_alive_call_index"; constexpr char kAssetTxPayment[] = "asset_tx_payment"; constexpr char kSs58Prefix[] = "ss58_prefix"; constexpr char kVersionField[] = "version"; @@ -48,6 +53,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) { uint8_t transfer_allow_death_call_index; uint8_t transfer_keep_alive_call_index; uint8_t transfer_all_call_index; + bool has_assets_pallet; + uint8_t assets_pallet_index; + uint8_t assets_transfer_all_call_index; + uint8_t assets_transfer_keep_alive_call_index; uint16_t ss58_prefix; uint32_t spec_version; bool asset_tx_payment; @@ -60,6 +69,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) { /*transfer_allow_death_call_index=*/2, /*transfer_keep_alive_call_index=*/4, /*transfer_all_call_index=*/5, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0, /*ss58_prefix=*/42, /*spec_version=*/1234, /*asset_tx_payment=*/false}, {"AssetHub Polkadot Metadata", @@ -68,6 +81,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) { /*transfer_allow_death_call_index=*/2, /*transfer_keep_alive_call_index=*/4, /*transfer_all_call_index=*/255, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/10, + /*assets_transfer_keep_alive_call_index=*/9, /*ss58_prefix=*/std::numeric_limits::max(), /*spec_version=*/12344321, /*asset_tx_payment=*/true}}; @@ -78,7 +95,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, SetAndGetChainMetadataRoundTrip) { tc.system_pallet_index, tc.balances_pallet_index, tc.transaction_payment_pallet_index, tc.transfer_allow_death_call_index, tc.transfer_keep_alive_call_index, tc.transfer_all_call_index, - tc.ss58_prefix, tc.spec_version, tc.asset_tx_payment); + tc.ss58_prefix, tc.spec_version, tc.asset_tx_payment, + tc.has_assets_pallet, tc.assets_pallet_index, + tc.assets_transfer_all_call_index, + tc.assets_transfer_keep_alive_call_index); PolkadotChainMetadataPrefs prefs = MakePrefs(); EXPECT_TRUE(prefs.SetChainMetadata(mojom::kPolkadotMainnet, metadata)); @@ -111,6 +131,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, InvalidRangeRejected) { value.Set(kTransferAllowDeathCallIndex, 1); value.Set(kTransferKeepAliveCallIndex, 1); value.Set(kTransferAllCallIndex, 1); + value.Set(kHasAssetsPallet, false); + value.Set(kAssetsPalletIndex, 0); + value.Set(kAssetsTransferAllCallIndex, 0); + value.Set(kAssetsTransferKeepAliveCallIndex, 0); value.Set(kAssetTxPayment, false); value.Set(kSs58Prefix, 0); value.Set(kSpecVersion, 100); @@ -133,6 +157,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, NegativeValueRejected) { -1); // Negative should be rejected. value.Set(kTransferKeepAliveCallIndex, 1); value.Set(kTransferAllCallIndex, 1); + value.Set(kHasAssetsPallet, false); + value.Set(kAssetsPalletIndex, 0); + value.Set(kAssetsTransferAllCallIndex, 0); + value.Set(kAssetsTransferKeepAliveCallIndex, 0); value.Set(kAssetTxPayment, false); value.Set(kSs58Prefix, 0); value.Set(kSpecVersion, 100); @@ -154,6 +182,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, MissingRequiredFieldRejected) { value.Set(kTransferAllowDeathCallIndex, 1); value.Set(kTransferKeepAliveCallIndex, 4); value.Set(kTransferAllCallIndex, 1); + value.Set(kHasAssetsPallet, false); + value.Set(kAssetsPalletIndex, 0); + value.Set(kAssetsTransferAllCallIndex, 0); + value.Set(kAssetsTransferKeepAliveCallIndex, 0); value.Set(kAssetTxPayment, false); value.Set(kSs58Prefix, 42); // Missing spec version should reject persisted value. @@ -175,6 +207,10 @@ TEST_F(PolkadotChainMetadataPrefsUnitTest, MissingRequiredFieldRejected) { value.Set(kTransferAllowDeathCallIndex, 1); value.Set(kTransferKeepAliveCallIndex, 4); value.Set(kTransferAllCallIndex, 1); + value.Set(kHasAssetsPallet, false); + value.Set(kAssetsPalletIndex, 0); + value.Set(kAssetsTransferAllCallIndex, 0); + value.Set(kAssetsTransferKeepAliveCallIndex, 0); value.Set(kSs58Prefix, 42); value.Set(kSpecVersion, 1234); update->Set(mojom::kPolkadotMainnet, std::move(value)); diff --git a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_unittest.cc b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_unittest.cc index e8a460e2169..6cc72c09d7c 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_unittest.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_chain_metadata_unittest.cc @@ -5,11 +5,11 @@ #include "brave/components/brave_wallet/browser/polkadot/polkadot_chain_metadata.h" +#include +#include #include -#include "base/base_paths.h" -#include "base/path_service.h" -#include "base/test/values_test_util.h" +#include "base/containers/span.h" #include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h" #include "brave/components/brave_wallet/common/hex_utils.h" #include "testing/gtest/include/gtest/gtest.h" @@ -18,7 +18,11 @@ namespace brave_wallet { namespace { -constexpr char kResult[] = "result"; +std::string ScaleEncodeString(std::string_view value) { + auto bytes = base::as_byte_span(value); + auto encoded = scale_encode_string(::rust::Slice(bytes)); + return std::string(encoded.begin(), encoded.end()); +} } // namespace @@ -30,7 +34,11 @@ TEST(PolkadotChainMetadataUnitTest, FromFields) { /*transfer_keep_alive_call_index=*/4, /*transfer_all_call_index=*/5, /*ss58_prefix=*/42, /*spec_version=*/1'234'567, - /*asset_tx_payment=*/true); + /*asset_tx_payment=*/true, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/10, + /*assets_transfer_keep_alive_call_index=*/9); EXPECT_EQ(metadata->system_pallet_index, 0u); EXPECT_EQ(metadata->balances_pallet_index, 7u); @@ -38,6 +46,10 @@ TEST(PolkadotChainMetadataUnitTest, FromFields) { EXPECT_EQ(metadata->transfer_allow_death_call_index, 2u); EXPECT_EQ(metadata->transfer_keep_alive_call_index, 4u); EXPECT_EQ(metadata->transfer_all_call_index, 5u); + EXPECT_TRUE(metadata->has_assets_pallet); + EXPECT_EQ(metadata->assets_pallet_index, 50u); + EXPECT_EQ(metadata->assets_transfer_all_call_index, 10u); + EXPECT_EQ(metadata->assets_transfer_keep_alive_call_index, 9u); EXPECT_EQ(metadata->ss58_prefix, 42u); EXPECT_EQ(metadata->spec_version, 1'234'567u); EXPECT_TRUE(metadata->asset_tx_payment); @@ -51,7 +63,11 @@ TEST(PolkadotChainMetadataUnitTest, EqualityOperator) { /*transfer_keep_alive_call_index=*/4, /*transfer_all_call_index=*/5, /*ss58_prefix=*/42, /*spec_version=*/1'234'567, - /*asset_tx_payment=*/false); + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0); PolkadotChainMetadata metadata_b = metadata_a; @@ -61,9 +77,23 @@ TEST(PolkadotChainMetadataUnitTest, EqualityOperator) { PolkadotChainMetadata metadata_d = metadata_a; metadata_d->asset_tx_payment = true; + auto metadata_e = PolkadotChainMetadata::FromFields( + /*system_pallet_index=*/0, /*balances_pallet_index=*/7, + /*transaction_payment_pallet_index=*/0x20, + /*transfer_allow_death_call_index=*/2, + /*transfer_keep_alive_call_index=*/4, + /*transfer_all_call_index=*/5, + /*ss58_prefix=*/42, /*spec_version=*/1'234'567, + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/10, + /*assets_transfer_keep_alive_call_index=*/9); + EXPECT_EQ(metadata_a, metadata_b); EXPECT_NE(metadata_a, metadata_c); EXPECT_NE(metadata_a, metadata_d); + EXPECT_NE(metadata_a, metadata_e); } TEST(PolkadotChainMetadataUnitTest, FromBytesInvalid) { @@ -77,29 +107,46 @@ TEST(PolkadotChainMetadataUnitTest, FromBytesInvalid) { EXPECT_FALSE(PolkadotChainMetadata::FromBytes(invalid_short)); } +TEST(PolkadotChainMetadataUnitTest, + ParseMetadataWithoutAssetsPalletLeavesAssetsFieldsUnset) { + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_polkadot.json"); + ASSERT_FALSE(metadata_bytes.empty()); + + auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes); + ASSERT_TRUE(metadata); + + EXPECT_FALSE((*metadata)->has_assets_pallet); + EXPECT_EQ((*metadata)->assets_pallet_index, 0u); + EXPECT_EQ((*metadata)->assets_transfer_all_call_index, 0u); + EXPECT_EQ((*metadata)->assets_transfer_keep_alive_call_index, 0u); +} + +TEST(PolkadotChainMetadataUnitTest, + ParseMetadataWithAssetsPalletMissingMethodFails) { + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_assethub_polkadot.json"); + ASSERT_FALSE(metadata_bytes.empty()); + + // The second transfer_keep_alive occurrence is the Assets pallet call + // variant; the first belongs to Balances. + const std::string transfer_keep_alive = + ScaleEncodeString("transfer_keep_alive"); + const std::string transfer_dead_alive = + ScaleEncodeString("transfer_dead_alive"); + ASSERT_TRUE(ReplaceNthOccurrence(metadata_bytes, transfer_keep_alive, + transfer_dead_alive, /*occurrence=*/1)); + + EXPECT_FALSE(PolkadotChainMetadata::FromBytes(metadata_bytes)); +} + TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponsePolkadot) { // Refreshed with: // curl -sS -H 'Content-Type: application/json' \ // -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \ // https://rpc.polkadot.io/ - const auto fixture_path = - base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) - .AppendASCII("brave") - .AppendASCII("components") - .AppendASCII("test") - .AppendASCII("data") - .AppendASCII("brave_wallet") - .AppendASCII("polkadot") - .AppendASCII("chain_metadata") - .AppendASCII("state_getMetadata_polkadot.json"); - const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path); - - const std::string* metadata_hex = json.FindString(kResult); - ASSERT_TRUE(metadata_hex); - ASSERT_FALSE(metadata_hex->empty()); - - std::vector metadata_bytes; - ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes)); + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_polkadot.json"); auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes); ASSERT_TRUE(metadata); @@ -111,7 +158,11 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponsePolkadot) { /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/0, /*spec_version=*/2'000'007, - /*asset_tx_payment=*/false); + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0); EXPECT_EQ(*metadata, expected); auto metadata2 = PolkadotMetadataFromChainName("Polkadot"); @@ -127,24 +178,8 @@ TEST(PolkadotChainMetadataUnitTest, // curl -sS -H 'Content-Type: application/json' \ // -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \ // https://polkadot-asset-hub-rpc.polkadot.io - const auto fixture_path = - base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) - .AppendASCII("brave") - .AppendASCII("components") - .AppendASCII("test") - .AppendASCII("data") - .AppendASCII("brave_wallet") - .AppendASCII("polkadot") - .AppendASCII("chain_metadata") - .AppendASCII("state_getMetadata_assethub_polkadot.json"); - const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path); - - const std::string* metadata_hex = json.FindString(kResult); - ASSERT_TRUE(metadata_hex); - ASSERT_FALSE(metadata_hex->empty()); - - std::vector metadata_bytes; - ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes)); + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_assethub_polkadot.json"); auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes); ASSERT_TRUE(metadata); @@ -156,7 +191,12 @@ TEST(PolkadotChainMetadataUnitTest, /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/0, /*spec_version=*/2'002'001, - /*asset_tx_payment=*/true); + /*asset_tx_payment=*/true, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/32, + /*assets_transfer_keep_alive_call_index=*/9); + EXPECT_EQ(*metadata, expected); auto metadata2 = PolkadotMetadataFromChainName("Polkadot Asset Hub"); ASSERT_TRUE(metadata2); @@ -170,24 +210,8 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponseWestend) { // curl -sS -H 'Content-Type: application/json' \ // -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \ // https://westend-rpc.polkadot.io/ - const auto fixture_path = - base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) - .AppendASCII("brave") - .AppendASCII("components") - .AppendASCII("test") - .AppendASCII("data") - .AppendASCII("brave_wallet") - .AppendASCII("polkadot") - .AppendASCII("chain_metadata") - .AppendASCII("state_getMetadata_westend.json"); - const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path); - - const std::string* metadata_hex = json.FindString(kResult); - ASSERT_TRUE(metadata_hex); - ASSERT_FALSE(metadata_hex->empty()); - - std::vector metadata_bytes; - ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes)); + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_westend.json"); auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes); ASSERT_TRUE(metadata); @@ -199,7 +223,11 @@ TEST(PolkadotChainMetadataUnitTest, ParseRealStateGetMetadataResponseWestend) { /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/42, /*spec_version=*/1'022'000, - /*asset_tx_payment=*/false); + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0); EXPECT_EQ(*metadata, expected); auto metadata2 = PolkadotMetadataFromChainName("Westend"); @@ -215,24 +243,8 @@ TEST(PolkadotChainMetadataUnitTest, // curl -sS -H 'Content-Type: application/json' \ // -d '{"id":1,"jsonrpc":"2.0","method":"state_getMetadata","params":[]}' \ // https://westend-asset-hub-rpc.polkadot.io/ - const auto fixture_path = - base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) - .AppendASCII("brave") - .AppendASCII("components") - .AppendASCII("test") - .AppendASCII("data") - .AppendASCII("brave_wallet") - .AppendASCII("polkadot") - .AppendASCII("chain_metadata") - .AppendASCII("state_getMetadata_assethub_westend.json"); - const base::DictValue json = base::test::ParseJsonDictFromFile(fixture_path); - - const std::string* metadata_hex = json.FindString(kResult); - ASSERT_TRUE(metadata_hex); - ASSERT_FALSE(metadata_hex->empty()); - - std::vector metadata_bytes; - ASSERT_TRUE(PrefixedHexStringToBytes(*metadata_hex, &metadata_bytes)); + std::vector metadata_bytes = + ReadMetadataFixture("state_getMetadata_assethub_westend.json"); auto metadata = PolkadotChainMetadata::FromBytes(metadata_bytes); ASSERT_TRUE(metadata); @@ -244,7 +256,12 @@ TEST(PolkadotChainMetadataUnitTest, /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/42, /*spec_version=*/1'022'005, - /*asset_tx_payment=*/true); + /*asset_tx_payment=*/true, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/32, + /*assets_transfer_keep_alive_call_index=*/9); + EXPECT_EQ(*metadata, expected); auto metadata2 = PolkadotMetadataFromChainName("Westend Asset Hub"); ASSERT_TRUE(metadata2); diff --git a/components/brave_wallet/browser/polkadot/polkadot_test_utils.cc b/components/brave_wallet/browser/polkadot/polkadot_test_utils.cc index 77a4143784f..1b4ac805825 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_test_utils.cc +++ b/components/brave_wallet/browser/polkadot/polkadot_test_utils.cc @@ -5,8 +5,12 @@ #include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h" +#include + #include "base/base_paths.h" +#include "base/check.h" #include "base/containers/map_util.h" +#include "base/containers/span.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -85,6 +89,40 @@ std::vector ReadMetadataFixture(std::string_view file_name) { return metadata_bytes; } +bool ReplaceNthOccurrence(std::vector& bytes, + std::string_view needle, + std::string_view replacement, + size_t occurrence) { + DCHECK(!needle.empty()); + if (needle.empty()) { + return false; + } + + const auto needle_bytes = base::as_byte_span(needle); + const auto replacement_bytes = base::as_byte_span(replacement); + auto it = bytes.begin(); + size_t num_found = 0; + + while (it != bytes.end()) { + auto match = std::ranges::search(it, bytes.end(), needle_bytes.begin(), + needle_bytes.end()); + if (match.begin() == bytes.end()) { + return false; + } + + if (num_found == occurrence) { + auto pos = bytes.erase(match.begin(), match.end()); + bytes.insert(pos, replacement_bytes.begin(), replacement_bytes.end()); + return true; + } + + ++num_found; + it = match.end(); + } + + return false; +} + std::optional PolkadotMetadataFromChainName( std::string_view chain_name) { // spec_version is unknown when constructing from chain name alone; callers @@ -103,7 +141,11 @@ std::optional PolkadotMetadataFromChainName( /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/42, kUnknownSpecVersion, - /*asset_tx_payment=*/false); + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0); } // https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/asset-hub-kusama-json.json#L969 @@ -116,7 +158,11 @@ std::optional PolkadotMetadataFromChainName( /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/42, kUnknownSpecVersion, - /*asset_tx_payment=*/true); + /*asset_tx_payment=*/true, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/32, + /*assets_transfer_keep_alive_call_index=*/9); } // https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/polkadot-json.json#L1096 @@ -129,7 +175,11 @@ std::optional PolkadotMetadataFromChainName( /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/0, kUnknownSpecVersion, - /*asset_tx_payment=*/false); + /*asset_tx_payment=*/false, + /*has_assets_pallet=*/false, + /*assets_pallet_index=*/0, + /*assets_transfer_all_call_index=*/0, + /*assets_transfer_keep_alive_call_index=*/0); } // https://github.com/polkadot-js/api/blob/f45dfc72ec320cab7d69f08010c9921d2a21065f/packages/types-support/src/metadata/v15/asset-hub-polkadot-json.json#L969 @@ -142,7 +192,11 @@ std::optional PolkadotMetadataFromChainName( /*transfer_keep_alive_call_index=*/3, /*transfer_all_call_index=*/4, /*ss58_prefix=*/0, kUnknownSpecVersion, - /*asset_tx_payment=*/true); + /*asset_tx_payment=*/true, + /*has_assets_pallet=*/true, + /*assets_pallet_index=*/50, + /*assets_transfer_all_call_index=*/32, + /*assets_transfer_keep_alive_call_index=*/9); } return std::nullopt; diff --git a/components/brave_wallet/browser/polkadot/polkadot_test_utils.h b/components/brave_wallet/browser/polkadot/polkadot_test_utils.h index 9a97872e2c6..fc07f5459e5 100644 --- a/components/brave_wallet/browser/polkadot/polkadot_test_utils.h +++ b/components/brave_wallet/browser/polkadot/polkadot_test_utils.h @@ -7,8 +7,10 @@ #define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_POLKADOT_POLKADOT_TEST_UTILS_H_ #include +#include #include #include +#include #include "base/containers/flat_map.h" #include "base/values.h" @@ -25,6 +27,19 @@ base::DictValue RequestBodyToJsonDict(const network::ResourceRequest& req); std::string ReadMetadataFixtureJson(std::string_view file_name); std::vector ReadMetadataFixture(std::string_view file_name); +// Replaces the zero-based nth occurrence of `needle` in `bytes` with +// `replacement`. Returns false if `needle` is empty or if the requested +// occurrence is not found. +// +// Examples: +// ReplaceNthOccurrence("a b a b", "a", "x", 0) -> "x b a b" +// ReplaceNthOccurrence("a b a b", "a", "x", 1) -> "a b x b" +// ReplaceNthOccurrence("a b", "z", "x", 0) -> false +bool ReplaceNthOccurrence(std::vector& bytes, + std::string_view needle, + std::string_view replacement, + size_t occurrence); + struct PolkadotMockRpc { public: PolkadotMockRpc(network::TestURLLoaderFactory* url_loader_factory, diff --git a/components/brave_wallet/browser/polkadot/polkadot_test_utils_unittest.cc b/components/brave_wallet/browser/polkadot/polkadot_test_utils_unittest.cc new file mode 100644 index 00000000000..98e929c3d3c --- /dev/null +++ b/components/brave_wallet/browser/polkadot/polkadot_test_utils_unittest.cc @@ -0,0 +1,65 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/polkadot/polkadot_test_utils.h" + +#include +#include + +#include "base/containers/span.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace brave_wallet { + +namespace { + +std::vector BytesForTest(std::string_view value) { + auto bytes = base::as_byte_span(value); + return std::vector(bytes.begin(), bytes.end()); +} + +} // namespace + +TEST(PolkadotTestUtilsUnitTest, + ReplaceNthOccurrenceReplacesRequestedOccurrence) { + auto bytes = BytesForTest("transfer transfer transfer"); + + EXPECT_TRUE(ReplaceNthOccurrence(bytes, "transfer", "replaced", + /*occurrence=*/1)); + + EXPECT_EQ(bytes, BytesForTest("transfer replaced transfer")); +} + +TEST(PolkadotTestUtilsUnitTest, + ReplaceNthOccurrenceSupportsDifferentLengthReplacement) { + auto bytes = BytesForTest("before transfer after"); + + EXPECT_TRUE(ReplaceNthOccurrence(bytes, "transfer", "x", + /*occurrence=*/0)); + + EXPECT_EQ(bytes, BytesForTest("before x after")); +} + +TEST(PolkadotTestUtilsUnitTest, ReplaceNthOccurrenceMissingNeedleFails) { + auto bytes = BytesForTest("before transfer after"); + const auto original = bytes; + + EXPECT_FALSE(ReplaceNthOccurrence(bytes, "missing", "x", + /*occurrence=*/0)); + + EXPECT_EQ(bytes, original); +} + +TEST(PolkadotTestUtilsUnitTest, ReplaceNthOccurrenceOutOfRangeFails) { + auto bytes = BytesForTest("transfer transfer"); + const auto original = bytes; + + EXPECT_FALSE(ReplaceNthOccurrence(bytes, "transfer", "replaced", + /*occurrence=*/2)); + + EXPECT_EQ(bytes, original); +} + +} // namespace brave_wallet